본문 바로가기
FrameWork/Spring

싱글톤 컨테이너와 싱글톤레지스트리에 대한 고찰

by titlejjk 2023. 7. 21.

전 글에서는 스프링의 싱글톤 디자인패턴에 대해서 배웠다.

스프링의 탄생의 목적은 기업용 웹 서비스를 위해 태어났으며, 여러 고객이 동시에 사용하는 서비스를 처리하기 위해 탄생 되었는데, 이 때 발생하는 고객들의 서비스로 인해 생기는 메모리의 할당 때문에 싱글톤이 만들어졌다.

예를 들어 고객들의 수십에서 수백만건의 요청이 발생하는 서비스에서 매 요청시 새로운 객체가 생성되면서 소멸되는 과정이 발생되는데 이렇게 되면 생성되는 객체수를 메모리가 감당하기 어려워 질 수 있게 되며 해당 서비스를 운영하는 부분에 있어 서비스 장애를 초래하게 될 것이다.

하지만 이를 해결한 것이 바로 싱글톤이다.

싱글톤은 단일 인스턴스를 가지며 싱글톤패턴을 가지가되는데 이는 하나의 객체를 이용하여 고객의 요청을 처리함으로써, 효율적인 메모리 관리를 할 수 있다는 큰 장점이 있다.

 

하지만 공유자원의 동시접근시 발생되는 동시성 문제에 따른 설계의 주의

해당 싱글톤객체 생성의 클래스내의 인스턴스가 단 한개만 생성되기 위한 코드작성,

싱글톤 패턴의 의존관계상( .instance() 메서드사용) 으로 인한 클라이언트가 구체 클래스에 의존(DIP위반)

테스트마다 데이터를 초기화해주어야 하는점

싱글톤 인스턴스 생성시 private생성자로 생성되어 자식 클래스를 만들기 어려운점. 그로인한 유연성 저하

전체적으로 Anti-패턴으로 불기기도 하는 점 등등

 

이런문제들이 있었는데 이런 단점을 모두 상쇄시키며 싱글톤패턴의 장점만을 살리는 것이 바로

 

Spring 싱글톤 컨테이너이다.

 

스프링 컨테이너는 싱글톤 컨테이너같이 기존의 싱글톤 패턴의 문제점을 해결하게 해주었다.

스프링컨테이너는 싱글턴 패턴을 사용하지 않아도 객체 인스턴스를 싱글톤으로 관리해주며 이를 통해 객체를 생성하고 관리를 해주는 기능을 싱글톤 레지스트리가 있다.

이로 인해 스프링 컨테이너에 단일 인스턴스만을 가질 수 있으며 위에 단점으로 나열 되던 부분이 해결이 가능해 진다.

 

싱글톤 컨테이너의 동작 방식은 아래와 같다.

 

  1. 빈 등록
    스프링 프레임워크에서 빈으로 등록된 객체는 싱글톤으로 관리된다. 이때, 빈은 xml설정 파일이나 자바 어노테이션을 사용하여 등록할 수 있다.
  2. 초기화
    빈은 스프링 컨테이너가 생성될 때 미리 인스턴스화 된다. 이때 객체의 생성과 초기화가 이루어 진다.
  3. 요청처리
    애플리케이션에서 해당 빈이 필요한 경우, 스프링 컨테이너는 이미 생성된 인스턴스를 반환한다.
  4. 공유
    싱글톤 컨테이너에서 관리되는 빈은 항상 동일한 인스턴스를 반환하므로, 이를 공유하면서 일관성을 유지할 수 있다.

 

싱글톤 레지스트리

 

스프링은 서버환경에서 싱글톤이 만들어져서 서비스 오브젝트 방식으로 사용되는 것을 적극적으로 지지한다. 하지만 위와 같은 단점을 보완하면서 스프링이 직접 싱글톤 형태의 오브젝트를 만들고 관리하는 기능을 제공하는 것이 싱글톤 레지스트리(singleton registry)다. 

스태틱 메서드와 private생성자를 사용해야하는 정상적인 클래스가 아닌 평범한 자바 클래스를 싱글톤으로 활용하게 해준다. 평범한 자바 클래스라도 IoC방식의 컨테이너를 사용해서 생성과 관계설정, 사용 등에 대한 제어권을 컨테이너에게 너미면 손쉽게 싱글톤 방식으로 만들어져 관리되게 할 수 있다.

오브젝트 생성에 대한 모든 권한은 IoC기능을 제공하는 애프리케이션 컨텍스트에게 있기 때문이다.

 

싱글톤 레지스트리 덕분에 싱글톤 방식으로 사용될 애플리케이션 클래스라도 public생성자를 가질 수 있고, 싱글톤으로 사용돼야 하는 환경이 아니라면 간단히 오브젝트를 생성해서 사용할 수 있다. 싱글톤으로 사용돼야 하는 환경이 아니라면 간단히 오브젝트를 생성해서 사용할 수 있다. 테스트환경에서 진행할 목 오브젝트로 대체도 가능하다.

 

가장 큰 이점으로는 싱글톤 패턴과 달리 스프링이 지지하는 객체지향적인 설계 방식과 원칙, 디자인 패턴(싱글톤 패턴 제외)등을 적용하는 데 아무런 제약이 없다. 

 

스프링은 IoC컨테이너일 뿐만이 아닌, 고전적인 싱글톤 패턴을 대신해서 싱글톤을 만들고 관리해주는 싱글톤 레지스트리 기능이 있는 것이다.

그래서 .getBean()을 아무리 여러 번을 호출해서 요청하더라도 매번 같은 오브젝트를 부여 받게 된다.

 

하지만

 

좋은 기능인 싱글톤컨테이너 이지만 주의할 점이 있다.

 

