如何在大型 Web 應用中保持資料的同步更新?

時間 2021-05-06 15:11:37

1樓:陳連傑

這個文體本質上是分布式系統的資訊一致性問題。有興趣可以看看下面的鏈結,這個問題無比的複雜。另外,光資料庫主從一致性已經很複雜,要是做到所有ui層一致,再多的lamport都搞不定。

2樓:dboy

我用過的乙個架構,基本上是乙個順時針單向訊息流架構,支援過每分鐘億級的訊息流(高峰應用有大量Log相關的東西),相當可伸縮。

客戶端乙個WS長連,只接受PUSH,WS Cluster只負責鏈結管理,不作業務處理

客戶端其餘操作都是普通HTTP請求

客戶端請求到達應用伺服器,在那裡決定是否有實時同步需求

如果有,從應用伺服器後台PUSH message到訊息陣列伺服器

訊息陣列伺服器同時也接受後台其他系統(統稱外部系統)的PUSH

訊息陣列的下游是乙個message consumer集群

Message consumer集群就是從訊息陣列(佇列)裡取出訊息,作處理,這裡處理業務

處理完的實時同步請求PUSH到WS集群

理論上,Message consumer 和 WS集群間只需要公用乙個長連

最後WS集群通過和客戶端的WS長連完成多播

單向資料流是乙個架構選擇,不是必然的,我們只是覺得這樣好弄

主要的設計細節,就是message本身的設計,分類處理,每類訊息的資料結構都是乙個強型別schema

大型實時系統通常都要用訊息佇列,因為可能有業務的實時非常關鍵,所以訊息要持久可靠,所以需要AMQP這樣的東西

我個人感覺這個架構最大的好處是前端寫程式好寫,後端也好分,前端就是正常的AJAX,跟WS互動也簡單,自己做好observer的角色就好了

其他細節就不贅述了。

3樓:

感覺題主在問2個問題:

1. 如何處理客戶端資料實時更新;

輪詢長鏈結websocket

2. 客戶端資料持久化。

基於JSONAPI規範的客戶端持久化方案,比如ember data

4樓:汪志成

基本贊成 @徐飛 的方案,再補充點我的思考。

拋開具體技術不談,從設計的角度來看,其實這種方案和資料庫主從伺服器的同步方案是類似的,可能還要更簡單一些。在具體設計實現上可以從中借鑑一些演算法。(剛發現另一位答主 @余紀良 和我想到一塊去了。

)深度使用RxJS等FRP庫可以實現惰性載入,讓呼叫方不用關心資料是否存在於本地的問題,不必所有更改都實時同步到前端。這樣在記憶體占用、傳輸速度、響應時間等方面就可以做出較好的權衡,程式複雜度也得以簡化。

注意考慮具體場景。在一些負載不高的場景下服務端可能只需要適時給前端推乙個「請重新整理」的通知就行了,然後前端就會重新查詢、重建當前用到的資料結構。這樣可以大幅簡化程式設計,提高可維護性。

精妙的設計往往意味著沉重的代價,如果這份代價不能產生相應的業務價值,那就放棄它。對於很多場景來說,實時性並非必須,300毫秒重新整理和3秒重新整理並沒有使用者可感知的差異,更不會影響業務的正確性。

5樓:vilicvane

最近因為產品需要開始做這一塊兒, 如果沒有理解錯的話, 和 @徐飛 老師提的方案比較像:

我們為可同步的資源建立了叫做 Syncable 的抽象, 前後端分別有自己對於這個資源的定義 (SyncableDefinition), 對與資源的修改通過前後端分別定義的同名 change (改變) 進行, 而這些改變則通常對應乙個操作, 比如 assign, handle-now.

在客戶端, 資源始終儲存在對應的 Map 中, 有效的 syncable 物件引用始終只有 1~2 份 (為什麼會有 2 份之後會提到). 一開始我們確實也沒有使用 immutable, 原因也和徐飛老師提到的類似, 但後來發現結合 immutable + getter + OnPush 的變化檢查策略, 並不會顯著增加維護難度 (自然不需要各種通知機制), 效能說不定會更好. 而 immutable 的變化僅僅是通過乙個十多行的 assignImmutable 函式完成的.

class

ValueObject

export

function

valueObject

(object:T

):Texport

function

assignImmutable

(object={}

asT,mutation: RecursivePartial

):T

,object

);let

keys

=Object

.keys

(mutation)as

(keyof

typeof

mutation

);for

(let

keyof

keys

)return

mutated;}

