goim 文章系列(共5篇):
- goim 架構與定製
- 從goim定製, 淺談 golang 的 interface 解耦合與gRPC
- goim中的 bilibili/discovery (eureka)基本概念及應用
- goim 的 data flow 資料流
- goim的業務整合(分享會小結與QA)
有個 slack 頻道, 不少朋友在交流 goim , 歡迎加入slack #goim
[簡述] goim.io 是 非常成功的 IM (Instance Message) 即時訊息平臺 , 本文介紹 goim 中的資料定義與 data flow 資料流轉
1. goim 中的 data flow 資料流轉
1.1 架構中的資料流轉
看圖
資料流轉
- http 介面向 logic 傳送資料
- logic 從 redis 中獲取會話資料, 以 protobuf 序列化資料, 傳送到 MQ
- job 從 MQ 中訂閱資料, 取出 im 傳送資料中的 server / room 向指定 server 的 comet 傳送資料
- comet 接收 job 分發的資料後, 存入 指定 channel 的 ring buffer , 再轉為 tcp/websocket 資料包, 傳送到指定 channel 的客戶端
1.2 簡化後的資料流轉細節
上示意圖示註了 goim 中的關鍵資料結構:
- 標註了 im 傳送資料構成, 注意, 這個資料結構是被logic 以 protobuf 序列化後發到 MQ , 並在 job 中反序列化後, 分發到 comet
- 這裡的會話資訊, 主要是 mid --> server 與 room-->server 的對應關係, 存在 redis 中
- comet 中的 im 資訊, 由 job 從 MQ 中反序列化後, 取出 server / room / keys( 一到多個key , 對應 channel ) 傳送到指定 comet server
- comet 以 tcp / websocket 封裝資料包, 傳送給終端使用者, 終端解包後顯示
2. goim 中的資料定義
2.1. logic 傳送 im 資訊
釋出 im 資訊定義( 在 protobuf 中的定義)
message PushMsg {
enum Type {
PUSH = 0;
ROOM = 1;
BROADCAST = 2;
}
Type type = 1;
int32 operation = 2;
int32 speed = 3;
string server = 4;
string room = 5;
repeated string keys = 6;
bytes msg = 7;
}
複製程式碼
2.2 會話資料
當 tcp client 或 websocket client 連線 comet server 時, comet 以 gRPC 向 logic 進行內部通訊, 生成會話資料, 存在 redis 中, 具體細節不展開, 看程式碼
當 http client 向 logic 傳送 im 訊息時, logic 向 redis 查詢會話資料, 對於已經存在的 room--> server / mid ( memberID) --> server 即傳送訊息到 MQ , 該部分程式碼比較清楚, 也不再加說明
2.3. tcp / websocket 資料包定義
推送 im 資訊, 物件名稱為 proto, 在 protobuf 中定義
message Proto {
int32 ver = 1 [(gogoproto.jsontag) = "ver"];
int32 op = 2 [(gogoproto.jsontag) = "op"];
int32 seq = 3 [(gogoproto.jsontag) = "seq"];
bytes body = 4 [(gogoproto.jsontag) = "body"];
}
複製程式碼
protobuf 檔案 github.com/Terry-Mao/g… 中第12行
tcp / websocket 資料包組包/折包操作在 /api/comet/grpc/protocol.go
由上圖可見, goim 在 tcp /websocket 資料包的資料包定義, 與 go 中 proto 定義, 多了, 資料包總長度 / 包頭長度兩個欄位
3. comet 中的處理
簡化資料流轉, 從傳送端資料到 接收端資料, 可以看到, serverID / roomID / channel ( 用 mid 或 key 來指示) 的主要作用作為分流/分發用, 在最後推送資料包中, 就不在包含這三個欄位了.
同時, comet 中使用了 ring buffer 來快取一個 channel 送達的多條資訊並推送到終端, 這裡, 並沒有看到對推送下發的資訊作更多處理.
_
_
看程式碼, 補充細節
// Channel used by message pusher send msg to write goroutine.
type Channel struct {
c *conf.CometConfig
Room *Room
CliProto Ring
signal chan *grpc.Proto
Writer xbufio.Writer
Reader xbufio.Reader
Next *Channel
Prev *Channel
Mid int64 // ######### memberID
Key string
IP string
watchOps map[int32]struct{}
mutex sync.RWMutex
}
複製程式碼
這裡:
- mid 就是 memberID , 當前 channel ( 使用者端與 comet 的長連線) 是哪個使用者連線上的 該長連線使用 key 作為長連線的會話標識, 換個方式說, key 也就標定了一個 im 資訊要發給哪個/哪幾個線上長連線對端的使用者
- key 就是長連線的會話ID, 可以這麼理解, 就算是 sessionID 吧
- watchOps 是一個map 對映表, 其中的 int32 是房間號. map 多個房間號, map 結構是用來查詢房間號是否在 map 中存在或不存在. watchOps 是當前長連線使用者用來監聽當前客戶端接收哪個房間的 im 訊息推送, 換個方式說, 一個 goim 終端可以接收多個房間傳送來的 im 訊息
- watchOps 初始化是在 tcp / websocket 客戶端進行首次連線時處理的, 細節看程式碼.
_
_
從 logic 自 http 的 post 請求中, 獲取釋出 im 資訊後, 序列化發到 MQ, 在 job 中拆包反序列化, 再組包, 這一步驟對效能是否有影響, 需發測試資料來定位, 但個人感覺, 這幾次拆包組包, 有點重複.
4. 小結
以上, 應開源社群的朋友要求, 對內部資料結構作了一個簡化分析, 花時不多,水平有限, 或有考慮不周或分析不當, 歡迎批評指點.
最後, goim.io 在網路上相關文章不少, 好文不少, 給我啟迪, 一併感謝.
推薦以下文章:
- alexstocks.github.io/html/im.htm… 作者 AlexStcks, 非常棒的文章, 集思踐行, 很有深度
- github.com/LinkinStars… 作者: LinkinStars, 這個值得一看
- moonshining.github.io/2018/03/09/… 寫得很細緻
再一次, 感謝 www.bilibili.com 的開源 & 毛劍 及眾多開源社群的前輩們,朋友們
_
關於我
網名 tsingson (三明智, 江湖人稱3爺)
原 ustarcom IPTV/OTT 事業部播控產品線技術架構溼/解決方案工程溼角色(8年), 自由職業者,
喜歡音樂(口琴,是第三/四/五屆廣東國際口琴嘉年華的主策劃人之一), 攝影與越野,
喜歡 golang 語言 (商用專案中主要用 postgres + golang )
_