Skip to content

๐Ÿ”Œ websocket์ด ๋Šฆ๊ฒŒ ํ• ๋‹น๋˜์–ด ๋ฐœ์ƒ๋˜๋Š” ๋ฌธ์ œ

sunghwki edited this page Dec 4, 2024 · 4 revisions
๋ถ„์•ผ ์ž‘์„ฑ์ž ์ž‘์„ฑ์ผ
BE ๊น€์„ฑํ™˜ 24๋…„ 12์›” 04์ผ

๋“ค์–ด๊ฐ€๊ธฐ ์ „

  • ์ฃผ์ถค์ฃผ์ถค ์„œ๋น„์Šค๋Š” ํ•œ๊ตญํˆฌ์ž์ฆ๊ถŒ์˜ ์›น์†Œ์ผ“์„ ์‚ฌ์šฉํ•ด ์ฃผ์‹์˜ ํ˜„์žฌ ์ฒด๊ฒฐ๋Ÿ‰๊ณผ ๊ฐ™์€ ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค.

  • ์œ ์ €์—๊ฒŒ ํ˜„์žฌ๊ฐ€๋ฅผ ๋ณด์—ฌ์ฃผ๊ธฐ ์œ„ํ•ด์„œ๋Š” ์ฃผ์‹๋งˆ๋‹ค ๋ผ์ด๋ธŒ๋ฐ์ดํ„ฐ๋ฅผ ๊ตฌ๋…ํ•˜๋Š” ๊ฒƒ์ด ์ตœ๊ณ ์ง€๋งŒ, ์›น์†Œ์ผ“์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋Š” ๊ฐœ์ˆ˜์™€ ์ฃผ์‹์„ ํ• ๋‹นํ•˜๋Š” ๊ฐœ์ˆ˜๋„ ํ•œ์ •๋˜์–ด ์žˆ๋‹ค

    • ํ˜„์žฌ ์›น์†Œ์ผ“์„ ํ•œ๊ตญํˆฌ์ž์ฆ๊ถŒ๊ณผ ์—ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐœ์ˆ˜๋Š” 4๊ฐœ, ์ฃผ์‹์„ ํ• ๋‹นํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐœ์ˆ˜๋Š” ์›น์†Œ์ผ“๋‹น 41๊ฐœ์ด๋‹ค.
    • ๋”ฐ๋ผ์„œ 164๊ฐœ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ผ์ด๋ธŒ๋กœ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์œผ๋‚˜, ์šฐ๋ฆฌ๋‚˜๋ผ์˜ ์ฃผ์‹์€ ๋Œ€๋žต 4200๊ฐœ์ •๋„๋กœ ํ„ฑ์—†์ด ๋ถ€์กฑํ•˜๋‹ค.
  • ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์œ ์ €์™€ ์—ฐ๊ฒฐ์€ socket.IO์˜ room๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•ด ํ˜„์žฌ ์ฃผ์‹์ฐฝ์„ ๋ณด๊ณ  ์žˆ๋Š” ์ง€ ์ฒดํฌํ•˜๊ณ , ํ•œ๊ตญํˆฌ์ž์ฆ๊ถŒ๊ณผ์˜ ์—ฐ๊ฒฐ์€ Websocket์„ ์ด์šฉํ•ด ์—ฐ๊ฒฐํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๊ตฌํ˜„ํ–ˆ๋‹ค.

    • ์œ ์ €๊ฐ€ ์ฃผ์‹์ฐฝ์„ ๋ณด๊ณ  ์žˆ์œผ๋ฉด ํ•œ๊ตญํˆฌ์ž์ฆ๊ถŒ์—์„œ ์ฃผ์‹ ์ •๋ณด๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ๊ฒŒ ๊ตฌ๋…ํ•˜๊ณ , ํ•œ ๋ช…์˜ ์œ ์ €๋„ ๋ณด์ง€ ์•Š๋Š” ๋‹ค๋ฉด ์ฃผ์‹ ์ •๋ณด ๊ตฌ๋…์„ ์ทจ์†Œํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์ œํ•œ๋˜์–ด ์žˆ๋Š” ๊ตฌ๋… ์ •๋ณด๋ฅผ ํšจ์œจ์ ์œผ๋กœ ๊ด€๋ฆฌํ–ˆ๋‹ค.