另外有一點大家很少提到的, 就是客戶端資料回滾機制. 由於希望未來產品的某個分支能夠完全離線使用, 這一塊兒最好一開始就想清楚. 我們的方式是對於每乙個資源都存乙份快照, 加乙份客戶端最新版, 當然很多時候它們都是同乙個引用.

為什麼要多此一舉?

一方面是因為因為種種原因客戶端的修改可能被伺服器端拒絕, 如果這種情況發生, 我們需要對客戶端資料進行回滾. 如果等待列表中還有尚未確認的變化, 還需要將這些變化在回滾後的物件上重放. 盡可能多的應用使用者對物件進行的操作.

另一方面是在乙個物件變化同步的過程中, 如果有來自其他客戶端的變化先廣播到, 那我們在合併時應該尊重變化的順序, 先將廣播收到的變化合併在快照上, 再以合併後的快照為基礎重放待伺服器端確認的變化.

除了上面這些比較主要的問題, 我覺得還有個比較有意思的是資源的可見性. 由於種種原因, 乙個資源對於某個客戶端的可見性會發生變化, 比如某個使用者對原有公開的資源設定了私有, 或是對原私有的資源進行了公開, 都會使乙個 change 對於某個客戶端等價為 creation 或 removal. 這些也算是需要注意的細節吧.

6樓:

站點大了的話是不可能保證實時更新的。大型站點多是採用非同步MQ的方式,所以同步不同步可以在業務上進行一定的控制。

比如引入MQ,訊息的投遞可以有不同的確認機制,如果投遞MQ即確認投遞成功,則可向訂閱使用者傳送訊息做一些操作。

但是設計到一些交易行為時,還是需要使用者感知的提示需要非同步。

7樓:張海

可是這個問題本來就很簡單啊,合格的web後端開發都能解決。實現方式就兩種,輪詢和長連線。

輪詢很簡單,就是客戶端跑個定時器,定時請求伺服器獲取新資料。

長連線本質也不複雜,就是乙個請求盡量開大超時時間,在超時時間內雙方傳送檢測包,維持連線狀態。

何況現在有很多類庫,框架,都已經幫你封裝好底層,直接使用無需關心底層細節。

web的基礎開發,現在都是熟極而流了,很少有什麼新鮮貨,無非實現的效能略有高低。合格的開發都能保證效能在某個基準水平線之上,牛x的大神可以優化,但是也不存在顯著提公升。

web後端開發現在的難點都是在於,受限於CAP原則,高併發,大資料量的情況下如何處理和同步資料。

單機支撐10K左右的請求,基本達到裝置和系統的物理極限,這個時候需要拆分不同的的服務和資料流,需要部署多點並在多點之間同步資料。這就是著名的10K問題,這個時候系統的複雜度會指數級提公升。

當然,做個架構支援超過10K請求的工程師也還是大有人在,難度還不算特別高。做好架構之後,支援更高請求無非多加機器裝置而已。

但是當併發請求超過某個範圍,我自己估算大概是10K的幾百至一千倍,即併發請求在600W-1000W量級,系統會再次遇到效能瓶頸,並且無法通過簡單的增加硬體裝置方式解決。這個時候系統的複雜度會再次指數級提公升,能夠在這種場景下解決資料同步問題的工程師,那就只有麟角鳳毛了。

可見web應用的問題難度,還是在併發和資料的量級上,題設沒有提到規模量級的情況下,我只能說這個問題很簡單。

8樓:逐風

根據這邊的模型,雖然大型沒試用過,大約是這樣的:

1.A有個資料池帶有時間戳,並且A保留1-N個版本的修改資料。

2.B關注了A之後,B帶有乙個A資料的資料池,並且有個時間戳。

3.B上線之後,檢查關注的時間戳是否等於原始A資料的時間戳,獲取差異資料。(第一次主動同步)

4.在A B都上線期間 A修改了資料。 剩下的事情就是如何通知B 有新的時間戳的問題。。

9樓:

推送通道的問題就不說了,很多解決方案。

關於資料同步我感覺很像資料庫的一主多從的複製機制,主從複製通過日誌同步實現。

伺服器相當於主,客戶端為從。

當客戶端發起增、刪、改的操作到伺服器後,伺服器執行完成,「順序」紀錄資源變動日誌,每條變動日誌產生乙個編號。

客戶端拉取變動日誌,拉取時傳入客戶端最後乙個變動編號,順序執行拉取結果。

初始化:當客戶端重新拉取資源的時候,同時拉取這個資源,和這個資源當前的最後一條日誌的變動編號。因為這個動作每個客戶端基本取一次就好,可以加鎖(加鎖的原因是保證當前資源與資源變動編號一致)

