Redis를 사용하여 여러 서버 구성으로 실시간 채팅 앱을 만들었습니다.

만든 것



이번에는 Redis의 이야기입니다.
Redis의 PubSub를 사용하여 여러 대 서버 구성의 시스템에서도 실시간 채팅을 실현 가능하게했습니다!


(왼쪽은 3000번 포트, 오른쪽은 3001번 포트와 별도의 서버에 접속하고 있습니다)

배경



요 전날 CyberAgent 씨가 주최 한 히다카손에 참가했습니다!
해커슨이라기보다는 ISUCON에 가까운 이벤트였지만, 어플리케이션/데이터베이스/인프라와 다양한 영역에서의 지식과 기술이 요구되어 자신의 능력 부족을 통감했습니다…
하지만 오목하고 있어도 주위의 엔지니어와의 차이는 깊어지는 한편이므로, 회개를 스프링에 최근에는 Linux 커멘드나 DB주위의 지식을 늘리기 위해서 책이나 Web 페이지를 읽으면서 공부하고 있습니다!

Redis란?



Redis는 REmote DIctionary Service의 약자로, Salvatore Sanfilippo씨에 의해 만들어진 키 밸류 스토어. 
인메모리 DB의 하나로 메모리에 데이터를 저장하기 때문에 CPU에서 직접 액세스할 수 있어 읽기/쓰기가 고속으로 실시할 수 있습니다. 초간 10만 SET 이상의 조작을 처리할 수 있다든가… 너무 굉장하다.

PubSub란?



PubSub는 Publish/Subscribe의 약자로 출판-구독 모델을 의미합니다.
PubSub에 필요한 액션은 두 가지입니다.
  • Subscriber(구독자)가 미리 어떤 채널을 Subscribe 하는지 설정한다.
  • Publisher(게시자)가 채널에 메시지를 던집니다.

  • Publisher가 사용하는 채널 = Subscriber가 구독하고 있는 채널, 그렇다면 Publisher가 메시지를 던졌을 때 Subscriber는 즉시 이벤트를 받을 수 있게 됩니다.

    구현



    server.js
    const app = require('express')()
    const server = require('http').createServer(app)
    const io = require('socket.io')(server)
    const redis = require('redis')
    const sub = redis.createClient()
    const pub = sub.duplicate()
    
    app.get('/', (req, res) => {
      res.sendFile(`${__dirname}/index.html`)
    })
    
    io.on('connection', socket => {
      socket.on('chat', msg => {
        pub.publish('chat', msg)
      })
    })
    
    sub.on('message', (channel, message) => {
      io.emit('chat', message)
    })
    
    sub.subscribe('chat')
    
    server.listen(3000, () => {
      console.log('listening on localhost:3000')
    })
    

    secondServer.js
    const app = require('express')()
    const server = require('http').createServer(app)
    const io = require('socket.io')(server)
    const redis = require('redis')
    const sub = redis.createClient()
    const pub = sub.duplicate()
    
    app.get('/', (req, res) => {
      res.sendFile(`${__dirname}/index.html`)
    })
    
    io.on('connection', socket => {
      socket.on('chat', msg => {
        pub.publish('chat', msg)
      })
    })
    
    sub.on('message', (channel, message) => {
      io.emit('chat', message)
    })
    
    sub.subscribe('chat')
    
    server.listen(3001, () => {
      console.log('listening on localhost:3001')
    })
    
    <!doctype html>
    <html>
      <head>
        <title>Socket.IO chat</title>
        <style>
          * { margin: 0; padding: 0; box-sizing: border-box; }
          body { font: 13px Helvetica, Arial; }
          form { background: #000; padding: 3px; position: fixed; bottom: 0; width: 100%; }
          form input { border: 0; padding: 10px; width: 90%; margin-right: .5%; }
          form button { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; }
          #messages { list-style-type: none; margin: 0; padding: 0; }
          #messages li { padding: 5px 10px; }
          #messages li:nth-child(odd) { background: #eee; }
          #messages { margin-bottom: 40px }
        </style>
      </head>
      <body>
        <ul id="messages"></ul>
        <form action="">
          <input id="m" autocomplete="off" /><button>Send</button>
        </form>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.1.1/socket.io.js"></script>
        <script src="https://code.jquery.com/jquery-1.11.1.js"></script>
        <script>
          $(function () {
            var socket = io();
            $('form').submit(function(){
              socket.emit('chat', $('#m').val());
              $('#m').val('');
              return false;
            });
            socket.on('chat', function(msg){
              $('#messages').append($('<li>').text(msg));
              window.scrollTo(0, document.body.scrollHeight);
            });
          });
        </script>
      </body>
    </html>
    
    
    $ node server.js
    $ node secondServer.js
    

    고전 한 곳



    도중 메시지를 던져도 서버 측에서 일체 이벤트를 받지 않는다는 벽에 부딪혔습니다.
    1시간 정도 고민하고 있던 곳에서, 서버측과 프런트측에서 사용하고 있는 Socket.io의 버젼이 다른 것을 깨달았습니다.실태!

    관련 기사



    히다카손의 무대 뒤

    좋은 웹페이지 즐겨찾기