- golang
- ์น๋ฆฐ์ด
- LCs
- ์ฌ๊ท
- js
- C++
- BFS
- ๋์ ํ๋ก๊ทธ๋๋ฐ
- DFS
- ๋นํธ๋ง์คํน
- ์ํฐ๋
- ์ฌ๋ผ์ด๋ฉ ์๋์ฐ
- ํธ๋ฆฌ
- ์นด์นด์ค2021
- ๋ฐฑ์๋ ํ๋ฆฌ์จ๋ณด๋ฉ
- ์๊ณ ๋ฆฌ์ฆ
- ๋ค์ต์คํธ๋ผ
- go
- ๊ฐ์ฅ๊ฐ๊น์ด๊ณตํต์กฐ์
- DP
- ๋นํธ๋งต
- ์นด์นด์ค ์ฝํ
- ํ๋ฆฌ์จ๋ณด๋ฉ
- ํ๋ก๊ทธ๋๋จธ์ค
- ์ด๋ถํ์
- ๋ฐฑ์ค
- ์์ฝ๋
- Union-Find
- Python
- nestjs
- Today
- Total
Hello Ocean! ๐ผ
[Go] go-socket.io์ Redis Adapter ๋ณธ๋ฌธ
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์ ํ๋ฉด,
- ํ์ฌ ์๋ฒ์ ์ฐ๊ฒฐ๋ ํด๋ผ์ด์ธํธ๋ก ์ ์ก๋๋ ๊ฒ์ ๋น์ฐํ๊ณ
- ์ถ๊ฐ์ ์ผ๋ก 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
}
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
'Go' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Go] ๋ฐ์ดํฐ ๊ฒฝํฉ์ ๊ฐ์งํด๋ณด์, Data Race Detector (0) | 2024.08.23 |
---|