這種方法最重要的是定義好資源,然後形成資源的變動日誌。

PS:腦洞有點大,不知道是不是符合題主的需求。

10樓:徐飛

這個問題,最複雜的地方不在選取什麼推送通道,而是如何同步資料的更新。

複雜的地方有幾個點:

不管是客戶端還是服務端,都必須把所有操作進行抽象,每個操作都是平級的,描述自己是什麼操作,變更了什麼資料,就像傳統的命令模式裡的那樣,這裡面的麻煩在於,不是所有資料都是平級的,可能有子資料,比如某物件的某屬性又是個物件陣列,可能會要把乙個普通操作拆成多個原子操作,原子操作包括:新增、移除、修改物件的屬性,新增,修改,移除陣列元素,或者重排序等等。

服務端需要給每個客戶端保持乙個池子,注意,是每個客戶端,而不是每個使用者,所以,如果乙個使用者同時登入了多個客戶端,就得有多份池子。這個池子裡面存放的是這個客戶端當前積壓的操作列表,還需要考慮釋放策略,比如他斷開了,什麼時候把這個池子清除,或者,他重新連線,是怎樣重新連線的,是在原先某個已經有部分資料的介面上重新連線,還是全重新整理。

客戶端可能會有對同乙份資料的不同引用,對於這一點,我不贊同那些把資料分拆傳遞的方式,這也是我傾向於Angular的原因之一。我建議這樣:

所有相同的業務資料,只在客戶端保留乙份,每個使用者通過引用持有原資料

但是,但是,他不能直接在這個資料上寫入,寫入操作必須在業務資料的一層封裝上去做,禁止直接去修改持有的這個引用(對於Angular來說,這個理論上也可以寫,但會造成可維護性的嚴重降低)

所以我們得出乙個模式:

在前端做乙個服務層,這裡面維護業務資料與服務端的同步(不再是呼叫RESTful介面這麼簡單,建議是服務端與客戶端使用相同的命令模式)

業務上層邏輯可以呼叫這個服務層來獲取資料,多個不同的消費者通過呼叫,獲取的是相同的引用

業務上層邏輯如果要對資料進行更改,不能自行與服務端聯絡,也不能直接在持有的業務資料上修改,而是來呼叫這個服務層封裝好的方法

如果你用的是Angular這種,到這裡基本就完事了。。。

如果你用的框架使用immutable,或者需要在元件間隔離資料,那你就折騰了,前端的服務層還得再來個事件派發機制,每個消費者呼叫一下,或者,反過來,把UI層也實現成乙個大大的命令模式,讓服務層反過來呼叫它(別這樣,這畫面太美了)

我來開個腦洞:很多時候,大家講服務層,都是認為它應該在下層,但認真想一想,服務層為什麼就一定是下層?不可以是上層?

在這個業務場景下,負責客戶端和服務端互動的那層命令模式才是真正的入口,其他不管前端還是後端,都應該是它們的下層,都是它們的操作物件!

如何在宿舍中保持沉默?

黎鐵鐵鐵鐵 無視宿舍發生的任何事情,做到沒有人找你你就不去找任何乙個人,能盡量自己處理的就自己處理,我行我素,我幹我的你玩你們的,聊天的時候你不聽,帶上耳機待在自己的宇宙 筱絮 保持沉默,要看哪種沉默了。如果他們說的你不感興趣,你保持沉默很簡單的呀 如果他們說的不符合你的價值觀,你又不想辯解,覺得沒...

如何在暴雨中保持優雅?

黃小鴨 優雅不優雅的吧 上大學時和室友出門遇上暴雨,雨傘什麼的根本不管用 索性就把傘收了 脫了鞋光著腳丫子在路上趟水 路燈在雨中迷迷晃晃的,天地間好像只有我們倆似的 馬路變成了小溪,夏日滾燙的路面把Sunny的暖意傳給新來的雨水,從腳麵上流過,很愜意 autistic amor 把傘收起來拿在手上,...

如何在孤獨的生活中保持樂觀和自律?

wang blue 瀉藥。孤獨的狀態下,很難樂觀,能保持心情的平靜已經是難能可貴了。孤獨的狀態,是每乙個人都要面對的問題,或者說,即使他們很熱鬧,其實也是大部分時間也是孤獨的,這是人類天生具備的。對待孤獨的做法,決定於你的性格,世界觀和人生觀,價值觀。一些人選擇融入集體,一些人默默無聞,一些人在黑暗...