|
同名知乎:少個分號
互聯(lián)網(wǎng)時代越來越多的實時協(xié)作軟件出現(xiàn),例如在線點餐、文檔編輯、在線繪圖等。 今天來聊聊這些場景一般如何實現(xiàn)的。 場景和問題實時協(xié)作軟件一般用于多個人同時操作(也包括一個人多個會話)。例如 Google Doc 可以支持同時編輯文檔,并將多人編輯的結(jié)果合并到一起展示,而且能相互看到其它人的操作。 但是,在實現(xiàn)過程中會有非常多的技術(shù)問題和業(yè)務(wù)邏輯問題需要考慮:
Web 平臺如何建立長連接?建立長連接的技術(shù)從遠(yuǎn)古互聯(lián)網(wǎng)技術(shù)發(fā)展開始就有很多方案(有些方案現(xiàn)在很多人都不會再聽到了)。 建立 Web 長連接我們一般可以:
當(dāng)前主流的方案一般是 WebSocket。 如何進(jìn)行一致性處理?相比網(wǎng)絡(luò)連接的問題,如何讓多人編輯的結(jié)果盡量不沖突這個問題更難。 不同的場景、數(shù)據(jù)類型可以采用的策略并不相同,下面以文章開頭的幾個例子說明如何實現(xiàn)一致性。 點餐的場景下如何處理一致性?一般將業(yè)務(wù)操作原子化+冪等,把數(shù)據(jù)更新修改為加、減操作,使用最終一次性讓服務(wù)器決策結(jié)果(發(fā)生沖突時,一般是先到先做,后到丟棄策略),并將事件分發(fā)到參與方。 在線繪圖場景如何實現(xiàn)一致性?抽象圖的數(shù)據(jù)結(jié)構(gòu),使用節(jié)點和邊對圖進(jìn)行結(jié)構(gòu)化處理,以節(jié)點和邊為原子單位,利用數(shù)據(jù)庫的能力高速更新。發(fā)生沖突時,可以使用后到覆蓋的策略,后到的更新會覆蓋前一次的更新??梢允褂靡恍┓顷P(guān)系型數(shù)據(jù)庫的 upset 能力,將插入和更新合并處理。
在線文檔編輯場景如何實現(xiàn)一致性?可以使用 OT 算法(另外一種算法叫做 CRDT,CRDT 可以看做 OT 算法的拓展),OT 算法使用偏移量作為更新依據(jù),可以快速合并協(xié)同者的數(shù)據(jù)內(nèi)容。 某種程度上來說,文檔結(jié)構(gòu)和圖的結(jié)構(gòu)非常類似,因此可以使用非關(guān)系型數(shù)據(jù)庫的特點盡可能地提高性能。 不管什么一致性策略,時間久了都會不一致,在游戲場景和一些協(xié)作算法中會使用一個叫影子跟隨的策略定期抹掉各個客戶端的差異。其原理是定期或者網(wǎng)絡(luò)連接中斷后,重新和服務(wù)器對齊版本,把服務(wù)器的最新版本往客戶端拉取。 如何支持離線操作?離線操作在實時協(xié)作系統(tǒng)是一個非常費力不討好的特性,因為總是會導(dǎo)致用戶數(shù)據(jù)丟失,而且非常難以探查問題。 以 MQTT over Socket 為例,如果要實現(xiàn)實時協(xié)作,可以考慮如下方案。
一般優(yōu)先推薦使用方案 2,這樣在版本比較時更可控,甚至可以允許用戶確認(rèn)兩個版本的差異,并根據(jù)某種策略合并。 另外需要注意,離線后重新上線需要使用類似影子跟隨的方式,獲取服務(wù)器最新的版本。(因為服務(wù)器總是要保存一份最新版本數(shù)據(jù))。 如何擴(kuò)容?實時協(xié)作系統(tǒng)不具備天然的水平擴(kuò)容能力,需要設(shè)計相關(guān)機(jī)制進(jìn)行擴(kuò)容。 擴(kuò)容方式一般有兩種:
我把他們叫做集中式擴(kuò)容和分散式擴(kuò)容。 集中式擴(kuò)容有點像吃大鍋飯,對用戶群不區(qū)分,而是對事件分發(fā)的服務(wù)器進(jìn)行水平擴(kuò)容。 下面是 Socket.IO 提供的解決方案:
反過來看分散式擴(kuò)容,如果我們根據(jù)一些業(yè)務(wù)策略將用戶 stick 到具體的服務(wù)器上,讓這些用戶在服務(wù)內(nèi)部完成事件交換,即使這些服務(wù)器在一定程度上是有狀態(tài)的。 分散式擴(kuò)容更像是游戲服務(wù)器模式,避免將消息廣播到太大范圍,因為我們在業(yè)務(wù)上總是能找到消息廣播的主題,讓廣播的范圍盡可能小,這樣系統(tǒng)崩潰時影響的用戶范圍比較小。 有那些框架可以幫助簡化實時協(xié)作的工作?從零開始實現(xiàn)一套實時協(xié)作系統(tǒng)比較困難,而且需要長期探索一些技術(shù)選型的問題。 這里分享一些常見的技術(shù)方案和框架作為參考。
從需求匹配性上來說,Convergence 就是為了實時應(yīng)用設(shè)計的,它有很多 Demo,包括在線協(xié)同編程、文檔協(xié)同編輯、協(xié)同繪圖等。借助 Akka 的分布式能力,Convergence 也支持水平拓展的集群部署。 Convergence 的缺點是它是一個完整的應(yīng)用,而不是一個庫,且是 Scala 編寫的,需要學(xué)習(xí)相關(guān)的語言特性。 而 Socket.IO 做的事情就少很多,Socket.IO 只提供網(wǎng)絡(luò)連接、事件分發(fā)等任務(wù),默認(rèn)情況通過 Websocket 進(jìn)行通信,在瀏覽器版本過低時也支持 HTTP 長連接,也就是 COMET 技術(shù)。 Socket.IO 提供了 Java、Nodejs 等平臺和語言的實現(xiàn),所以更容易集成到自己的應(yīng)用中來。 默認(rèn)情況下 Socket.IO 在內(nèi)存中實現(xiàn)消息轉(zhuǎn)發(fā),也可以接入集中的數(shù)據(jù)源或者事件 Adapter 來完成消息廣播的能力。 常用的有:
圖片來源:https:///docs/v4/adapter/ 實時協(xié)作應(yīng)用設(shè)計上的注意事項有一些實踐上的坑不得不提一下。 用戶數(shù)據(jù)丟失幾乎是必然的。 這一點主要還是 CAP 定理約束,在 系統(tǒng)設(shè)計 | 分布式事務(wù)場景、概念和方案整理(含概念圖) 中我們討論過這個話題,而在實時協(xié)作應(yīng)用中這種問題變得尤為突出。 用戶離線后即使開啟高的 QoS 保證消息最終發(fā)送到用戶,但是當(dāng)用戶最終收到消息后,現(xiàn)場可能已經(jīng)發(fā)生變化,所以這甚至帶來一些副作用。 不要過于依賴歷史消息。 為了保證用戶的數(shù)據(jù)安全,我們可能會開啟 QoS 保證,很多通信框架都會通過確認(rèn)的方式保證送達(dá)事件。 高的 QoS 不僅影響性能而且對一致性幫助不大,因為和上面提到的類似,歷史消息對最終結(jié)果意義不大。 本地實現(xiàn)Undo、Redo 操作 我們有時候會設(shè)置 Undo、Redo 操作來實現(xiàn)撤回和重做,但是一定不要將這兩個事件廣播出去,而是應(yīng)該在本地完成后,作為新的操作事件發(fā)送出去。 例如 Undo 在本地表現(xiàn)為刪除一段文本,那么發(fā)送到服務(wù)器的消息應(yīng)該是刪除一段文本。 合理設(shè)置心跳檢測機(jī)制 通信框架都會使用心跳來檢測離線,默認(rèn)情況下部分框架的心跳設(shè)置非常長,可能不能滿足某些場景需要,可以設(shè)置更短的心跳。 但是,心跳一般是網(wǎng)絡(luò)協(xié)議的職責(zé),盡量不要在應(yīng)用中再實現(xiàn)一次。 隨處可見的非阻塞式編程。 實時協(xié)作往往需要非常高的并發(fā)處理能力,一旦在代碼中需要異步處理的地方出現(xiàn)阻塞,整個系統(tǒng)的性能就會急劇下降,而且比較難以排查。 我們可以采用一些非阻塞式的框架或者庫提高響應(yīng)式編程的體驗,例如 RxJava、RxJS 等。 編程語言和平臺影響很大 有一些語言的并發(fā)能力天生就很強,比如 elixir、Erlang/OTP、Scala、Nodejs 等。這些編程語言或者平臺往往都是非阻塞式的,而 Java 一般來說主要應(yīng)用于業(yè)務(wù)系統(tǒng),對于需要高并發(fā)的場景來說,有能力的話嘗試其它語言可以收獲非常多(我曾經(jīng)將 MQTT 服務(wù)器切換為 EMQX 后獲得性能上的飛躍提升,不過其構(gòu)建在 Erlang/OTP 之上,非常遺憾的是,完全沒有能力對其源碼進(jìn)行拓展)。 參考資料[1] The free and open source engine for real-time collaboration https:/// [2] Socket.IO 文檔 https:///docs/v4/adapter/ [3] Building Real-time Applications with Phoenix & Elixir https://www./courses/building-real-time-applications-phoenix-elixir [4] Building real-time collaboration applications: OT vs CRDT https://www./blohttps://raw./linksgo2011/shaogefenhao-v2/master/src/posts/architecture//real-time-collaboration-ot-vs-crdt/ [5] Comet (programming) https://en./wiki/Comet_(programming) |
|
|