반응형

https://chunghyup.tistory.com/50

 

[Pattern]Circuit Breaker Pattern - 회로 차단 패턴

MSA 방식의 아키텍쳐를 택한 서비스를 개발하는 중 아래와 같이 다른 MS(Micro Service)를 호출하는 경우는 상당히 많다. 1 2 const authService = new AuthService() authService.login(loginDto) cs 오류 처..

chunghyup.tistory.com

위 게시물과 연결되는 내용입니다.

 

JS를 이용하여 간다하게 Circuit Breaker를 구현해보았다.

 

몇번 실패를 하면 Open을 할 것인지, 몇 초 후 halfOpen으로 만들것인지 두가지 기능만 구현을 해 놓았다.

 

직접 사용하며 추가로 필요한 옵션이 필요하다면 기능을 추가 구현 하면 될 것 같다.

 

NPM에도 올려놓았는데, 사용 중 ISSUE가 발생해서 리포팅이 들어왔으면 좋겠다....

 

https://www.npmjs.com/package/super-simple-circuit-breaker

 

super-simple-circuit-breaker

Super Simple Circuit Breaker

www.npmjs.com

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
class SimpleCircuitBreaker {
 
  /**
   * 
   * @param {Object} options 
   * @param {number} options.retry number of retry count
   * @param {number} opeions.halfopenTime open time in millesec
   */
  constructor(options) {
    this[STATUS] = CIRCUIT_STATUS.CLOSED
    this[OPTIONS] = options
    this[FAILED_COUNT] = 0
  }
 
  getCurrentStatus() {
    return this[STATUS]
  }
 
  run = async (func) => {
    const isAsync = func.constructor.name === "AsyncFunction";
    if(!isAsync) {
      return Promise.reject(new Error('Function must be async function'));
    }
 
    if(this[STATUS] === CIRCUIT_STATUS.OPEN) {
      return Promise.reject(new Error('CIRCUIT OPEN'));
    }
 
    return new Promise(async (resolve, reject) => {
      try {
        const result = await func();
        this.handleSuccess();
        resolve(result);
      } catch (err) {
        this.handleFail();
        reject(err);
      }
    })
  }
 
  handleFail = () => {
    this[FAILED_COUNT]++;
    if (this[FAILED_COUNT] >= 3) {
      this[STATUS] = CIRCUIT_STATUS.OPEN;
      this.startTimer();
    }
  }
 
  handleSuccess = () => {
    if (this[STATUS] === CIRCUIT_STATUS.CLOSED) return
    
    this.reset()
  }
 
  startTimer () {
    this[HALFOPEN_TIMER] = setTimeout(() => {
      this[STATUS] = CIRCUIT_STATUS.HALFOPEN
    }, this[OPTIONS].halfopenTime);
  }
 
  reset() {
    clearTimeout(this[HALFOPEN_TIMER]);
    this[STATUS] = CIRCUIT_STATUS.CLOSED;
    this[FAILED_COUNT] = 0;
  }
 
}
cs
반응형
반응형

이 글은 https://microservices.io/patterns/monolithic.html 를 참고하여 작성되었습니다.

 

1. 어플리케이션을 위한 개발 팀이 있다.

2. 새로운 팀 멤버는 빠르게 적응 해야한다.

3. 어플리케이션은 이해하기 쉽고, 수정하기 쉬워야 한다.

4. 당신은 지속적 배포(continuous deployment)를 행하고 싶다.

5. 확장성(scalability)과 가용성(availability)을 위하여 여러개의 어플리케이션 인스턴스를 여러대의 머신에 띄우고 싶다.

6. 신기술들(framework, programming language)을 사용하여 그 장점을 취하고 싶다.

 

Solution

위와 같은 사항들이 강제 될 때 모놀리식 아키텍처를 택 할 수 있고, 아래와 같은 솔루션이 하나의 방법이 될 수 있다.

- 단일 자바 WAR 파일(a single Java WAR file)

- 단일 디렉토리 계층 구조를 가지는 Rails 또는  NodeJS 코드(a single ditectory hierarchy of Rails or NodeJS code)

 

Example

이커머스 어플리케이션을 개발한다고 가정을 해보자. 고객의 주문을 받고, 재고를 확인하고, 결제와 배송도 가능해야한다.

어플리케이션은 StoreFront UI(Frontend UI)와 재고, 주문, 결제, 배송을 처리하기 위한 서버(Backend service)로 이루어질 것이다.

그리고 어플리케이션은 단일 모놀리식 아키텍처로 배포된다. 예를들면 Java Web application을 WAR형태로 Tomcat 과 같은 컨테이너에서 실행된다. Rails나 Node 의 경우에는 단일 디렉토리 구조로 배포가 되고, Nginx와 함께 실행 될 수도 있다.

가용성을 위하여 여러개의 서버 인스턴스가 실행되고, Load Balancer를 이용하여 서비스가 될 수 있다.

 

Resulting context

모놀리식 아키텍처가 가지는 아래와 같은 장점을 가진다.

  • 개발이 쉽다. easy to develop
  • 배포가 쉽다. easy to deploy
  • 스캐일링이 쉽다. easy to scale

하지만 어플리케이션과 팀의 규모가 커질수록 점점 더 중요해지는 단점이 존재한다.

 

  • 유지보수가 어려워진다.
  • IDE에 부하가 심해진다.
  • 웹 컨테이너(ex. Tomcat)에 부하가 심해진다.
  • 지속적인 개발이 어려워진다.
  • 스케일링이 어려워 질 수 있다.(장점에서 말하는 스케일링과는 범위가 다르다.)
  • 스케일링 개발의 걸림돌이 된다.
  • 기술스택을 이해하기 위해서는 장기적인 노력이 필요하다.