๋ฌธ์ œ ์ƒํ™ฉ

  • ์œ ์ €๊ฐ€ ์ ‘์†ํ–ˆ์„ ๋•Œ, ์„œ๋ฒ„๊ฐ€ ๋ง‰ ์ผœ์กŒ์„ ์ƒํ™ฉ์ผ ๊ฒฝ์šฐ ๋ผ์ด๋ธŒ๋ฐ์ดํ„ฐ๊ฐ€ ์ œ๋Œ€๋กœ ๋ฐ›์•„์ง€์ง€ ๋ชปํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค.
    • ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ฐ›์•„์ง€์ง€ ์•Š๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค.

๊ฐ ํด๋ž˜์Šค ์„ค๋ช…

  • LiveData

    • ํ•œ๊ตญํˆฌ์ž์ฆ๊ถŒ ์›น์†Œ์ผ“์˜ ๋ฉ”์‹œ์ง€, ๋ฆฌํ„ด ๋ฉ”์‹œ์ง€ ๋“ฑ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ์ฒ˜๋ฆฌํ•˜๋Š” ํด๋ž˜์Šค
    • 4๊ฐœ์˜ ์ฝœ๋ฐฑํ•จ์ˆ˜๋ฅผ WebsocketClient ํด๋ž˜์Šค์— ํ• ๋‹นํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์ž‘๋™ํ•œ๋‹ค.
    • WebsocketClient๋ฅผ ํ• ๋‹นํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐœ์ˆ˜๊ฐ€ ๋Ÿฐํƒ€์ž„ ๋•Œ ๊ฒฐ์ •๋˜์–ด ์ด๋ฅผ WebsocketClient ๋ฅผ ๋™์ ์œผ๋กœ ํ• ๋‹นํ•œ๋‹ค.
  • WebsocketClient

    • ์›น์†Œ์ผ“ ๋กœ์ง๋งŒ ์ฒ˜๋ฆฌํ•˜๋Š” ํด๋ž˜์Šค
    • ๋กœ๊น…, ์—ฐ๊ฒฐ์„ ํ•˜๊ณ , ์ฝœ๋ฐฑ์„ ์™ธ๋ถ€์—์„œ ์ฃผ์ž…๋ฐ›๋Š” ํด๋ž˜์Šค

์ด์ „ ์ฝ”๋“œ

export class WebsocketClient {
  static url = process.env.WS_URL ?? 'ws://ops.koreainvestment.com:21000';
  private client: WebSocket;

  constructor(@Inject('winston') private readonly logger: Logger) {
    this.client = new WebSocket(WebsocketClient.url);
  }
  
  // ์ค‘๊ฐ„ ์ƒ๋žต
  private sendMessage(message: string) {
    if (this.client.readyState === WebSocket.OPEN) {
      this.client.send(message);
      this.logger.info(`Sent message: ${message}`);
    } else {
      this.logger.warn('WebSocket not open. Queueing message.');
    }
  }
  
  static websocketFactory(logger: Logger) {
    const websocket = new WebsocketClient(logger);
    return websocket;
  }
  //์ดํ•˜ ์ƒ๋žต
}
  • ์ด๋Ÿฐ ํ™˜๊ฒฝ์—์„œ ์œ ์ €๊ฐ€ subscribe๋ฅผ ํ•ด์„œ ์ฃผ์‹ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›๊ณ ์ž ํ•˜๋ฉด ๋‹ค์Œ sequence diagram ์ฒ˜๋Ÿผ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.
