본문 바로가기

개발공부/원티드 챌린지 정리

7월 백엔드 챌린지 3. 사용자 수에 따른 규모를 확장하는 방법 2

728x90

1) 메세지 큐

메세지 큐에 일단 보관한 메시지는 소비자가 꺼낼 때까지 안전하게 보관할 수 있다

장점 : 서비스 또는 서버 간 결합이 느슨해져, 규모 확장성이 보장되어 안정적인 애플리케이션을 구성할 수 있다. 생산자는 소비자의 프로세스가 다운되어도 메시지를 발행할 수 있고, 그 반대도 가능하다

 

사용 사례

알림 푸시 발송, 이메일 발송, 이미지 보정 등 시간이 오래 걸릴 수 있는 프로세스를 비동기로 처리한다

 

이벤트 큐 : 메시지 큐와 다르게 한 번 읽은 데이터는 즉시 삭제되지 않는다

오늘날의 애플리케이션은 이벤트 기반 MSA를 구현할 뗴 이벤트 큐를 많이 활용한다

특징 : 단일 진실 공급원, 장애가 일어난 시점부터 재처리 가능, 많은 양의 실시간 데이터를 효율적으로 처리 가능

https://www.youtube.com/watch?v=H_DaPyUOeTo 

https://tv.kakao.com/channel/3150758/cliplink/391419257

 

2) 이벤트 기반 MSA

현대 EDMSA(Event Driven MSA)에서는 시스템 이벤트를 생성/소비하는 방식으로 서로 통신한다

이벤트를 소비해도 전달한 시스템에서 바로 사라지지 않고 다른 컨슈머도 가져갈 수 있게 보존된다

 

DDD와 Bounded context

- Bounded context를 비즈니스 중심으로 설계하면 팀 별로 느슨하게 결합하며 고도로 응집된 MSA를 할 수 있어 비즈니스에서 필요한 솔루션을 자율적으로 설계 및 구현 ~ POlYGLOT 가능

- 팀 간 복잡한 의존 관계가 줄어든다

- 기술 중심으로 설계할 경우 모놀리식 시스템과 같은 문제를 겪는다

 

이벤트 기반 통신 구조

- 요청과 응답 API에 얽매이는 일 없이 이벤트 스트림 내부에 정의된 이벤트 데이터를 매개로 소통한다. 생산자는 자신이 맡은 이벤트 스트림에 잘 정의된 데이터를 생성하는 일만 책임지면 된다. 소비자는 이벤트 스트림에서 들어온 데이터를 처리하는 일을 담당한다. 느슨한 결합과 높은 응집도를 추구하는 Bounded context 원칙을 더 확실히 준수할 수 있다

 

동기식 마이크로서비스의 문제

https://www.youtube.com/watch?v=J-VP0WFEQsY 

1. 점대점 결합 : 다른 서비스에 의존한다

2. 의존적 확장 : 의존하는 다른 모든 서비스가 확장이 가능한지 고려해야 한다

3. API 버저닝 : API 스펙을 변경하는 건 매우 어렵다

4. 테스트 : 의존하는 서비스까지 통합해서 테스트하기 어렵다

 

비즈니스 토플로지

마이크로서비스 + 이벤트 스트림 + API 집합이다

마이크로서비스는 비즈니스 Bounded context를 구현한다

이벤트 스트림은 전체 Context의 도메인 데이터를 공유하기 위해 필요한 데이터 통신 수단이다

3) Microservice Communication Patterns

1. Synchronous Calls

- 구현하기 쉬운 Communication Pattern이다

- 서비스1은 서비스2가 요청 처리를 완료하고 응답을 반환할 때까지 대기한다

- 동기식이기 때문에 구조가 간단하다

- Netflix의 Feign 그리고 Hystrix(Circuit Breaker)를 사용할 수 있다

 

Timeouts

- 서비스1이 서비스2로 요청을 보내고 서비스2가 요청을 처리 도중에 서비스1에서 timeout 예외가 발생한다면

두 서비스 간 데이터가 불일치할 수 있다

 

Strong Coupling

동기 서비스 간 강한 결합 생성

 