기본적으로 싱글톤이 멀티스레드 환경에서 서비스 형태의 오브젝트로 사용되는 경우에는 상태정보를 내부에 갖고 있지 않은 무상태(stateless)방식으로 만들어져야 한다, 다중 사용자의 요청을 한꺼번에 처리하는 스레드들이 동시에 싱글톤 오브젝트의 인스턴스 변수를 수정하는 것은 리스크가 매우 크다. 하나의 저장공간을 사용 하므로 서로의 값을 덮어쓰고 자신이 저장하지 않은 값을 읽어올 수가 있기 때문이다.

그러므로 싱글톤은 기본적으로 인스턴스 필드의 값을 변경하고 유지하는 상태유지(stateful)방식으로 만들지 않는다. 이를 지키지 않으면 개발자 혼자서 개발하고 테스트 할 때 딱히 문제는 없지만, 서버에 배포되고 여러 사용자가 동시에 접속하면 데이터가 엉망이 된다.

 

상태가 없는 방식으로 클래스를 만드는 경우에 각 요청에 대해 정보나, DB나 서버의 리소스로부터 생성한 정보는 어떻게 다뤄하나?

👉이럴 경우 파라미터와 로컬 변수, 리턴 값을 이용한다.
   메서드 파라미터나 메서드로 인해 생성되는 로컬 변수는 매번 새로운 값을 저장할 독립적인 공간이 만들어지기 때문에 싱글톤이라해도

   여러 스레드가 변수의 값을 덮어쓸 일이 없기 때문이다.

 

그 외에도 정리해보자면 다음과 같다.

 

  1. 스레드 안전성
    싱글톤 빈은 여러 스레드에서 동시에 접근할 수 있다. 따라서 빈의 메서드가 스레드 안전하지 않은 경우 동시성 문제가 발생할 수 있다. 만약 빈이 상태를 가지고 있고, 여러 스레드가 동시에 해당 상태를 변경한다면, 적절한 동기화를 고려해야함
  2. 의존성 주입
    싱글톤 빈이 다른 빈들과 의존성을 가지게 된다면 이 의존성들도 모두 싱글톤으로 관리된다. 이는 큰 오류를 초래할 수 있으므로 만약 빈이 다른 빈과 별개의 상태를 가져야 한다. 의존성 주입 방식을 적절히 선택해야하는 숙제를 가지고 있다.
  3. 빈의 범위 설정
    스프링은 싱글톤 이외에도 프로토타입, 세션, 요청과 같은 다양한 빈의 범위를 설정 가능하다. 싱글톤 컨테이너에서 모든 빈을 싱글톤으로 등록하면 원하지 않는 메모리 낭비가 발생할수 있으니 빈의 범위를 적절하게 설정해야 한다.
  4. 메모리의 누수
    싱글톤 빈은 애플리케이션이 종료될 때마다 메모리에 남아있는데 빈이 사용하지 않는 자원을 계속 유지할 경우 메모리 누수가 발생한다. 특히 외부 리소스를 사용하는 경우, 리소스를 해제하는 매커니즘이 필요하다.
  5. 불변(Immutable)객체 사용
    싱글톤 빈의 상태를 변경하지 않는 불변객체를 사용해야한다. 불변 객체는 스레드 안전성을 보장하며 상태 변경으로 인한 문제를 방지 할 수 있다.
  6. 테스트
    싱글톤 컨테이너는 애플리케이션 전반에 걸쳐서 인스턴스를 공유마흐로, 테스트 시 예상치 못한 오류 발생을 야기한다. 특히 상태 변경이 이루어지는 경우 테스트간의 의존성문제가 발생 가능하므로, 각 테스트가 독립적으로 동작하도록 한다.
  7. 대용량 객체 사용
    대용량 데이터를 다루는 객체 싱글톤을 사용할 경우, 메모리 사용량 증가로 인한 성능 저하 이슈 발생가능성이 있으니 캐싱 등의 방법을 고려하여 최적화해 줘야한다.
  8. 적절한 사용 시점
    모든 객체를 싱글톤으로 등록하는 것은 절대로 적절하지 않는 판단이다.
    상태가 변하지 않거나 공유할 필요가 없는 객체들은 싱글톤으로 등록하지 않는 것이 바람직하다.

이런 문제를 고려하지 않고 코드를 작성하게 되면 아래와 같은 문제점이 발생하게 된다.

분명 

userA가 10000원 userB가 20000원을 동시에 가격을 넣었는데 동시에 넣다보니 userB가 입력한 20000원이 출력이 되었다.

 

userA입장에서는 10000원을 결제 했는데 userB가 중간에 결제를 진행해버리는 바람에 userA의 결제값이 20000원이 바꿔치기 되어 버렸다.

 

StatefulService는 싱글톤으로써 price필드가 공유되는 필드인데 특정 클라이언트가 공유되는 필드가 변형이 되었다.

 

위와같은 상황때문에 무상태로 설계를 해야한다.(필드 대신에 자바에서 공유되지 않는, 지역변수, 파라미터, ThreadLoca등을 사용해야한다)

 

로컬변수(지역변수)의 경우 각 스레드가 고유의 스택 메로리를 생성하여 지역변수를 보관한다. 그로인해 공유문제가 발생하지 않는다.

 

'FrameWork > Spring' 카테고리의 다른 글

싱글톤 컨테이너  (0) 2023.07.24
웹 애플리케이션과 싱글톤  (0) 2023.07.19
@RequiredArgsConstructor  (0) 2023.07.16
Spring은 어떻게 다양한 설정 형식을 지원할까?BeanDefinition  (0) 2023.07.13
JPA (작성중)  (0) 2023.07.12

댓글