Hello Ocean! ๐ŸŒผ

[Go] go-socket.io์˜ Redis Adapter ๋ณธ๋ฌธ

Go

[Go] go-socket.io์˜ Redis Adapter

bba_dda 2022. 3. 3. 17:42
๋ฐ˜์‘ํ˜•

socket.io์—์„œ๋Š” redis adapter๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

Adapter๊ฐ€ ๋ฌด์—‡์ผ๊นŒ?

https://socket.io/docs/v4/adapter/

์ •์˜

Adapter๋Š” client์˜ ๋ถ€๋ถ„์ง‘ํ•ฉ ํ˜น์€ ์ „์ฒด client์—๊ฒŒ event๋ฅผ broadcastํ•˜๋Š” server-side component์ด๋‹ค.

์™œ ํ•„์š”ํ• ๊นŒ?

๊ธฐ๋ณธ์ ์œผ๋กœ socket.io์—์„œ in-memory adapter๊ฐ€ broadcast๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š”๋ฐ,

socket.io ์„œ๋ฒ„๊ฐ€ ์—ฌ๋Ÿฌ๋Œ€๋กœ ๋ถ„์‚ฐ๋˜์–ด์žˆ์„ ๋•Œ, in-memory adapter๋ฅผ ์ด์šฉํ•˜๋ฉด ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธด๋‹ค.

์•„๋ž˜ ๊ทธ๋ฆผ์ฒ˜๋Ÿผ ๋‘ ๊ฐœ์˜ socket.io ์„œ๋ฒ„๊ฐ€ ์žˆ์„ ๋•Œ, in-memory adapter๋ฅผ ์ด์šฉํ•˜๋ฉด

server A์—์„œ server B์— ์—ฐ๊ฒฐ๋˜์–ด์žˆ๋Š” client๋“ค์—๊ฒŒ ๋ฉ”์„ธ์ง€๋ฅผ ๋ณด๋‚ด์ค„ ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

๊ทธ๋ž˜์„œ ๊ธฐ๋ณธ in-memory adapter๋ฅผ ๋‹ค๋ฅธ adapter๋กœ ๊ต์ฒดํ•  ํ•„์š”๊ฐ€ ์žˆ๋‹ค.

์ง€์›ํ•˜๋Š” Adapter์˜ ์ข…๋ฅ˜

๊ธฐ๋ณธ in-memory adapter๋ฅผ ์ œ์™ธํ•˜๊ณ 

js์˜ socket.io์—์„œ๋Š” ๊ณต์‹์ ์œผ๋กœ 4์ข…๋ฅ˜์˜ adapter๊ฐ€ ์กด์žฌํ•œ๋‹ค.

go-socket.io๋Š” ๋”ฐ๋กœ ์ •๋ฆฌ๋˜์–ด์žˆ๋Š” ๋ฌธ์„œ๋Š” ์—†์ง€๋งŒ,

github ์†Œ์Šค์ฝ”๋“œ๋ฅผ ๋ดค์„ ๋•Œ, redis-adapter๋งŒ ์กด์žฌํ•œ๋‹ค. (22๋…„ 3์›” ๊ธฐ์ค€)

 

Redis Adapter

https://socket.io/docs/v4/redis-adapter/

Redis Adapter๋Š” Redis์˜ Pub/Sub ๋งค์ปค๋‹ˆ์ฆ˜์— ์˜์กดํ•œ๋‹ค.

 

Redis Pub/Sub ๋งค์ปค๋‹ˆ์ฆ˜์— ๋Œ€ํ•ด ์ž˜ ๋ชจ๋ฅธ๋‹ค๋ฉด ๋จผ์ € ๊ณต๋ถ€ํ•˜๊ณ  ์˜ค๋Š” ๊ฒƒ์„ ์ถ”์ฒœํ•œ๋‹ค!

 