Easy to Implement

동기 서비스는 구현이 쉽다

 

2. Simple Messaging

- 서비스1은 메시지 브로커에게 메시지만 보내고 잊으면 된다

- 서비스2는 메시지 브로커에게 메시지를 구독하기만 하면 된다

- 서비스1과 2는 서로의 존재를 알 필요가 없다

- 메시지 브로커를 기준으로 페이로드가 포함된 메시지를 서로 주고 받기만 하면 된다

 

Automatic Retry

메시지 브로커 라이브러리에 따라 Retry 기능 제공

서비스2가 사용이 불가능한 상태일 때, 서비스2가 정상화 될 때 까지 메시지 전달을 시도

물론 서비스2가 polling 하는 구조라면 고려할 필요는 없다

 

Loose Coupling

서비스2는 서비스1 을 호출하지 않기 때문에 느슨한 결합을 유지할 수 있다

 

Message Broker must not fail

메시지 브로커에 문제가 발생하면 안된다

메시지 브로커에 데이터가 중앙 집권화되기 때문에 hell will break loose 할 것이다

 

Pipeline contains Schema

메시지 구조가 변경된 경우 모든 클라이언트가 변경된 메시지 구조를 처리할 수 있어야 한다. 이는 마이크로서비스의 주요 목표 중 하나인 independent deployments (독립 배포)와는 모순된다. 이러한 모순은 하위 호환성이 보장되게 변경하는 것으로 완화할 수 있다

 

Two-Phase Commit

서비스1, 서비스2 를 서로 같은 트랜잭션으로 묶고 싶다면 Two-Phase commit 을 사용할 수 있다. 단, 데이터베이스나 메시지 브로커가 지원을 안할 수도 있으며 좋은 성능을 기대하기 힘들다 (거의 사용하지 않음)

 

+ https://youtu.be/urpF7jwVNWs?t=420 

만약에

→ A 라는 서비스가 있다.

→ 이 서비스의 특정 API 는 Product 테이블과 Review 테이블을 Join 해서 보여주고 있다.

→ 근데 갑자기 MSA 하면서 Product 테이블을 직접 볼 수 없게 되고 Review 볼 수 없게 되었다.

→ Product API, Review API 각각 찔러서 애플리케이션에서 데이터를 조합해서 화면에 보여준다.

→ 단점: 일단 API 의존도가 생기고, 페이징처리 어렵다.그룹바이 쿼리 이런거 힘듬 할 수 있는게 많지 않음

→ 계속 API 찌리니까 트래픽도 많이 발생시킴

→ 그럴바엔 Product, Review 쪽에서 이벤트 발행하고 A 라는 서비스는 내부DB 에 이 데이터들을 저장한다.

→ 그리고 API 호출이 들어오면 내부 DB 에서 조인해서 보여준다.

 

3. Transactional Messaging

특징

- 메세지를 브로커에 전달하기 전에 데이터베이스에 저장하는 방식

- 수신자는 메시지를 송신하고 처리하기 전에 데이터베이스에 저장한다

 

No Need for Two-Phase Commit

- 메시지를 보내는 쪽과 받는 쪽의 로컬 데이터베이스에 저장하기 때문에 언제든 롤백, 복구가 가능하다

 

Message Broker may Fail

- 데이터베이스에 메시지가 저장이 되기 때문에, 브로커에 장애가 나더라도 데이터베이스에서 메세지를 조회하면 된다

 

Complex Setup

- 아키텍처를 구성하기엔 다소 복잡하다. 발행해야 하는 메시지를 데이터베이스에 저장해야 되기 때문이다. 또한 데이터베이스로부터 데이터를 pulling하고 처리하지 않은 메시지에 대한 처리 로직을 작성해야한다

-> 송신측 : 메세지 브로커로 전송/ 수신측 : 메시지를 처리하는 비즈니스 로직 호출

 

4. Zero-Payload Events

특징

페이로드에 대한 포인터만 메시지로 전달한다. 예를 들어 Order Id = 4711 주문이 배송되었다는 메시지를 발행할 경우, 제로페이로드 방식을 사용한다면 서비스1은 메시지에 Event Type = orderShipped 그리고 Order Id = 4711만 포함해서 브로커에 전달한다. 그리고 수신자는 송신자를 호출해 주문 데이터를 요청한다

 

