김영한님의 스프링 핵심원리 고급편 강의를 들으면서 학습한 내용을 정리한 글입니다.

 

 

 

프록시(Proxy)란 어떤 것을 직접 호출하지 않고 대리자를 통해 간접적으로 호출할 때 대리자에 해당하는 개념입니다. 네트워크에서는 보안상의 문제를 해결하기 위해 클라이언트가 직접 서버에 접근하지 않고 프록시 서버를 거쳐 접근하곤 합니다.

 

직접 호출
클라이언트와 서버 개념에서 클라이언트가 서버를 직접 호출하고, 처리 결과를 직접 받는 것

 

간접 호출
클라이언트가 요청을 서버에 직접 하는 것이 아니라, 대리자를 통해서 간접적으로 서버에 요청하는 것. 이때 대리자를 프록시라 합니다.

 

프록시의 주요 기능

  • 접근제어
    • 권한에 따른 접근 차단
    • 캐싱
    • 지연 로딩
  • 부가 기능 추가
    • 원래 서버가 제공하는 기능에 더해 부가 기능을 수행
    • ex) 요청, 응답을 변형.
    • ex) 실행 시간을 측정해 추가 로그를 남긴다.
  •  

프록시 패턴


https://javadevcentral.com/proxy-pattern-vs-decorator-pattern

프록시 패턴은 실제객체(Service)를 Proxy 객체가 감싸고 있는 형태로 실제 객체에 대한 접근제어를 프록시가 진행하게 됩니다.

 

프록시 캐시 적용 전

프록시를 적용하기 전에는 클라이언트가 실제 객체(RealSubject)를 직접 호출 하여 데이터("data")를 가져오는 형태로 작성하였습니다. 매번 데이터를 직접 접근하여 가져오게 되기 때문에 접근횟수만큼 지연시간이 길어진다는 단점이 있습니다.

 

> 접근 과정 : Client -> ChacheProxy -> RealSubject

 

RealSubject.java

@Slf4j
public class RealSubject implements Subject {

  @Override
  public String operation() {
    log.info("실제 객체 호출");
    sleep(1000); //DB 접근 지연시간
    return "data";
  }

  private void sleep(int millis) {
    try {
      Thread.sleep(millis);
    } catch (InterruptedException e) {
      throw new RuntimeException(e);
    }
  }

}
16:55:44.350 [Test worker] INFO hello.proxy.pureproxy.proxy.code.RealSubject - 실제 객체 호출
16:55:45.358 [Test worker] INFO hello.proxy.pureproxy.proxy.code.RealSubject - 실제 객체 호출
16:55:46.365 [Test worker] INFO hello.proxy.pureproxy.proxy.code.RealSubject - 실제 객체 호출

 

프록시 캐시 적용 후

프록시 패턴을 통해 캐시를 적용하였습니다. 한번 DB에 접근한 후에는 프록시 객체의 메모리 캐시에 데이터를 저장해 두고 이후 호출에서는 DB를 호출하지 않고 캐시의 데이터를 반환하도록 설계하였습니다


> 접근 과정 : Client -> ChacheProxy -> RealSubject

 

ChachProxy.java

@Slf4j
public class CacheProxy implements Subject {

  private Subject subject;
  private String cache; //메모리 캐시

  public CacheProxy(Subject subject) {
    this.subject = subject;
  }

  @Override
  public String operation() {
    log.info(“프록시 객체 호출”);
    if(cache == null) {
      return cache = subject.operation();
    }
    return cache;
  }

}
16:55:43.312 [Test worker] INFO hello.proxy.pureproxy.proxy.code.CacheProxy - 프록시 객체 호출
16:55:43.319 [Test worker] INFO hello.proxy.pureproxy.proxy.code.RealSubject - 실제 객체 호출
16:55:44.324 [Test worker] INFO hello.proxy.pureproxy.proxy.code.CacheProxy - 프록시 객체 호출
16:55:44.325 [Test worker] INFO hello.proxy.pureproxy.proxy.code.CacheProxy - 프록시 객체 호출

실제 객체의 호출은 한번만 이루어지고 이후에는 프록시 객체만을 호출하는 형태로 DB의 호출 횟수를 줄일 수 있었습니다. 실제로 JPA에서는 영속성 컨텍스트를 통해 1차캐시기능을 제공하여 DB접근 병목을 해결합니다. 

 

 

데코레이터 패턴


https://refactoring.guru/design-patterns/decorator

데코레이터 패턴은 상황과 용도에 다라 객체에 부가기능을 동적으로 부여하기 위해 사용됩니다.

 

데코레이터 패턴 적용

데코레이터 패턴을 적용하여 실제 객체(RealComponent)에 부가기능을 적용해 보았습니다.

 

> 접근 과정 : Client -> TimeDecorator -> MessageDecorator -> RealComponent

 

RealComponent.java

@Slf4j
public class RealComponent implements Component {

  @Override
  public String operation() {
    log.info(“RealComponent 실행”);
    sleep(1000);
    return “data”;
  }

  private void sleep(int millis) {
    try {
      Thread.sleep(millis);
    } catch (InterruptedException e) {
      throw new RuntimeException(e);
    }
  }
}

MssageDecorator.java

@Slf4j
public class MessageDecorator implements Component {

