“ID串行化”是如何保證消息順序性的?
在《消息順序性為何這么難?》中,介紹了一種為了保證“所有群友展示的群消息時序都是一致的”所使用的“ID串行化”的方法:讓同一個群gid的所有消息落在同一臺服務器上處理。
ID串行化是如何實現(xiàn)的呢?
1. 互聯(lián)網(wǎng)高可用常見分層架構(gòu)
客戶端,反向代理層,接入層,服務層,存儲層,這是互聯(lián)網(wǎng)常見的高可用分層架構(gòu)。
畫外音:這個圖用過好多次。
這里的“服務層”至關(guān)重要,ID串行化保證的是,同一個群gid的消息落在同一個服務上。
畫外音:服務集群有很多節(jié)點,如果能落在同一個服務節(jié)點上,就可以利用這個服務節(jié)點做消息串行化。
2. 服務層上下游細節(jié)
服務一般由RPC框架實現(xiàn),上游調(diào)用方是多線程程序,通過RPC-client訪問服務,而RPC-client內(nèi)部又通過連接池connection-pool來訪問的。
畫外音:為了保證高可用,連接池會對集群中的每個服務都建立連接。
如上圖:
(1)上游是業(yè)務應用;
(2)下游是服務集群;
(3)業(yè)務應用,它又分為了這么幾個部分:
- 上層是任務隊列(粉色);
- 中間是工作線程(藍色),每個工作線程完成實際的業(yè)務任務,典型的工作任務是通過服務連接池進行RPC調(diào)用;
- 下層是服務連接池(綠色),所有的RPC調(diào)用都是通過服務連接池往下游服務發(fā)請求執(zhí)行;
畫外音:橙色是連接池中的一條連接。
工作線程的典型工作流是這樣的:
- void work_thread_routine(){
- // 獲取任務
- Task t = TaskQueue.pop();
- // 任務邏輯處理,組成一個網(wǎng)絡包packet
- Packet p = MakePacket(t);
- // 從Service連接池獲取一個Service連接
- ServiceConnection c = CPool.GetConnection();
- // 通過Service連接發(fā)送報文執(zhí)行RPC請求
- c.Send(p);
- // 將Service連接放回Service連接池
- CPool.PutConnection(c);
- }
如何保證同一個群gid的消息落在同一個服務上呢?
對連接池進行少量改動,獲取連接時:
- CPool.GetConnection()
畫外音:返回任何一個可用服務連接。
升級為
- CPool.GetConnection(long id)
畫外音:返回id取模相關(guān)聯(lián)的服務連接。
只要傳入群gid,就能夠保證同一個群的請求獲取到同一個連接,從而使請求落到同一個服務上。
需要注意的是,連接池不關(guān)心傳入的long id是什么業(yè)務含義:
- 傳入群gid,同gid的請求落在同一個服務上;
- 傳入用戶uid,同uid的請求落在同一個服務上;
- 傳入任何業(yè)務xid,同業(yè)務xid的請求落在同一個服務上;
ID串行化訪問服務,同一個id訪問同一個服務,當服務掛掉時,會不會受影響服務可用性?
不會,當有下游服務掛掉的時候,連接池能夠檢測到連接的可用性,取模時要把不可用的服務連接排除掉。
取模訪問服務,是否會影響各連接上請求的負載均衡?
不會,只要數(shù)據(jù)訪問id是均衡的,從全局來看,由id取模獲取各連接的概率也是均等的,即負載是均衡的。
【本文為51CTO專欄作者“58沈劍”原創(chuàng)稿件,轉(zhuǎn)載請聯(lián)系原作者】