sequenceDiagram
    participant User
    participant LiveData
    participant WebsocketClient
    participant WebsocketToKoreaInvestment

		LiveData->>WebsocketClient: ๋™์ ์œผ๋กœ ํ• ๋‹นํ•˜๋Š” ์ค‘
    User->>LiveData: subscribe()
    LiveData->>WebsocketClient: subscribe()
    WebsocketClient->>WebsocketToKoreaInvestment: (์•„์ง ์†Œ์ผ“์ด ์—ด๋ฆฌ์ง€ ์•Š์€ ์ƒํƒœ)
    WebsocketClient->>User: Error: socket not assigned

Loading

์›์ธ

  • Websocket๋‚ด ์ฝ”๋“œ์—์„  ๋Œ€๋ถ€๋ถ„ ๋™๊ธฐ๋กœ ์ž‘๋™ํ•˜์ง€๋งŒ, upgrade์š”์ฒญ์— ๋Œ€ํ•ด์„œ event emitter๋ฅผ ํ†ตํ•ด ๋ฐ›์•„์„œ ์ฒ˜๋ฆฌํ•œ๋‹ค.
// Websocket lib/websocket.js
req.on('upgrade', (res, socket, head) => {
    websocket.emit('upgrade', res);
    //์ค‘๊ฐ„ ์ƒ๋žต
    
    // socket์„ ์‹ค์งˆ์ ์œผ๋กœ ํ• ๋‹นํ•˜๋Š” ํ•จ์ˆ˜
    // ํ• ๋‹น์ด ์™„๋ฃŒ๋˜๋ฉด _readyState๋ฅผ WebSocket.OPEN์œผ๋กœ ๋ณ€๊ฒฝ
    websocket.setSocket(socket, head, {
      allowSynchronousEvents: opts.allowSynchronousEvents,
      generateMask: opts.generateMask,
      maxPayload: opts.maxPayload,
      skipUTF8Validation: opts.skipUTF8Validation
    });
    //์ดํ•˜ ์ƒ๋žต
 })
  • ์›น์†Œ์ผ“์˜ ๊ฒฝ์šฐ ์ฒ˜์Œ์— http ๋˜๋Š” https ๋กœ ๋จผ์ € ์—ฐ๊ฒฐ์„ ์‹œ๋„ํ•˜๊ณ , 101 switching protocols์ด ๋“ค์–ด์˜ค๋ฉด http์—์„œ Websocket ์œผ๋กœ ์—…๊ทธ๋ ˆ์ด๋“œ๋ฅผ ํ•œ๋‹ค.

  • ์ด๋•Œ, event emitter๋ฅผ ์ด์šฉํ•ด upgrade ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๊ณ , ํ•œ๊ตญํˆฌ์ž์ฆ๊ถŒ์™€์˜ ์†Œ์ผ“์ด ์—ด๋ฆฐ ์ƒํ™ฉ์ด ์•„๋‹Œ ์ƒํƒœ์—์„œ ์œ ์ €์˜ ์š”์ฒญ์ด ๋“ค์–ด์˜ฌ ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์ด์—ˆ๋‹ค.

ํ•ด๊ฒฐ์ฑ… 1 - OnModuleInit

image

  • nestjs์˜ lifecycle์— ๋งž์ถฐ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ๋ฐฉ์‹์ด๋‹ค.
  • ๋‹ค๋งŒ, ์ด๋Ÿฐ ๊ตฌ์กฐ์—๋„ ์‹ค์งˆ์ ์œผ๋กœ ์†Œ์ผ“์ด ์–ธ์ œ ํ• ๋‹น๋˜๋Š” ์ง€๋Š” ์•Œ ์ˆ˜ ์—†์œผ๋ฏ€๋กœ ๋ฌธ์ œ๋ฅผ ์™„๋ฒฝํ•˜๊ฒŒ ํ•ด๊ฒฐํ•  ์ˆœ ์—†์—ˆ๋‹ค.