redis adapter์— ์—ฐ๊ฒฐ๋œ ์—ฌ๋Ÿฌ socket.io์„œ๋ฒ„ ์ค‘ ํ•œ ์„œ๋ฒ„์—์„œ emit์„ ํ•˜๋ฉด,

  1. ํ˜„์žฌ ์„œ๋ฒ„์— ์—ฐ๊ฒฐ๋œ ํด๋ผ์ด์–ธํŠธ๋กœ ์ „์†ก๋˜๋Š” ๊ฒƒ์€ ๋‹น์—ฐํ•˜๊ณ 
  2. ์ถ”๊ฐ€์ ์œผ๋กœ Redis ์ฑ„๋„์— ๋ฉ”์„ธ์ง€๊ฐ€ publish๋˜๊ณ , ํด๋Ÿฌ์Šคํ„ฐ์˜ ๋‹ค๋ฅธ socket.io ์„œ๋ฒ„์—์„œ ์ด๋ฅผ ์ˆ˜์‹ ํ•ด ํ•ด๋‹น ์„œ๋ฒ„์— ์—ฐ๊ฒฐ๋œ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ๋„ ์ „์†ก๋œ๋‹ค.

์—ฌ๊ธฐ๊นŒ์ง€๊ฐ€ ๋ฌธ์„œ์˜ ์„ค๋ช…์ด๋‹ค.

๋ฌธ์„œ์—์„œ๋Š” socket.io ์„œ๋ฒ„๊ฐ€ ์—ฌ๋Ÿฌ ๋Œ€๋กœ ๋ถ„์‚ฐ๋˜์–ด ์žˆ์„ ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค๋Š” ๋‚ด์šฉ๋งŒ ์ ํ˜€์žˆ๋‹ค.

 

ํ•˜์ง€๋งŒ ์ด๊ฒƒ์„ ์‘์šฉํ•˜๋ฉด, socket.io์„œ๋ฒ„๊ฐ€ ์•„๋‹Œ ์ผ๋ฐ˜์ ์ธ ์„œ๋ฒ„๋ฅผ socket.io ์„œ๋ฒ„์— ๋Œ€ํ•œ trigger์ฒ˜๋Ÿผ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ๋‹ค. 

 

ํŠน์ • ์„œ๋ฒ„์—์„œ Redis์— ํŠน์ • ํ˜•์‹์˜ ๋ฉ”์„ธ์ง€๋ฅผ publishํ–ˆ์„ ๋•Œ,
๋™์ผํ•œ Redis์— ์—ฐ๊ฒฐ๋œ socket.io ์„œ๋ฒ„์˜ Redis Adapter๋ฅผ ํ†ตํ•ด emitํ•˜๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค!

 

 

์ด ๊ณผ์ •์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š”,

Redis์˜ ์–ด๋–ค ์ฑ„๋„์— ์–ด๋–ค ํ˜•์‹์˜ ๋ฉ”์„ธ์ง€๋ฅผ publish ํ•ด์•ผ socket.io์˜ redis adapter๊ฐ€ ์•Œ์•„์ฑŒ ์ˆ˜ ์žˆ๋Š”์ง€ ์•Œ์•„์•ผ ํ•œ๋‹ค.

 

js์˜ ๊ฒฝ์šฐ์—๋Š” ๊ฐ€์ด๋“œ๋ผ์ธ์„ ์ œ๊ณตํ•˜๋Š”๋ฐ, (js์˜ redis adapter github)

go-socket.io๋Š” ๊ฐ€์ด๋“œ๋ผ์ธ์„ ์ œ๊ณตํ•˜์ง€ ์•Š์•„์„œ ์ง์ ‘ ์ฐพ์•„์•ผ ํ–ˆ๋‹ค.

 

์‚ฝ์งˆ ๐Ÿ› ๏ธ

postman์œผ๋กœ socket์„ ์—ฐ๊ฒฐํ•ด ๋†“๊ณ ,

redis-cli๋ฅผ ์ด์šฉํ•ด, ํ„ฐ๋ฏธ๋„์—์„œ psubscribe socket.io* ๋ช…๋ น์–ด๋ฅผ ์ด์šฉํ•ด ๊ตฌ๋…ํ•˜๊ฒŒ ํ•œ ๋’ค

์„œ๋ฒ„์—์„œ broadcast์™€ emit์„ ํ•˜๋ฉด์„œ ํ„ฐ๋ฏธ๋„์— ์–ด๋–ป๊ฒŒ ๋กœ๊ทธ๊ฐ€ ์ฐํžˆ๋Š”์ง€ ํ™•์ธํ•ด๋ณด์•˜๋‹ค.

 