  private Component component;

  public MessageDecorator(Component component) {
    this.component = component;
  }

  @Override
  public String operation() {
    log.info("MessageDecorator 실행");
    return "****" + component.operation() + "****";
  }
}

 

프록시 패턴과 데코레이터 패턴의 차이


둘 다 프록시를 사용하는 방식이지만, 의도에 따라 프록시패턴과 데코레이터 패턴으로 구분합니다.

  • 프록시 패턴
    • 접근 제어
    • 구성 관계(Composition)
    • 프록시 객체와 실제 객체의 관계가 컴파일 타임에 정해진다.
  • 데코레이터 패턴
    • 부가기능 추가
    • 집합 관계(Aggregation)
    • 프록시 객체와 실제 객체의 관계가 런타임에 정해진다.

 

구체 클래스 기반 프록시(상속)


@Slf4j
public class ConcreteLogic {

  public String operation() {
    log.info("ConcreteLogic 실행");
    return "data";
  }

}
@Slf4j
public class TimeProxy extends ConcreteLogic {

  private ConcreteLogic concreteLogic;

  public TimeProxy(ConcreteLogic concreteLogic) {
    this.concreteLogic = concreteLogic;
  }

  @Override
  public String operation() {
    log.info("TimeProxy 실행");

    long start = System.currentTimeMillis();
    var result = concreteLogic.operation();
    long end = System.currentTimeMillis();
    long resultTime = end - start;

    log.info("TimeProxy 종료, Running time={}", resultTime);
    return result;
  }

}

 

위의 예제에 부모 클래스에 final 키워드가 붙은 필드가 추가된다면 부모 클래스의 생성자를 호출해야 하기 때문에 파라미터를 넣어서 super(..) 호출이 강제되게 됩니다. 또한 클래스나 메서드에 final 키워드가 붙는 경우 상속이 제한되기 때문에 클래스 기반의 프록시를 사용할 수 없습니다.

 

인터페이스 기반 프록시(합성)


public interface Subject {

  String operation();

}
@Slf4j
public class RealSubject implements Subject {

  @Override
  public String operation() {
    log.info("실제 객체 호출");
    sleep(1000);
    return "data";
  }

  private void sleep(int millis) {
    try {
      Thread.sleep(millis);
    } catch (InterruptedException e) {
      throw new RuntimeException(e);
    }
  }

}
@Slf4j
public class CacheProxy implements Subject{

  private Subject subject;
  private String cache;

  public CacheProxy(Subject subject) {
    this.subject = subject;
  }

  @Override
  public String operation() {
    log.info("프록시 객체 호출");
    if(cache == null) {
      return cache = subject.operation();
    }
    return cache;
  }

}

인터페이스 기반의 프록시는 상속이라는 제약에서 자유롭고, 역할과 구현을 명확하게 나눌 수 있다는 점에서 장점이 있습니다.

 

하지만 인터페이스가 필요하다는 단점이 있습니다. 인터페이스의 도입은 구현이 변경될 가능성이 있을 때 효과적인데, 구현이 변경될 가능성이 없는 코드에 항상 인터페이스를 도입하는 것은 번거로울 수 있습니다.

 

Reference


'CS' 카테고리의 다른 글

[Network] 웹소켓(Websocket)  (0) 2022.10.09
[OS] 데드락의 탐지와 해결방법  (1) 2022.10.03
[Design Pattern] Facade 패턴을 통한 SRP원칙 준수  (0) 2022.08.22
[Design Pattern] Singleton Pattern  (0) 2022.08.22
[OS] Scheduler  (0) 2022.08.22

1. 웹소켓(Websocket)


[웹소켓이란?]

HTTP의 경우 클라이언트의 요청이 있을때만 서버가 응답하고, 매번 연결을 맺고 끊는 비용이 발생하기 때문에 실시간성을 보장해야하는 서비스에서는 적합하지 않습니다. 이경우 양방향 통신 방식을 사용하는것이 적합한데 이때 사용할 수 있는 프로토콜이 웹소켓입니다.

 

웹소켓은 2011년 IETF에 의해 표준화(RFC 6455)된 양방향 통신 프로토콜입니다. 웹소켓 연결 수립을 위해 HTTP프로토콜을 사용하며, 기존 HTTP프로토콜의 포트(80, 443)과 호환되어 추가적인 방화벽 설정이 필요하지 않다는 장점이 있습니다. HTML5를 지원하는 대부분의 브라우저가 웹소켓을 지원하고 있습니다.

 

 

[HTTP 과 Websocket의 비교]

HTTP Websocket
단방향(클라이언트 → 서버) 양방향(클라이언트 ↔ 서버)
비연결성 연결지향
필요한 경우에만 서버에 접근하는 서비스에 적합 서버와 실시간으로 데이터를 주고받아야 하는 서비스에 적합

 

 

[웹소켓과 소켓 연결]

웹소켓과 소켓은 IP와 PORT를 사용한다는 점에서 유사하지만, 소켓 연결은 TCP/IP 프로토콜을 기반으로 연결을 맺는 네트워크 연결방식으로 전송계층에서 동작합니다. 반면 웹소켓의 경우 HTTP를 통해 연결을 수립하며 애플리케이션 계층에서 동작한다는 차이가 있습니다.

 

 