ํ•ด๊ฒฐ์ฑ… 2 - ํ๋ฅผ ์ด์šฉํ•ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐ

  • ๋ฌธ์ œ๋ฅผ ์‹ค์งˆ์ ์œผ๋กœ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด์„  ์œ ์ €์˜ ์š”์ฒญ์„ ์ž„์‹œ๋กœ ์ €์žฅํ•˜๊ณ , ๋งŒ์•ฝ ์†Œ์ผ“์ด ์—ด๋ฆฐ๋‹ค๋ฉด ๊ทธ ์ดํ›„ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋กœ์ง์ด ํ•„์š”ํ–ˆ๋‹ค.
  • ์ด๋•Œ ํ๋ฅผ ์–ด๋””์— ์ ์šฉํ•˜๋Š” ์ง€์— ๋Œ€ํ•œ ๊ณ ๋ฏผ์ด ์žˆ์—ˆ๋‹ค.
    • LiveData - ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ถ€๋ถ„์œผ๋กœ ์›น์†Œ์ผ“์˜ ์—ฐ๊ฒฐ๊ณผ๋Š” ์ƒ๊ด€ ์—†๋Š” ๋กœ์ง๋งŒ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์„ ์ฒ˜์Œ์— ์ƒ๊ฐํ–ˆ์—ˆ๋‹ค.
    • ๋”ฐ๋ผ์„œ ํ๋ฅผ ์ ์šฉํ•ด ์œ ์ € ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์—๋Š” WebsocketClient์— ์ ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋งž๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ๋‹ค.
export class WebsocketClient {
  static url = process.env.WS_URL ?? 'ws://ops.koreainvestment.com:21000';
  private client: WebSocket;
  private messageQueue: string[] = [];

  constructor(@Inject('winston') private readonly logger: Logger) {
    this.client = new WebSocket(WebsocketClient.url);
    this.initOpen(() => this.flushQueue());
    this.initError((error) => this.logger.error('WebSocket error', error));
  }

  static websocketFactory(logger: Logger) {
    return new WebsocketClient(logger);
  }
  
  //์ค‘๊ฐ„ ์ƒ๋žต
  
  private sendMessage(message: string) {
    if (this.client.readyState === WebSocket.OPEN) {
      this.client.send(message);
      this.logger.info(`Sent message: ${message}`);
    } else {
      this.logger.warn('WebSocket not open. Queueing message.');
      this.messageQueue.push(message); // ํ์— ๋ฉ”์‹œ์ง€๋ฅผ ์ถ”๊ฐ€
    }
  }

  private flushQueue() {
    while (this.messageQueue.length > 0) {
      const message = this.messageQueue.shift();
      if (message) {
        this.sendMessage(message);
      }
    }
  }
}
  • ์ด๋Ÿฐ ๋ฐฉ์‹์œผ๋กœ ์‹ค์ œ๋กœ ์†Œ์ผ“์ด ์—ด๋ฆฌ์ง€ ์•Š์€ ์ƒํ™ฉ์—์„œ๋„ ์•ˆ์ •์ ์œผ๋กœ ์œ ์ €์˜ ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ ์š”์ฒญ์— ์‘๋‹ตํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

์ฐธ๊ณ ์ž๋ฃŒ

Websocket github

nestjs lifecycle

websocket connection

๐Ÿœ ํŒ€ ๊ฐœ๋ฏธ

๐Ÿ›๏ธ ํŒ€ ๋ฌธํ™”

๊ฐœ๋ฐœ ์œ„ํ‚ค

FE

BE

Infra

๐Ÿ—ฃ๏ธ ๋ฐœํ‘œ

๐Ÿ“š ํšŒ์˜๋ก

๐Ÿ”ด ์ธํ„ฐ๋ฏธ์…˜
๐ŸŸ  1์ฃผ์ฐจ
๐ŸŸก 2์ฃผ์ฐจ
๐ŸŸข 3์ฃผ์ฐจ
๐Ÿ”ต 4์ฃผ์ฐจ
๐ŸŸฃ 5์ฃผ์ฐจ
๐ŸŸค 6์ฃผ์ฐจ

๐Ÿ’ญ ํšŒ๊ณ 

๐Ÿง‘โ€๐Ÿคโ€๐Ÿง‘ ๋ฉ˜ํ† ๋ง

Clone this wiki locally