socket.io*๋ฅผ ๊ตฌ๋…ํ•œ ์ด์œ ๋Š”, redis adapter ์„ค์ •์— prefix์˜ ๊ธฐ๋ณธ๊ฐ’์ด socket.io์˜€๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. redis ์ฑ„๋„ ์ด๋ฆ„์ด redis adapter์˜ prefix๋กœ ์‹œ์ž‘ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์—ฌ๊ธฐ์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

 

go๋กœ ์ž‘์„ฑ๋œ socket.io ์„œ๋ฒ„์—์„œ

server.BroadcastToNamespace("/test", "hi", "hello")์˜ ๊ฒฐ๊ณผ๋กœ ์•„๋ž˜์™€ ๊ฐ™์€ ๋กœ๊ทธ๊ฐ€ ์ฐํ˜”๋‹ค.

1) "pmessage"
2) "socket.io*"
3) "socket.io#/test#c3e57b2d-2400-45d9-9cda-6db7817929df"
4) "{\"args\":[\"hello\"],\"opts\":[\"\",\"hi\"]}"

์ฑ„๋„ : socket.io#/test#${uid}

๋ฉ”์„ธ์ง€: {\"args\":[\"hello\"],\"opts\":[\"\",\"hi\"]}

 

์ด๋ ‡๊ฒŒ ๋ช‡ ์ฐจ๋ก€ ๋กœ๊ทธ๋ฅผ ์ฐ์–ด๋ณธ ๊ฒฐ๊ณผ,

prefix#namespace# ์ฑ„๋„์— ์•„๋ž˜์™€ ๊ฐ™์€ ํ˜•์‹์˜ ๋ฉ”์„ธ์ง€๋ฅผ publishํ•ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๊ฒŒ๋˜์—ˆ๋‹ค.

{
    args: [payload...],
    opts: ["roomId", "event name"],
}
  • args: ๋ง ๊ทธ๋Œ€๋กœ ๋‚ด์šฉ(payload)
  • opts๋Š” 2์นธ์งœ๋ฆฌ ๋ฐฐ์—ด์ธ๋ฐ,
    • opts[0] : roomId (roomId๊ฐ€ ํ•„์š” ์—†๋Š” ๊ฒฝ์šฐ์—๋Š” “” ์ด๋ ‡๊ฒŒ ๋นˆ ๊ฐ’์œผ๋กœ ์ž‘์„ฑ)
    • opts[1] : event name ์œผ๋กœ ์ž‘์„ฑํ•˜๋ฉด ๋œ๋‹ค!

 

์ด๋ ‡๊ฒŒ publishํ•˜๋‹ˆ ๋งค์šฐ ์ž˜ ์ž‘๋™ํ–ˆ๋‹ค :)

์ฝ”๋“œ ๋ถ„์„

ํ•˜์ง€๋งŒ.. ๋ญ”๊ฐ€ ์ฃผ๋จน๊ตฌ๊ตฌ์‹์œผ๋กœ ์ฐพ์€ ๊ฒƒ ๊ฐ™์€ ๋Š๋‚Œ์ด ๋“ค์–ด, go-socket.io์ฝ”๋“œ๋ฅผ ๋ถ„์„ํ•ด๋ณด์•˜๋‹ค.

๊ทธ ์ „์—๋„ ์ฝ”๋“œ ๋ถ„์„์„ ์‹œ๋„ํ–ˆ์œผ๋‚˜ ๋ช…ํ™•ํ•˜๊ฒŒ ๋ณด์ด์ง€ ์•Š์•˜๋Š”๋ฐ, ๋‹ต์„ ์•Œ๊ณ ๋‚˜์„œ ์ฝ”๋“œ๋ฅผ ๋ณด๋‹ˆ๊นŒ ๋” ์ž˜ ์ฝํ˜”๋˜ ๊ฒƒ ๊ฐ™๋‹ค.

๊ทธ๋ฆฌ๊ณ  redis adapter๊ด€๋ จ ์ฝ”๋“œ๊ฐ€ ํ•œ ํŒŒ์ผ์— ๋“ค์–ด์žˆ์–ด์„œ ๊ทธ๋ฆฌ ๋ณต์žกํ•˜์ง€ ์•Š๊ฒŒ ๋ณผ ์ˆ˜ ์žˆ์—ˆ๋‹ค.

 