Dump pipe

- 메시지 구조가 정말 심플하기 때문에 하위 호환성에 대해 고려할 필요가 없다

 

Combinable with Transactional Messaging

- 메시지 브로커에 장애가 발생해도 재시도를 할 수 있다.(페이로드는 서비스1을 호출해서 얻기 때문)

- 이벤트 페이로드를 얻기 위해 서비스 간 동기적 호출이 필요하고, 서비스의 복잡도가 올라간다

4) 샤딩

- 데이터베이스를 수평적 확장하는 것을 의미한다(Scale-out). 더 많은 서버를 추가함으로 성능을 향상시킬 수 있다

- 대규모 데이터베이스를 샤드라고 부르는 작은 단위로 분할하는 것을 의미한다. 모든 샤드는 같은 스키마를 쓰지만 샤드에 보관되는 데이터 사이에는 중복이 없다

단점

- 여러 샤드에 걸친 데이터를 조인하기 매우 어렵다

- 핫스팟 키 문제 : 특정 샤드에 쿼리가 집중이 될 수 있다

- 데이터 재 샤딩의 어려움

5) 처리율 제한

1. 처리율 제한

- 클라이언트 또는 서비스가 보내는 트래픽의 처리율을 제어하기 위한 장치. 특정 임계치를 넘으면 그 이후 요청은 중단시킨다

2. 사용 사례

- 사용자는 초당 2회 이상 새 글을 올릴 수 없다. 같은 IP 주소로는 하루에 10개 이상 계정을 생성할 수 없다. 같은 디바이스로는 주당 5회 이상 리워드을 요청할 수 없다

3. 설계

(1) 클라이언트 측

- 위변조가 가능하며 권장하지 않는다

(2) 서버 측

(3) 미들웨어

- MSA인 경우, 처리율 제한 장치는 보통 API Gateway에 구현한다

- API Gateway : 처리율 제한, SSL 종단, 사용자 인증, IP 허용 목록, 관리 등

(4) 정리

- 현재 기술 스택이 서버 측에 기능 구현이 가능한지 점검

- 상황에 맞는 알고리즘 사용, 만약 제 3 사업자가 제공하는 API Gateway를 사용한다면 선택지는 제한이 될 수 있다

- MSA에 기반하고 있다면 인증, IP 허용 같은 기능을 이미 API Gateway에 적용했을 수 있다. 그러면 처리율 제한도 API Gateway에 포함하는 것이 좋다

- 충분한 인력이 없다면 상용 솔루션도 고려해보는 것이 좋다

4. 알고리즘

(1) 토큰 버킷 알고리즘

- 토큰이 주기적으로 채워진다

- 각 요청이 처리될 때마다 하나의 토큰을 사용한다

- 토큰이 없다면 해당 요청은 버려진다

(2) 특징

- 많은 기업이 보편적으로 사용하는 알고리즘

- 통상적으로 API 엔드포인트마다 별도의 버킷을 둔다

- IP 주소별로 처리율 제한을 적용해야 한다면 IP 주소마다 버킷을 하나씩 할당해야 한다

- 시스템의 처리율을 초당 10,000개 요청으로 제한한다면, 모든 요청이 하나의 버킷을 공유하도록 해야 한다

(3) 장단점

장점

- 구현이 쉽다

- 메모리 효율적

- 짧은 시간에 집중되는 트래픽도 잘 처리

단점

- 버킷 크기 & 토큰 공급률 두 개의 인자를 필요로 하는 알고리즘이기 때문에 적절하게 튜닝하는 것이 어렵다

 

케이스 : 500 에러가 1분에 10건이 발생하면 슬랙으로 알림을 쏜다

- 500 에러 발생 시, 레디스에 카운트 한다

- 1분마다 카운트 리셋

- 스케줄러로 30초마다 체크하고 500 에러가 10건이 넘어가면 슬랙 알림을 쏜다

 

 

 

728x90