在上一篇文章阅悍,我們講到了長連接常見的實(shí)現(xiàn)方案致开,相信大家對(duì)長連接已經(jīng)有一定的了解了,這篇文章我們會(huì)講 FeatureProbe 的長連接實(shí)現(xiàn)方案艇潭。
一、為什么FeatureProbe需要長連接
Feature Toggle 在部分場(chǎng)景下戏蔑,客戶端對(duì)實(shí)時(shí)性有較高的要求蹋凝,如緊急情況,希望配置立刻下發(fā)生效总棵。有的 Feature 在 Web 端加載或 App 啟動(dòng)的時(shí)候就要讀取到開關(guān)的值鳍寂,雖然緩存能解決一部分問題,但是最快拿到最新的值彻舰,會(huì)更符合用戶的預(yù)期。我們?cè)谏掀刑岬竭^候味,長連接可以解決數(shù)據(jù)推送和請(qǐng)求優(yōu)化這兩個(gè)場(chǎng)景刃唤。
1、可選協(xié)議
- SSE :Server Send Event 能滿足服務(wù)端向客戶端發(fā)送數(shù)據(jù)的需求白群,協(xié)議簡(jiǎn)單尚胞,但因?yàn)椴皇请p工的數(shù)據(jù)通路后期無法實(shí)現(xiàn) HTTP 的請(qǐng)求優(yōu)化。
- TCP :目前最主流的長連接協(xié)議帜慢,配合 TLS 1.3 可以做到很好的使用效果笼裳。
- QUIC :本身握手和 TLS1.3 融合,還支持連接恢復(fù)粱玲,多Stream避免包頭阻塞問題躬柬,有很多優(yōu)勢(shì),因?yàn)榛赨DP抽减,可能會(huì)有部分特殊網(wǎng)絡(luò)環(huán)境被禁止允青。
- UDP :需要自己實(shí)現(xiàn)丟包重傳,部分網(wǎng)絡(luò)環(huán)境有可能被限制卵沉。
- WebSocket :對(duì)瀏覽器友好颠锉,小程序唯一支持的雙向收發(fā) (全雙工) 協(xié)議法牲,很難做連接優(yōu)化。
2琼掠、設(shè)計(jì)目標(biāo)
- 盡可能支持更多的端拒垃,小程序,移動(dòng)端瓷蛙,多種語言服務(wù)端悼瓮;
- 盡量降低 SDK 的實(shí)現(xiàn)復(fù)雜度,方便后期社區(qū)貢獻(xiàn)速挑;
- 盡可能使開關(guān)快速生效谤牡;
- 盡可能低的數(shù)據(jù)傳輸量。
二姥宝、FeatureProbe長連接方案
1翅萤、協(xié)議選擇 WebSocket
小程序是我們一期要優(yōu)先支持的平臺(tái),所以所有不支持小程序的協(xié)議都不在一期的考慮范圍內(nèi)腊满。
- 優(yōu)點(diǎn):是可以支持小程序和瀏覽器環(huán)境套么,小程序是我們優(yōu)先要支持的部分,在國內(nèi)的重要性非常高碳蛋,很多創(chuàng)業(yè)團(tuán)隊(duì)甚至只開發(fā)小程序的 APP 版本胚泌。
- 缺點(diǎn):是連接建立的優(yōu)化很難進(jìn)行.在國內(nèi)網(wǎng)絡(luò)環(huán)境整體較好的情況下,大部分的請(qǐng)求還是在較快的響應(yīng)范圍之內(nèi).我們可以在后面二期的時(shí)候再針對(duì)其他端做多協(xié)議切換肃弟。
我們?cè)? Websocket 的基礎(chǔ)上進(jìn)一步選擇了 Socektio 這個(gè)網(wǎng)絡(luò)庫:
優(yōu)點(diǎn):是在 WebSocket 的基礎(chǔ)上提供了斷開重連玷室,發(fā)送緩沖,消息確認(rèn)笤受,廣播穷缤,整體的編解碼邏輯簡(jiǎn)單,提供了長輪詢(long polling)的回退方案箩兽,在不支持 WebSocket 的設(shè)備上也能兼容津肛。
缺點(diǎn):客戶端有限,老項(xiàng)目已經(jīng)比較成熟汗贫,目前已經(jīng)不太活躍身坐。
2、服務(wù)端推送
FeatureProbe Server 發(fā)現(xiàn)開關(guān)更新后落包,發(fā)送事件給關(guān)心這個(gè)開關(guān)的連接部蛇,對(duì)端的 SDK 收到事件,觸發(fā)一次開關(guān)拉取咐蝇。這里面能做的優(yōu)化是直接下發(fā)開關(guān)的值搪花,因?yàn)?Server SDK 和 Client SDK 的處理邏輯不同,我們放到下個(gè)迭代優(yōu)化。
如何發(fā)現(xiàn)變化:開關(guān)的規(guī)則是存儲(chǔ)在 FeatureProbe API 服務(wù)中的撮竿,目前 FeatureProbe Server 通過接口周期性訪問得到吮便,直觀的想法是輪詢時(shí),去 diff 開關(guān)的值幢踏,就可以發(fā)現(xiàn)變化髓需,但是效率比較低。因?yàn)?SDK 是針對(duì)項(xiàng)目環(huán)境下所有的開關(guān)進(jìn)行獲取房蝉,這里環(huán)境的 SDK KEY 拉取整體的開關(guān)規(guī)則時(shí)僚匆,添加一個(gè) version 就可以判斷兩次之間是否一致。
如何表示 SDK 對(duì)某個(gè)開關(guān)感興趣: 目前 SDK 向 Featureprobe Server 獲取開關(guān)搭幻,是以 SDK_KEY 為粒度的咧擂。在 SocketIO 連接建立后,SDK 會(huì)向 Server 注冊(cè) SDK_KEY, Server 就可以把這個(gè)連接存儲(chǔ)在相同 SDK KEY 的列表中檀蹋,等有開關(guān)發(fā)生變化松申,Server 知道開關(guān)是發(fā)生在哪個(gè) SDK_KEY 中,把 對(duì)應(yīng) SDK_KEY 列表中所有的連接都發(fā)送一個(gè)更新事件俯逾,就完成了變更的通知贸桶。實(shí)際實(shí)現(xiàn)利用了SocketIO 提供了 Room 的概念,僅需把連接和 SDK KEY做一下關(guān)聯(lián)桌肴,變更時(shí)直接對(duì) SDK_KEY 發(fā)送事件就可以了皇筛。
代碼示意:
import { createServer } from "http";
import { Server } from "socket.io";
const httpServer = createServer();
const io = new Server(httpServer);
io.on("register", (sdk_key, socket) => {
socket.join(sdk_key);
});
httpServer.listen(3000);
// notify clients
io.to(SDK_KEY).emit("update");
3、客戶端接收
FeatureProbe SDK 目前是 pull 模式和服務(wù)端通信坠七,即啟動(dòng)后通過輪詢來周期性獲取全量開關(guān)的數(shù)據(jù)水醋。在 SocketIO 的幫助下,添加 push 的模式很簡(jiǎn)單彪置。在原有基礎(chǔ)上初始化 SocketIO 的客戶端拄踪,連接建立后把 SDK KEY 發(fā)送給 Server, 然后監(jiān)聽一個(gè) Server 下發(fā)的 update 事件,收到事件就立刻觸發(fā)一次開關(guān)全量的拉取悉稠。斷開重連宫蛆,心跳艘包,回調(diào)等都交給 SocketIO 來做的猛。這里有個(gè)優(yōu)化是下發(fā)的數(shù)據(jù)可以是開關(guān)變更的數(shù)據(jù),而不僅僅是變更事件想虎,這個(gè)也是我們后續(xù)準(zhǔn)備做的工作卦尊。
三、最終實(shí)現(xiàn)
FeatureProbe Server 是 Rust 語言實(shí)現(xiàn)的舌厨,考慮到后續(xù)的性能和擴(kuò)展性等原因岂却,我們不想再引入一個(gè) nodejs 的模塊專門做長連接的管理,所以我用 Rust 實(shí)現(xiàn)了 SocketIO 的服務(wù)端 socketio-rs(實(shí)現(xiàn)的rust方案已經(jīng)開源到GitHub,點(diǎn)擊socketio-rs可訪問)躏哩,實(shí)際的 FeatureProbe 客戶端業(yè)務(wù)代碼和服務(wù)端業(yè)務(wù)代碼都相對(duì)比較簡(jiǎn)潔署浩。
目前FeatureProbe 使用 Apache 2.0 License 協(xié)議已經(jīng)完全開源。你可以從 GitHub 或 Gitee 上搜索FeatureProbe獲取到所有源代碼扫尺。