์ฝ”๋“œ ๋งํฌ

 

1. ์ฑ„๋„ ์ด๋ฆ„

์•„๋ž˜ ์ฝ”๋“œ์—์„œ key field๋ฅผ ๋ณด๋ฉด

prefix#nsp(namespace)#uid ๋ผ๊ณ  ๋˜์–ด์žˆ๋Š”๋ฐ, ์œ„์—์„œ ํ™•์ธํ•œ redis ๋กœ๊ทธ์˜ ํ˜•์‹๊ณผ ์ผ์น˜ํ•œ๋‹ค.

socket.io#/test#c3e57b2d-2400-45d9-9cda-6db7817929df

func newRedisBroadcast(nsp string, opts *RedisAdapterOptions) (*redisBroadcast, error) {
    ...
    rbc := &redisBroadcast{
        rooms:      make(map[string]map[string]Conn),
        requests:   make(map[string]interface{}),
        sub:        subConn,
        pub:        pubConn,
        key:        fmt.Sprintf("%s#%s#%s", opts.Prefix, nsp, uid),
        reqChannel: fmt.Sprintf("%s-request#%s", opts.Prefix, nsp),
        resChannel: fmt.Sprintf("%s-response#%s", opts.Prefix, nsp),
        nsp:        nsp,
        uid:        uid,
    }

    ...
}

 

 

๋‹ค๋งŒ, ์ด๊ฑด socket.io ์„œ๋ฒ„์—์„œ broadcastํ–ˆ์„ ๋•Œ ์ƒ๊ธฐ๋Š” redis message์ด๊ณ ,

๊ทธ๋ƒฅ redis์— ๋ฐ”๋กœ publishํ•  ๋•Œ๋Š” uid ๋ถ€๋ถ„์„ ์ƒ๋žตํ•ด๋„ ๋˜์—ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  defalut namespace (/)๋Š” namespace ์ž๋ฆฌ์— /๋ฅผ ์ ๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ์•„์˜ˆ ์ƒ๋žตํ•ด์•ผ ํ–ˆ๋‹ค.

 

๊ฒฐ๋ก  โญ๏ธ

๋”ฐ๋ผ์„œ, ๋‚ด๊ฐ€ ๋งŒ๋“  ํ”„๋กœ์ ํŠธ์—์„œ๋Š”

  • prefix : socket.io
  • namespace : defalut (/)

์˜€๊ธฐ ๋•Œ๋ฌธ์— socket.io## ์ฑ„๋„์— publish๋ฅผ ํ•ด์•ผ ํ–ˆ๋‹ค.

 

js๋Š” ์—ฌ๊ธฐ์— ์ฑ„๋„ ๋„ค์ด๋ฐ ๊ด€๋ จ ๋‚ด์šฉ์ด ๋‚˜์™€์žˆ๋Š”๋ฐ,

์ด์— ๋”ฐ๋ฅด๋ฉด socket.io#namespace#roomId ๋กœ publish ํ•ด์•ผํ•˜๋Š”๋ฐ, go์—์„œ๋Š” ์ฑ„๋„ ์ด๋ฆ„์— roomId๋ฅผ ์ ๋Š” ๋ฐฉ๋ฒ•์ด ๋จนํžˆ์ง€ ์•Š์•˜๋‹ค. (์„ค๊ณ„๊ฐ€ ๋‹ค๋ฅด๊ฒŒ ๋˜์–ด์žˆ๋‚˜๋ณด๋‹ค..)

go-socket.io์—์„œ๋Š” ์œ„์—์„œ ๋งํ•œ ๊ฒƒ ์ฒ˜๋Ÿผ opts[0]์— roomId๋ฅผ ์ ์–ด์ค˜์•ผ ํ•œ๋‹ค.

 

2. ๋ฉ”์„ธ์ง€ ํ˜•์‹

์•„๋ž˜ ์ฝ”๋“œ๋Š” redis adapter๊ฐ€ redis์—์„œ subscribe๋ฅผ ํ†ตํ•ด ๋ฐ›์€ ๋ฉ”์„ธ์ง€๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฉ”์†Œ๋“œ์ด๋‹ค.