반응형
반응형

MSA 방식의 아키텍쳐를 택한 서비스를 개발하는 중 아래와 같이 다른 MS(Micro Service)를 호출하는 경우는 상당히 많다.

1
2
const authService = new AuthService()
authService.login(loginDto)
cs

오류 처리까지 포함한다면 아래와 같은 코드가 작성 될 수 있다.

1
2
3
4
5
6
try {
    const authService = new AuthService()
    authService.login(loginDto)
catch (err) {
    throw new Error("Cannot call rpc");
}
cs

트래픽이 얼마 없는 환경에서는 위 코드는 문제 없이 동작하는 것 처럼 보인다.

하지만, 트래픽이 꽤(추상적이지만..) 발생하고 있는 운영중인 서비스라고 가정을 한다면

 

로그인 API 호출이 발생할때마다 위 코드는 실행이 될 것이고

Auth서버가 어떠한 이유로(서버 자체의 문제, DB 문제, 또 다른 MS와의 통신 문제 등) 동작을 하지 않아 timeout 이 난다면?

 

timeout 에러가 발생하기 전까지 우리의 코드는 계속해서 어딘가에 해당 정보들을 저장하고 있을것이다.

메모리, 스레드, DB connection과 같은 자원들을 계속해서 잡아먹고 있을 것이고 결국 Memory가 모자라 이 서버까지 문제가 생길 수 있다.

 

예외처리를 했기 때문에 문제가 없다고 생각했겠지만, 이러한 코드가 반복되었다면 하나의 MS의 문제가 전체 MSA의 영향을 끼치는 큰 문제가 발생 할 수 있다.(MSA를 사용하는 목적과 크게 부합된다.... 하나의 MS가 죽어도 다른 MS들은 정상적으로 동작하여야 한다!)

 

회로차단 패턴(Circuit Break Pattern)은 이러한 문제를 방지하기 위한 패턴이다.

실패 할 수 있는(실패의 가능성이 있는) 작업을 반복적으로 실행하지 않도록 하기 위한 패턴이다.

 

Circuit Break Pattern 이라는 이름이 붙은 이유는 회로도에서 스위치를 이용하여 회로를 차단하는방법과 유사하기 때문이다.

 

 

코드로 위와 같은 기능을 구현한다면, 서버가 호출이 가능할 때에는 로직이 실행이 되고 호출이 불가능 할때에는 로직이 실행이 되지 않도록 작성을 하면 된다.

 

 

위 이미지에서는 3개의 상태를 이용하여 Circuit Breaker Pattern을 구현하기 위한 다이어그램이다.

 

Open : API 호출이 불가능한 상태(회로 스위치가 열려 회로가 끊어진 상태)

Closed : API 호출이 가능한 상태(회로 스위치가 닫혀 회로가 연결 된 상태)

Half Open : API 호출이 가능할수도 불가능할수도 있는 상태

 

세가지 상태로 구분이 된다.

 

간단한 의사코드를 작성하기에 앞서, 구현을 위한 몇가지 옵션을 정해보자.

 

1. 3번의 실패가 발생 할 시 회로를 차단하여 API 호출을 하지 않도록 한다.

2. 회로가 Open 상태가 된다면 3분의 시간 후 Half Open상태로 변경한다.

3. Half Open 상태에서 API 호출이 성공한다면 Closed 상태로 변경하고, 실패한다면 다시 3분 동안 Open 상태로 전환한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
const status = {
    OPEN: 0,
    CLOSED: 1,
    HALF_OPEN: 2
}
 
const currentState = status.CLOSED
const halfOpenTime = 0;
const failedCount = 0
 
function login() {
    try {
        const authService = new AuthService()
        authService.login(loginDto)
    } catch (err) {
        throw new Error("Cannot call rpc");
    }
}
 
function callCircuitBreakerFn(cb) {
    if (currentState === status.CLOSED) {
        try {
            cb()
        } catch (err) {
            failedCount++
            if(failedCount >= 3) {
                currentState = status.OPEN
                halfOpenTime = Date.now() + (60 * 3)
            }
        }
    } else {
        if (Date.now() >= halfOpenTime) {
            currentState = status.HALF_OPEN
        }
 
        if (currentState === status.HALF_OPEN) {
            try {
                cb()
            } catch (err) {
                currentState = status.OPEN
                halfOpenTime = Date.now() + (60 * 3)
            }
        }
 
    }
    
}
 
cs

깔끔하지 않은 의사코드이지만, 대충 이런 형태의 코드가 작성 될 수 있을 것 같다.

실제 서비스에 적용을 한다면 여러개의 MS를 관리하기 위한 Class를 생성해야 할 것이고, 고려해야 할 사항들이 조금 더 생길 것 같다.

 

실제 서비스에 적용을 해보고 시행착오와 최종 코드등은 이후에 공유해봐야겠다.

 

참고

https://topswagcode.com/2016/02/07/Circuit-Breaker-Pattern/

 

Circuit Breaker Pattern

What is a circuit breaker? A circuit breaker is an automatically operated electrical switch designed to protect an electrical circuit from damage caused by Overcurrent/overload or short circuit. Its basic function is to interrupt current flow after Protect

topswagcode.com

 

반응형

+ Recent posts