2. 웹소켓의 동작 방식


 

[Connection State]

클라이언트의 웹소켓 연결 요청

GET /chat HTTP/1.1
Host: example.com:8000
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13

클라이언트는 웹소켓 연결을 수립하기 위해서 HTTP 요청보내야 합니다. 이때 반드시 HTTP 1.1 버전 이상이어야 하며, GET 메서드를 사용하여 연결을 요청해야 합니다. Upgrade, Connection 헤더를 통해 웹소켓 프로토콜로의 업그레이드 요청을 하게됩니다.

 

 

서버측 웹소켓 연결 요청 응답

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

서버가 클라이언트로부터 웹소켓 연결 요청을 받게되면, 서버는 101 상태코드로 프로토콜을 변경하겠다는 응답을 하게 됩니다. 이때 Sec-WebSocket-Accept 의 값은 클라이언트가 보낸 Sec-Websocket-Key 로부터 계산된 값으로 클라이언트가 계산한 값과 일치하지 않으면 연결이 수립되지 않습니다.

 

 

[Open State]

데이터 프레임

연결이 수립되고 나면 다음과 같은 데이터 프레임을 통해 데이터를 주고 받게됩니다.

Frame format:

      0                   1                   2                   3
      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
     +-+-+-+-+-------+-+-------------+-------------------------------+
     |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
     |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
     |N|V|V|V|       |S|             |   (if payload len==126/127)   |
     | |1|2|3|       |K|             |                               |
     +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
     |     Extended payload length continued, if payload len == 127  |
     + - - - - - - - - - - - - - - - +-------------------------------+
     |                               |Masking-key, if MASK set to 1  |
     +-------------------------------+-------------------------------+
     | Masking-key (continued)       |          Payload Data         |
     +-------------------------------- - - - - - - - - - - - - - - - +
     :                     Payload Data continued ...                :
     + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
     |                     Payload Data continued ...                |
     +---------------------------------------------------------------+

 

 

[Closing State]

웹소켓 종료 요청, 응답

웹소켓 종료 요청은 클라이언트, 서버 상관없이 보낼수 있습니다. 요청을 받은 상대방은 웹소켓 종료 요청에 대한 응답을 보내고 웹소켓 연결을 종료합니다.

 

 

3. Websocket Emulation : SockJS


[Websocket Emulation]

만약 브라우저에서 websocket을 지원하지 않는다면, Websocket Emulation기술을 사용해 웹소켓을 사용하는것처럼 사용자 경험을 제공할 수 있습니다. 대표적으로 SockJS와 Socket.io가 있는데 SockJS는 스프링에서, Socket.io는 node.js에서 주로 사용됩니다.

 

 

[SockJS의 동작 방식]

SockJS클라이언트는 GET /info 요청을 통해 브라우저가 어떤 전송방식을 지원하는지 확인합니다. 만약 브라우저가 Websocket을 지원하지 않는다면 WebSocket → HTTP Streaming → HTTP Long Polling 순으로 연결 방식을 바꿉니다. SockJS를 사용하기 위해서는 SockJS 클라이언트를 통해 연결을 요청해야 하며, 서버측은 SockJS를 지원해야 합니다.

 

4. Polling, Long Polling, Streaming


[Polling]

 

브라우저가 새로운 정보를 확인하기 위해 요청을 계속 전송하고 서버는 이에 대해 매번 즉시 응답하는 방식입니다. 클라이언트가 정보를 확인하는 빈도가 빈번하지 않은경우에 적합합니다. 클라이언트가 매번 요청을 보내기 때문에 클라이언트의 수가 많아지면 서버의 부담이 커진다는 단점이 있습니다.

 

 

 

[Long Polling]

 

Polling 방식과 동일하게 브라우저가 새로운 정보를 확인하기 위해 요청을 계속 전송하지만, 서버는 새로운 정보가 업데이트 될때까지 요청에 대해 응답하지 않는 방식입니다. 데이터 업데이트가 빈번한경우 Polling 방식에 비해 성능 이점이 크지 않습니다. 다수의 클라이언트에 대해 동시에 이벤트가 발생할 경우 서버의 부담이 커진다는 단점이 있습니다.

 

 

 

[Streaming]

 

서버가 클라이언트에 응답을 보낼때 연결을 닫지않고 계속해서 응답을 보내는 방식입니다. 응답시 연결을 유지하기 때문에 Polling 방식에 비해 요청이 줄어든다는 장점이 있지만, 일반적인 HTTP통신 방식이 아니기 때문에 범용성이 떨어집니다.

 

 

Reference

'CS' 카테고리의 다른 글

[DesignPattern] 프록시(Proxy)  (0) 2022.11.11
[OS] 데드락의 탐지와 해결방법  (1) 2022.10.03
[Design Pattern] Facade 패턴을 통한 SRP원칙 준수  (0) 2022.08.22
[Design Pattern] Singleton Pattern  (0) 2022.08.22
[OS] Scheduler  (0) 2022.08.22

데드락의 정의

  • 두 개 이상의 프로세스나 스레드가 서로 자원을 얻지 못해 다음 처리를 하지 못하는 상태
  • 무한히 다음 자원을 기다리게 되는 상태를 말한다