func (bc *redisBroadcast) onMessage(channel string, msg []byte) error {
    channelParts := strings.Split(channel, "#")
    nsp := channelParts[len(channelParts)-2]
    if bc.nsp != nsp {
        return nil
    }

    uid := channelParts[len(channelParts)-1]
    if bc.uid == uid {
        return nil
    }

    var bcMessage map[string][]interface{}
    err := json.Unmarshal(msg, &bcMessage)
    if err != nil {
        return errors.New("invalid broadcast message")
    }

    args := bcMessage["args"]
    opts := bcMessage["opts"]

    room, ok := opts[0].(string)
    if !ok {
        return errors.New("invalid room")
    }

    event, ok := opts[1].(string)
    if !ok {
        return errors.New("invalid event")
    }

    if room != "" {
        bc.send(room, event, args...)
    } else {
        bc.sendAll(event, args...)
    }

    return nil
}

์ฝ”๋“œ link

 

onMessage()์—์„œ msg๋ฅผ ์ฒ˜๋ฆฌํ•  ๋•Œ,

args, opts ํ•„๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๊ณ , ์œ„์—์„œ redis ๋กœ๊ทธ๋ฅผ ํ†ตํ•ด์„œ ์œ ์ถ”ํ•œ ๊ฒƒ๊ณผ ์ •ํ™•ํžˆ ์ผ์น˜ํ–ˆ๋‹ค.

opts[0]๋ฒˆ room์— opts[1] event๋กœ args๋ฅผ broadcast

  • room๋‹จ์œ„๊ฐ€ ์•„๋‹ˆ๋ผ broadcastํ•˜๊ณ  ์‹ถ์„ ๋•Œ์—๋„ opts ๊ธธ์ด๋Š” 2์—ฌ์•ผํ•จ (roomid๋ฅผ ๋นˆ ๊ฐ’(””)์œผ๋กœ ๋„ฃ์–ด ๋ณด๋‚ด๊ธฐ)
    • opts: [””, “event name”]

socket.io ๋ฌธ์„œ์— ๋”ฐ๋ฅด๋ฉด,

adapter๋ฅผ ํ†ตํ•ด์„œ ๋‹จ์ˆœ ๋ฉ”์„ธ์ง€ emit ๋ฟ ์•„๋‹ˆ๋ผ

join room์ด๋‚˜, leave room ๋“ฑ์˜ ๊ธฐ๋Šฅ๋„ ๊ฐ€๋Šฅํ•˜๋‹ค๊ณ  ํ•˜๋Š”๋ฐ ์–ด๋–ป๊ฒŒ ํ•˜๋Š”์ง€ ์•„์ง ๋ชจ๋ฅธ๋‹ค!

 

๋‹ค๋งŒ redis_broadcast.go์˜ dispatch() ์—์„œ onMessage ๋ง๊ณ ๋„, onResponse, onRequest๋„ ์ˆ˜ํ–‰ํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ด์ชฝ ์ฝ”๋“œ๋ฅผ ๋” ์ž์„ธํ•˜๊ฒŒ ๋ณด๋ฉด ์•Œ ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™๋‹ค. 

func (bc *redisBroadcast) dispatch() {
    for {
        switch m := bc.sub.Receive().(type) {
        case redis.Message:
            if m.Channel == bc.reqChannel {
                bc.onRequest(m.Data)
                break
            } else if m.Channel == bc.resChannel {
                bc.onResponse(m.Data)
                break
            }

            err := bc.onMessage(m.Channel, m.Data)
            if err != nil {
                return
            }

        case redis.Subscription:
            if m.Count == 0 {
                return
            }

        case error:
            return
        }
    }
}

 

์ถ”๊ฐ€

go-socket.io์—์„œ ์“ฐ๋Š” redis package : redigo

๋‚ด๊ฐ€ ๊ฐœ๋ฐœํ•œ socket server์—์„œ ์“ฐ๋Š” redis package : go-redis

  • redigo vs redis

https://go.libhunt.com/compare-redigo-vs-redis

๋ฐ˜์‘ํ˜•

'Go' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

[Go] ๋ฐ์ดํ„ฐ ๊ฒฝํ•ฉ์„ ๊ฐ์ง€ํ•ด๋ณด์ž, Data Race Detector  (0) 2024.08.23