제가 요즘 회사에서 작업하는 코드가 있는데, 이게 현재 redis 기반의 pub/sub 패턴을 사용하고 있습니다. 애초에 이미 production에서 잘 동작하는 코드니까(양심 😂) '아 뭐 이런거구나' 하고 있었는데... 최근
이걸 cloud run으로 옮기네,
queue에 들어가기 전에 인터셉트해서 처리하네,
서버리스로 하면 대기시간이 짧아지네,
이런 논의들이 오고가게 되는데...
아 이거 대충 넘겼다간 뭔소린지 하나도 모르겠구나 싶어서 공부하고 정리하려 합니다.
publish/subscribe의 줄임말이며, 비동기식 메세징 패턴입니다. publisher가 연산해야 할 일(task)를 발행(publish)하면 그 task의 범주를 구독(subscribe)한 수신자가 받아 대신 일을 처리합니다. 그 시간동안 publisher는 다른 작업을 수행할 수 있죠.
구글 클라우드 페이지가 설명을 잘 해놓았길래, 구글에서 제공하는 그림을 보고 이해해봅시다.
그림의 맨 위에서부터 설명해보자면 아래와 같습니다.
publisher(게시자)
message를 생성해 topic에 전달하는 서버
message(메세지)
publisher로부터 subscriber에게 최종적으로 전달되는 데이터와 속성(property)의 조합
topic(토픽)
publisher가 메세지를 전달하는 리소스
subscription(구독)
특정 단일 주제의 메시지 스트림이 구독 애플리케이션으로 전달되는 과정을 나타내는, 이름이 지정된 리소스
subscriber(구독자)
메세지를 수신하는 서버
publisher와 subscriber는 서로에 대해 알 필요가 없습니다. 대신 중간다리 역할을 하는 브로커, 혹은 버스라고 불리는 존재가 추가적으로 존재합니다. 브로커 or 버스는 publisher와 subscriber 모두에게 알려진 존재로, 저희같은 경우에는 redis 서버가 bus 역할을 담당하고 있습니다.
아까 publisher, subscriber는 서로 몰라도 되는 대신, 이 중간자를 통해야 한다 했죠, 그래서 이 bus 객체를 생성할 때 pubClient, subClient의 정보를 전달해줘야 합니다.
// 버스 정의
import { EventEmitter } from 'events';
import { RedisClient } from 'redis';
class Bus {
subscriptions: EventEmitter;
pubClient: RedisClient;
subClient: RedisClient;
constructor() {
// ... create redis client and create bus ...
this.prefix = 'bus:0'; // 0은 redis 연결옵션에 있는 db key의 value입니다.
}
private getChannelName(topic) {
return this.prefix + `:${topic}`;
// ex: 'bus:0:greeting'
}
publish(topic: string, message: string) {
const channel = this.getChannelName(topic);
this.pubClient.publish(channel, message);
return;
}
subscribe(topic: string, listener: Subscriber) {
const channel = this.getChannelName(topic);
this.subscriptions.on(channel, listener);
return;
}
}
//사용처 예시
const callGreetingWorker = (parameter: YourParameterType, language: string) => {
return bus.publish(`speakIn:${language}`, JSON.stringify(parameter));
}
callGreetingWorker({ when: 'night'}, 'en');
이제 subscriber가 해당 정보(메세지)를 받으면, 이에 관련 작업을 시작합니다. (ex: 각 채널에 대한 트리거를 설정해놓는 식으로 작업 발동)
// speakIn:en 채널에 대한 작업
const triggeredFunc = (message: string) => {
const query = JSON.parse(message);
const { when } = query;
switch (when) {
case 'morning':
return 'good morning';
case 'noon':
return 'good afternoon';
case 'evening':
return 'good evening';
case 'night':
return 'good night';
}
}
실제로는 단순히 인사말 찍는 용도가 아닌, 대용량 데이터 처리나, 여러 서버가 공용으로 사용해야 할 데이터를 주고받을 때 사용됩니다.
지금까지 보면 언뜻 옵저버 패턴이랑 유사한 것 같은 느낌이 듭니다. 그래서 누군가는 pub/sub을 옵저버의 다른 이름이라고 말하기도 합니다.
하지만 브로커의 존재 여부 등 성격을 생각해보면 옵저버 패턴과 pub/sub은 엄연히 다른 구조인 것 같습니다.
이 부분에 대해서는 다른 분의 게시물을 참고해보면 좋을 것 같습니다.
https://jistol.github.io/software%20engineering/2018/04/11/observer-pubsub-pattern/
쿠버네티스 기초실습 (GCP 기반) - 2 - Pod (0) | 2022.10.01 |
---|---|
쿠버네티스 기초실습 (GCP 기반) - 1 (1) | 2022.08.24 |
경력 0개월차 개발자의 고래 해체 작업 썰 - 3 [ECS task-definitions] (0) | 2021.10.04 |
경력 0개월차 개발자의 고래 해체 작업 썰 - 2 [Postgre 데이터 Dump] (0) | 2021.09.08 |
경력 0개월차 개발자의 고래 해체 작업 썰 - 1 [Docker, ECS] (1) | 2021.08.24 |
댓글 영역