식사하는 철학자

  • 다익스트라가 운영체제의 교착상태(Deadlock)을 설명하기 위해 낸 문제
  • 5명의 철학자가 원탁에 앉아서 식사를 한다
    • 왼쪽 포크가 사용가능할때 까지 대기한다. 사용가능하다면 집어든다.
    • 오른쪽 포크가 사용가능할때 까지 대기한다. 사용가능하다면 집어든다
    • 양쪽의 포크를 잡으면 일정 시간만큼 식사를 한다.
    • 오른쪽 포크를 내려놓는다.
    • 왼쪽 포크를 내려놓는다.
    • 다시 1번으로 돌아간다
  • 모든 철학자가 왼쪽의 포크를 집어든 상태에서 오른쪽 포크가 사용가능할때 까지 무한히 대기하는 상황이 연출 된다.

교착상태의 발생 조건

  • 상호배제
    • 한 프로세스가 자원을 점유하고 있을때 다른 프로세스가 해당 자원을 점유하지 못하는 것.
  • 점유대기
    • 프로세스가 자원을 점유하고 있는 상태에서 다른 자원을 기다린다.
  • 비선점
    • 프로세스는 해당 자원을 사용하는 프로세스가 자원을 자발적으로 반환할 때까지 기다린다.
  • 순환대기
    • A프로세스가 필요한 자원을 B프로세스가 점유하고있고, B프로세스가 필요한 자원을 A프로세스가 점유하고 잇는 상황

교착 상태의 해결 방법

예방

  • 교착 상태 발생조건중 하나를 제거하여 교착상태를 예방할 수 있다.
    • 상호 배제 방지
      • 여러 프로세스가 동시에 자원을 점유하게 되면, 동시성 문제가 발생할 수 있다.
    • 점유 대기 방지
      • 자원을 요구할때 자원을 반납하고 요구한 자원을 사용하기 위해 기다리게 하면 자원에 대한 내용을 저장하고 복원하기 위한 비용이 발생하기 때문에 비효율적이다. 또한 여러자원에 일관성을 보장해야 하는 작업의 경우 여러 자원에 동시에 접근하여 업데이트 해야 하므로, 이방법을 적용할 수 없다.
    • 비선점 방지
      • 만약 선점을 가능하게 할 경우 기아상태가 발생할 수 있으며, 작업의 진행상태를 저장하지 않는 자원의 경우 적용이 어렵다.
    • 순환 대기 방지
      • 자원에 우선순위를 매겨 순차적으로 획득하도록 한다면, 추가로 필요한 자원들이 어떠한 프로세스에게도 점유 되어 있지 않음을 보장할 수 있지만, 우선순위를 매기는데 비용이 크다.
  • 시스템 처리량이나 자원 사용의 효율성을 떨어트릴 수 있다.

회피

  • 데드락 발생 가능성을 지속적으로 검사해서 데드락을 회피하는 방식.
  • 은행원 알고리즘
    • Safe State(안전 상태) : 시스템이 교착상태를 일으키지 않으며, 각 프로세스가 요구한 최대 요구량 만큼 자원을 할당할 수 있는 상태. 안전순서열 존재
    • Unsafe State(불안전 상태) : 안전순서열이 존재하지 않는 상태, 교착상태의 필요조건으로 무조건 교착상태가 발생하는 것은 아니다.
    • 요구사항
      • Max : 각 고객들이 최대로 요구할 돈
      • Allocated : 각 고객들이 빌리고 있는 돈
      • Available : 빌려 줄 수 있는 돈
  • 은행원 알고리즘은 해당 프로세스가 시작할때 프로세스가 가져야할 자원의 최대 개수를 미리 알아야 하기 때문에 실제 돌아가는 프로그램에 적용하기 어렵다.

은행이 가지고있는 자원 : 12

최대 요구량 현재 할당량

A 10 5
B 5 2
C 9 2

안전 순서열 : B → A → C

은행은 현재 9달러를 할당하고 있으므로 가용 달러는 3달러이다.

B : 3달러를 B에게 빌려주면, B는 채무를 해결하고 은행은 5달러를 돌려받아 가용 달러는 5달러가 된다.

A : 5달러를 A에게 빌려주면, A는 채무를 해결하고 은행은 10달러를 돌려받아 가용달러는 10달러가 된다.

C : 7달러를 C에게 빌려주면, C는 채무를 해결하고 모든 대출자가 채무를 해결하게 된다.

탐지

  • 각 유형의 자원이 한개씩 있는 경우
    • 대기 그래프
      • 자원 할당 그래프에서 자원을 제거한 후 간선들을 결합하여 사이클이 발생하면 교착상태로 판단한다
  • 각 유형의 자원이 여러개 있는 경우
    • 대기 그래프 는 사용할 수 없으며, 은행원 알고리즘과 같이 상태정보를 가지는 자료구조를 사용해야 한다.
  • 탐지 알고리즘의 실행 시점
    • 교착 상태가 자주 발생할수록 자주 실행시켜야 한다.
    • 자원요청할 때마다 탐지 알고리즘을 호출하면 성능저하가 발생한다.
    • 지정된 시간간격으로 돌리거나 CPU사용률을 기준으로 돌린다.

회복

  • 교착상태가 발생한 이후 문제를 해결하는 방법
    • 교착상태의 프로세스를 종료한다. (모두 종료하거나, 하나씩 종료해가며 해결)
    • 교착상태의 프로세스를 선점하여 다른 프로세스에 할당한다.
      • 희생자 선택 : 최소의 피해를 줄 수 있는 프로세스를 선택해야 한다.
      • 롤백 : 선점당한 프로세스를 문제없는 이전 상태로 롤백해야 한다.
      • 기아상태 : 한 프로세스가 계속 자원을 선점하지 못하도록 해야한다.

무시

  • 교착상태해결을 위해서도 문맥교환으로 인한 오버헤드가 발생한다. 교착 상태로 인한 오버헤드보다 교착상태 해결을 위한 오버헤드가 큰 경우 무시한다.

'CS' 카테고리의 다른 글

[DesignPattern] 프록시(Proxy)  (0) 2022.11.11
[Network] 웹소켓(Websocket)  (0) 2022.10.09
[Design Pattern] Facade 패턴을 통한 SRP원칙 준수  (0) 2022.08.22
[Design Pattern] Singleton Pattern  (0) 2022.08.22
[OS] Scheduler  (0) 2022.08.22

Goal

  • Facade 패턴에대한 이해
  • SRP원칙에 대한 이해

SRP : 단일 책임 원칙(Single responsibility principle)

객체는 변화해야 할 단 한가지 이유만을 가져야 합니다.

쉽게 생각하면 변경이 있을때 파급 효과가 적다면, 즉 수정해야 할 코드가 적다면 단일 책임 원칙을 잘 따른것이라 볼 수 있습니다.

다음과 같이 SRP위반 사례를 예시로 SRP를 지켜야 하는 이유에 대해 알아보겠습니다.

Problem 1 : Accidental duplication

다음은 급여 애플리케이션의 Employee 클래스입니다.

Employ 클래스에는 calculatePay(), reportHours(), save() 메서드가 존재하는데 각각은 회계팀, 인사팀, 전산팀이 주로 사용합니다.

개발자가 이 세 메서드를 Employee 단일 클래스에 배치하여 서로 결합되었습니다. 이로인해 각 엑터의 결정이 다른 엑터가 의존하는 부분에 영향을 줄 수 있게 됩니다.

class Employee {
    int hour;

    // Actor : 회계팀
    public int calculatePay() { 
        ...
        regularHour();
        ... 
    }

    // Actor : 인사팀
    public void reportHours() { 
        ...
        regularHour();
        ... 
    }

    // Actor : 전산팀
    public void save() { ... }

    // 초과 업무 시간을 제외한 업무시간 계산
    private int regularHour() { ... }
}

calculatePay()reportHours()는 초과 업무 시간을 제외한 업무시간을 계산하는 regularHour()를 공유합니다. 회계팀에서 초과 업무 시간을 제외한 업무시간 계산 방식을 변경하고자 regularHour()를 수정하게 되면 기존의 방식을 그대로 사용하던 인사팀은 이에 영향을 받게 되고, reportHours()는 기존의 방식대로 동작하지 못하게 됩니다.

이러한 문제는 서로 다른 액터가 의존하는 코드를 너무가까이(단일 클래스)에 배치했기 때문에 발생합니다.

Problem 2 : Merge

인사팀에서는 reportHours()를 전산팀에서는 Employee의 스키마를 수정하기로 동시에 결정합니다. 각 팀의 수정사항이 반영될 때 충돌 즉, 병합이 발생하게 됩니다.

Solve

이러한 문제를 해결 하기 위해서는 데이터를 가지고 있는 EmployeeData클래스와 각 메서드를 가지고 있는 클래스로 분리하고 메서드를 가진 각 클래스들이 EmployeeData클래스를 공유하도록 하면 됩니다. 하지만 이경우 개발자가 메서드를 가진 세 클래스를 인스턴스화 하고 추적해야한다는게 단점입니다.

이경우 퍼사드 패턴(Facade Pattern)을 적용하여 클래스의 생성하여 인스턴스의 생성을 위임할 수 있습니다. 퍼사드 패턴은 서브 클래스간의 통합 인터페이스를 제공하는 역할로 클라이언트는 퍼사드 객체에서 제공하는 메서드를 호출함으로써 복잡한 서브클래스 사용을 간편하게 할 수 있습니다.

class EmployeeData {
    //data(공유자원)
    int hour;

    private EmployeeData() {
        this.hour = 0;
    }

    private static class LazyHolder {
        static final EmployeeData EMPLOYEE_DATA = new EmployeeData();
    }

    public static EmployeeData getInstance() {
        return LazyHolder.EMPLOYEE_DATA;
    }
}

class PayCalculator {
    final EmployeeData data = EmployeeData.getInstance();

    void calculatePay() {
        // 예제 코드
        data.hour += 1;
    }
}

class HourReporter {
    final EmployeeData data = EmployeeData.getInstance();

    void reportHours() {
        // 예제 코드
        data.hour += 1;
    }
}

class EmployeeSaver {
    final EmployeeData data = EmployeeData.getInstance();

    void save() {
        // 예제 코드
        data.hour += 1;
        System.out.println(data.hour);
    }
}

class EmployeeFacade {
    private final PayCalculator payCalculator;
    private final HourReporter hourReporter;
    private final EmployeeSaver employeeSaver;

    public EmployeeFacade() {
        payCalculator = new PayCalculator();
        hourReporter = new HourReporter();
        employeeSaver = new EmployeeSaver();
    }

    public void calculatePay() {
        payCalculator.calculatePay();
    }

    public void reportHour() {
        hourReporter.reportHours();
    }

    public void save() {
        employeeSaver.save();
    }
}

public class Facade {
    public static void main(String[] args) {
        EmployeeFacade employeeFacade = new EmployeeFacade();

        employeeFacade.calculatePay();
        employeeFacade.reportHour();
        employeeFacade.save();
    }
}

Singleton pattern을 적용하여 공유자원인 EmployeeData에 대한 thread-safety를 확보하였고, EmployeeFacade 클래스를 통해 객체의 생성을 위임하여 개발자가 메서드를 가진 세 클래스에 대한 인스턴스화를 하지 않아도 되도록 설계를 하였습니다.

다만 이러한 Facade 패턴을 관련 없는 메소드를 엮기 위해 사용할 경우 God-object가 될 수 있으므로 성능향상(관련없는 객체도)과 유지보수성(관련있는 객체만)의 Trade-off를 잘 고려하여 사용하여야 합니다.

Reference

'CS' 카테고리의 다른 글

[Network] 웹소켓(Websocket)  (0) 2022.10.09
[OS] 데드락의 탐지와 해결방법  (1) 2022.10.03
[Design Pattern] Singleton Pattern  (0) 2022.08.22
[OS] Scheduler  (0) 2022.08.22
[OS] RAM disk vs Disk 성능비교  (0) 2022.08.22

Definition

Singleton의 사전적 의미는 외동, 카드 패에서 한장만 있는것을 지칭하는 말입니다.
이러한 사전적 의미와 비슷하게, Singleton Pattern은 객체를 하나만 생성하도록 제한하기 위해 사용하는 디자인 패턴입니다.

생성패턴의 종류

  1. 추상 팩토리 패턴(Abstract Factory Pattern)
  2. 빌더 패턴(Builder Pattern)
  3. 팩토리 메서드 패턴(Factory Method Pattern)
  4. 프로토타입 패턴(Prototype Pattern)
  5. 싱글턴 패턴(Singleton pattern)

Problem

1. 싱글톤 패턴은 클래스가 인스턴스를 하나만 가짐을 보장합니다.

이렇게 인스턴스의 수를 통제하는 가장 보편적인 이유는 데이터 베이스나 파일과 같은 일부 공유 리소스에 대한 액세스를 제어하기 위함입니다.

생성자 호출은 항상 새로운 객체를 반환하기 때문에 생성자로는 이러한 동작을 구현할 수 없습니다.

2. 인스턴스에 대한 전역 접근을 허용합니다.

전역변수와 마찬가지로 프로그램의 모든 위치에서 인스턴스에 접근할 수 있으며, 다른 코드에 의해 인스턴스가 덮어쓰이지 않도록 보호하기도 합니다.

Solution

  • Singleton 패턴을 적용한 클래스의 생성자의 접근지정자를 private으로 둠으로써 생성자에 의한 새로운 객체 생성을 제한합니다.
  • 정적(static) 생성 메서드는 인스턴스가 호출된 적이 없다면, private 생성자를 호출하고, 생성된 인스턴스를 static field에 캐싱하여 이후 호출되더라도 같은 인스턴스를 반환하게 합니다.

요구사항

  • 개발중인 시스템의 스피커에 접근할 수 있는 클래스를 만들어라

만약 스피커의 인스턴스가 100개 존재할 경우 볼륨을 1올리기 위해서 100개의 모든 스피커 인스턴스에 접근하여
볼륨을 올리는 작업을 수행해 주어야 합니다. 이는 개발의 복잡도를 높이고, 시스템 리소스도 크게 낭비하게 합니다.

다음과 같이 싱글턴 패턴(Singleton Pattern)을 사용하여 이러한 문제를 방지할 수 있습니다.

class SystemSpeaker {
    static private SystemSpeaker speaker;
    private int volume;

    // 외부에서의 생성자 접근을 방지하기 위해 private 접근 지정자를 사용
    private SystemSpeaker() {
        this.volume = 5;
    }

    public static SystemSpeaker getInstance() {
        // speaker 가 null일 경우에만 생성하여 인스턴스가 하나만 생성됨을 보장.
        if(speaker == null) {
            speaker = new SystemSpeaker();
        }
        return speaker;
    }
}

Client Side

public class Singletone {
    public static void main(String[] args) {
        SystemSpeaker speaker1 = SystemSpeaker.getInstance();
        SystemSpeaker speaker2 = SystemSpeaker.getInstance();

        System.out.println(speaker1.equals(speaker2));
    }
}

이와같이 프로그램의 클래스가 모든 클라이언트에서 단일 인스턴스만 있어야 하는 경우 Singleton Pattern을 사용합니다.

  • Singleton Pattern은 생성자의 접근 지정자를 private으로 설정하여 getInstance()함수 이외에 클래스의 개체를 만드는 모든 생성방법을
    비활성화 합니다.

But race condition can occur

하지만 다중 스레드 환경에서 객체의 생성 여부를 확인하고 생성자를 호출하는 과정은 원자성을 보장하지 못하기 때문에 경합 상태(Race Condition)을 발생시킬 수 있습니다.

  1. Thread 1 : Speaker 인스턴스가 생성되지 않음을 확인함
  2. Thread 1 -> Thread 2 : Context Switch
  3. Thread 2 : Speaker 인스턴스가 생성되지 않음을 확인함
  4. Thread 2: Speaker Instance생성
  5. Thread 2 -> Thread1 : Context Switch
  6. Thread 1: Speaker Instance생성(정적 speaker 객체에 대한 경합 상태 발생 -> 2개의 인스턴스 생성)

Solution

thread-safe한 Singleton Pattern을 위해 다음과 같은 방법들을 고려할 수 있습니다.

1. 정적변수 초기화

class SystemSpeaker {
    // 정적 변수는 클래스 로딩시 한번만 수행되기 때문에 인스턴스 초기화는 한번만 수행됩니다.
    static private SystemSpeaker speaker = new SystemSpeaker();
    private int volume;

    private SystemSpeaker() {
        this.volume = 5;
    }

    public static SystemSpeaker getInstance() {
        return speaker;
    }
}

이경우 객체 생성을 원치 않더라도 클래스가 로딩되는 시점에 인스턴스가 초기화 되어 메모리를 점유하게 되어 비효율적입니다.

2. Synchronized 를 통한 동기화 제어

class SystemSpeaker {
    static private SystemSpeaker speaker;
    private int volume;

    private SystemSpeaker() {
        this.volume = 5;
    }

    // 인스턴스 생성 메서드를 임계구역으로 변경
    public synchronized static SystemSpeaker getInstance() {
        if(speaker == null) {
            speaker = new SystemSpeaker();
        }
        return speaker;
    }
}

Synchronize를 통한 객체 초기화는 lock, unlock 과정에서 성능을 크게 저하시킬 수 있습니다.

3. Enum

enum 클래스는 클래스처럼 보이게 하는 상수로 서로 관련 있는 상수들을 모아 심볼릭한 명칭의 집합으로 정의한 것입니다.(Jdk 1.5버전 이상에서만 가능한 방법입니다.)

enum SystemSpeaker {
    SPEAKER;

    public static SystemSpeaker getInstance() {
        return SPEAKER;
    }
}

Enum을 이용한 방식은 Thread-safe를 보장하지만 컴파일 타임에 enum의 초기화가 진행됩니다.
때문에 Android와 같이 Context에 의존성이 있는 환경에서는 초기화 과정에서 매번 Context정보를 전달해야 하는 문제가 발생합니다.

4. Lazy Holder

class SystemSpeaker{
    int volume;

    private SystemSpeaker() {
        this.volume = 5;
    }

    private static class LazyHolder {
        static final SystemSpeaker SPEAKER = new SystemSpeaker();
    }

    public static SystemSpeaker getInstance() {
        return LazyHolder.SPEAKER;
    }
}

객체가 필요할때로 초기화를 미룬다는 뜻에서 Lazy Initialization이라 부르기도 합니다.
JVM에 의해 SystemSpeaker클래스가 로드되면 클래스는 초기화를 거칩니다. 이때 LazyHolder클래스는 getInstance()메서드가 호출되기 전까지 로드되지 않습니다.

getInstance()메서드가 호출되면 LazyHolder()클래스를 로드하고 초기화를하는데 클래스의 초기화 단계는 JLS에 의해 순차적, 즉 비동시적임을 보장하므로 thread-safe합니다.

Singletone Pattern의 thread-safe를 보장하기 위한 가장 좋은 방법입니다.

Reference

'CS' 카테고리의 다른 글

[OS] 데드락의 탐지와 해결방법  (1) 2022.10.03
[Design Pattern] Facade 패턴을 통한 SRP원칙 준수  (0) 2022.08.22
[OS] Scheduler  (0) 2022.08.22
[OS] RAM disk vs Disk 성능비교  (0) 2022.08.22
[OS] Multiprocessor Scheduling  (0) 2022.08.22

CPU Scheduling

많은 프로세스가 동시에 자원을 요구할 때 어떤 프로세스가 CPU를 먼저 사용할지 결정하는 정책을 말합니다.

Workload (작업부하)

  • 프로세스가 얼마나 많은 양의 자원을 요구하느냐

Simple assumtion about porcess

  • 각 작업은 같은 시간동안 수행된다
  • 모든 작업은 같은 시간에 시작된다
  • 한번 시작되면 완료될때 까지 실행된다
  • 모든 작업은 CPU만을 사용한다.
  • 각 작업의 수행시간을 미리 알고 있다.

Job & Process

  • 동일한 의미의 단어지만 스케줄러를 연구하는 도메인 에서는 job이라는 단어를 선호합니다.

프로세스(Process)는 주소공간을 가지고 있는 실행중인 프로그램을 말합니다

작업(job)은 셸에서 사용하는 개념으로 실행중인 대화형 프로그램을 말합니다. 데몬과 반대되는 개념입니다. 대화형 프로그램을 실행 중인 경우 ctrl + z키를 눌러 프로그램을 일시 중단할 수 있습니다.

Deamon - 사용자가 직접적으로 제어하지 않고, 백그라운드에서 돌면서 여러 작업을 하는 프로그램입니다.

Metric

성능, 의존성 등과 같은 것을 측정하기위한 척도

Metric for Scheduling

  • Turnaround time(반환시간, Complete time - Arrived time)
    • 처음 실행시간부터 작업을 완료하는데 소요된 시간(CPU, waiting, I/O 등 모든 시간을 포함)
  • Response time(응답시간, First run time - Arrived time)
    • 처음 실행시간부터 작업이 응답하기까지 소요된 시간
  • Fairness(형평성)
    • 두개의 프로세스가 종료되는 시간이 얼마나 비슷한지
  • Throughput(처리율, jobs/sec)
    • 단위시간당 처리하는 작업의 수(처리량)
  • Deadline(마감시간)
    • Turnaround time < Deadline time

Response time(응답시간)

Turnaround time - Time required for a particular process to complete, from submission time to completion. It is equal to the sum total of Waiting time and Execution time.
-> batch 시스템에서 유리

Response time - The time taken in a program from the issuance of a command to the commence of a response to that command.(i.e., the time-interval between submission of a request, and the first response to that request, not the output .)
-> interactive 시스템에서 유리

FIFO(First In First Out)

처음으로 도착한 프로세스를 먼저 스케줄링하는 정책(a.k.a FCFS(First Come First Serve))

  • Pros : 1) 간단하다 2) 구현하기 쉽다
  • Cons : 1) 대기시간이 길어질 수 있다. (convoy effect)

Convoy Effect - The whole Operating System slows down due to few slow processes

SJF(Shortest Job First)

수행시간이 가장 짧은 작업에 높은 우선순위를 부여(a.k.a SPN(Shortest Process Next))

  • Pros : 최적의 알고리즘으로 증명됨
  • Cons : 짧은 수행시간의 작업이 늦게 도착했을때 어떻게 하나?

STCF(Shortest Time to Completion First)

SJF와 유사하나 선점형(Preemptive) 스케줄링 방식입니다

Non-preemptive Scheduling(비선점형 스케줄링)

  • 프로세스가 한번 스케줄 되면 그 프로세스가 끝날때까지 수행합니다.

Preemptive Scheduling(선점형 스케줄링)

  • 프로세스가 완료되지 않더라도 중간에 멈추고 다른 프로세스가 끼어들 수 있는 스케줄링 방식입니다.
  • context switch가 요구됩니다.
  • 최근 모든 스케줄러는 선점형 스케줄링 방식을 사용합니다

Response time

  • Turnaround time : batching 시스템에서 유리한 metric
  • Response time : interactive 시스템에서 중요한 metric(ex, 대화형 쉘에서 프로그램을 실행했는데 늦게 실행되면 답답)

RR(Round Robin)

Run queue의 작업이 완료될때 까지 Time quantum만큼 빠르게 돌아가며 수행
-> Response Time 단축, Turnaround time 증가

Tradeoff of time slice(time quantum)

  • Samll : 높은 응답 속도, Context switch로 인한 오버헤드 증가
  • Large : 낮은 응답 속도, Context switch로 인한 오버헤드 감소

-> time quantum의 크기가 커질수록 FCFS와 동일하게 동작하며 0에 가까울수록 switching overhead가 증가하여 비효율적입니다.

Tradeoff of between response time and turnaround time

  • Traditional isuue in CS : Interactivity(응답속도) vs Performance(성능)
  • 두마리 토끼를 동시에 잡을수는 없다

Incorporating I/O

프로세스로 부터 입출력 요청이 있을때 프로세스는 입출력이 완료될 때 까지 기다려야 합니다.

  • Busy waiting : 프로세스 A의 I/O가 수행되는동안 상태 확인, CPU자원 낭비
  • Blocked : 프로세스 A의 I/O가 수행되는동안 프로세스 B를 스케줄 하여 자원 낭비를 최소화

MLFO(Multilevel Feedback Queue)

  • 선점형 스케줄링 방식으로 여러개의 Ready Queue에 사용
  • 각 Queue마다 다른 Priority level 을 부여받음
  • 높은 우선순위의 Queue가 먼저 스케줄되며, 같은 queue내에서는 RR방식 적용합ㄴ디ㅏ
    • Rule 1 : Priority(A) > Priority(B) 일때 A가 먼저 수행합니다.
    • Rule 2 : Priority(A) = Priority(B) 일때 라운드 로빈 방식으로 수행합니다.
    • Rule 3 : Job이 들어오면 가장 상위 Ready Queue에 할당합니다.
    • Rule 4a : 할당된 Time slice(time quantum)가 끝나면 하위 Queue로 우선순위가 내려갑니다.
    • Rule 4b : Time slice가 끝나기전 스케줄링이 해제 동일한 우선순위 레벨에 남습니다.

'CS' 카테고리의 다른 글

[Design Pattern] Facade 패턴을 통한 SRP원칙 준수  (0) 2022.08.22
[Design Pattern] Singleton Pattern  (0) 2022.08.22
[OS] RAM disk vs Disk 성능비교  (0) 2022.08.22
[OS] Multiprocessor Scheduling  (0) 2022.08.22
[OS] Linux Kernel Structure  (0) 2022.08.22

+ Recent posts