摘要:這里有點(diǎn)像的主從同步一樣,拿到內(nèi)存的最后版本后還有新過來的寫操作進(jìn)入和隊(duì)列,先把歷史版本推給客戶端,再把之后的寫操作一次推給客戶端。
以下為演講實(shí)錄:本文是野狗科技聯(lián)合創(chuàng)始人&架構(gòu)師謝喬在ArchSummit 北京2015全球架構(gòu)師峰會(huì)上進(jìn)行的《基于數(shù)據(jù)同步云服務(wù)架構(gòu)實(shí)踐》的演講實(shí)錄,主要分為三個(gè)方面:野狗的數(shù)據(jù)同步理念,數(shù)據(jù)同步的架構(gòu)演進(jìn),數(shù)據(jù)同步的細(xì)節(jié)問題。
野狗官博:https://blog.wilddog.com/
野狗官網(wǎng):https://www.wilddog.com/
公眾訂閱號(hào):wilddogbaas
可能大家在實(shí)際的應(yīng)用場(chǎng)景中不使用數(shù)據(jù)同步的業(yè)務(wù)模式,但是我是想跟大家分享我們?cè)谘葸M(jìn)過程中一些問題的解決思路,希望能對(duì)大家有所幫助。
今天的演講內(nèi)容主要分三個(gè)議題:
野狗的數(shù)據(jù)同步理念
數(shù)據(jù)同步的架構(gòu)演進(jìn)
數(shù)據(jù)同步的細(xì)節(jié)問題
野狗的數(shù)據(jù)同步理念首先從云端這塊兒開始講起,我們的數(shù)據(jù)存儲(chǔ)是個(gè)Schema-free的形式,樹形的數(shù)據(jù)庫(kù)像一顆Json樹,更像前端工程師們用的數(shù)據(jù)結(jié)構(gòu),它能把原來的關(guān)系型數(shù)據(jù)通過一些關(guān)聯(lián)查詢形成聚合型的數(shù)據(jù),比如blog,里面有標(biāo)題、回復(fù)等內(nèi)容,就相當(dāng)于把數(shù)據(jù)重新聚合,這樣數(shù)據(jù)之間的關(guān)系就更直觀了,方便大家快速的設(shè)計(jì)比較好的數(shù)據(jù)結(jié)構(gòu),完美的與url結(jié)合,每條數(shù)據(jù)都通過url來唯一定位,每個(gè)path作為一個(gè)key,就成為了key-value的數(shù)據(jù)結(jié)構(gòu)。
經(jīng)典的云服務(wù)是這樣的:現(xiàn)提供一個(gè)API,然后有其他的auth接入,云端有存儲(chǔ),有用戶管理,有hosting功能,還有周邊的一些工具,客戶端通過rest api這種方式與云端進(jìn)行交互來開發(fā)你的業(yè)務(wù)模型。
而野狗除了這一部分以外,還有一個(gè)富客戶端的SDK,本地也做了存儲(chǔ),當(dāng)本地?cái)?shù)據(jù)發(fā)生變化的時(shí)候會(huì)通過一個(gè)事件來通知用戶,然后用戶進(jìn)行修改。
具體來講,是客戶端與服務(wù)端建立一個(gè)長(zhǎng)連接,來完成數(shù)據(jù)同步,當(dāng)同步完成之后產(chǎn)生數(shù)據(jù)變化,就可以完成業(yè)務(wù)邏輯的實(shí)現(xiàn)。如果我們把模型再抽象一點(diǎn),就像一個(gè)主從的同步,客戶端作為從,和云端進(jìn)行副本級(jí)的同步過程。
也可以有另外一種同步方式,大家的服務(wù)可以與野狗云進(jìn)行實(shí)時(shí)同步。比如說,你的服務(wù)端進(jìn)行了一次數(shù)據(jù)修改,同步到云端,云端把這個(gè)修改同步給關(guān)注這個(gè)數(shù)據(jù)的客戶端。
數(shù)據(jù)實(shí)現(xiàn)同步的基本模型是這樣的:
開始有一個(gè)初始化的慢同步,可以做全量的同步或者條件同步,比如這個(gè)例子,客戶端A進(jìn)行了條件同步,同步到本地產(chǎn)生了一個(gè)本地副本,客戶端B通過全同步拉取到本地形成一個(gè)本地副本。當(dāng)客戶端A修改后,產(chǎn)生了新的數(shù)據(jù),我們把它叫增量同步,數(shù)據(jù)會(huì)push到云端。然后本地使用best-effort模式,客戶端先成功觸發(fā)事件,然后再同步到云端,云端再同步到其他的客戶端,實(shí)現(xiàn)最終一致性。
這個(gè)過程很像op log的過程,也是基于長(zhǎng)連接的,如果每次連接發(fā)生了異常,這里會(huì)重新連接進(jìn)行一次初始化慢同步過程。這也是我們所做的數(shù)據(jù)同步和消息推送的根本區(qū)別,原因是,消息推送要保證每個(gè)消息順序到達(dá),而且不丟失,數(shù)據(jù)同步則是在性能上的提升,只關(guān)心最終的數(shù)據(jù)狀態(tài)。一旦發(fā)生異常,客戶端重新連入到云端以后,不會(huì)把之前過程中的op log都傳過去,只需要重新進(jìn)行一次初始化操作,讓兩端進(jìn)行同步恢復(fù)就可以了。
數(shù)據(jù)同步的架構(gòu)演進(jìn)剛才講的業(yè)務(wù)方面的內(nèi)容可能比較枯燥,接下來就是我們技術(shù)架構(gòu)的演進(jìn)過程。
首先看一下我們技術(shù)架構(gòu)的特點(diǎn),跟其他傳統(tǒng)業(yè)務(wù)不太一樣,屬于寫多讀少。因?yàn)樽x只需要讀一次到客戶端以后,讀客戶端的副本就可以了,而且一些修改操作直接修改客戶端本地,再由終端同步到云端,剩下的操作大部分都是寫操作。寫同步當(dāng)然是越實(shí)時(shí)越好,但問題就是讀的性能肯定會(huì)有一些延遲,后面會(huì)詳細(xì)講解。
我們實(shí)現(xiàn)的是最終一致性,因?yàn)檫@不是強(qiáng)一致性的架構(gòu),很多客戶端可以關(guān)注同一個(gè)數(shù)據(jù)節(jié)點(diǎn)的變化。因?yàn)槲覀儾捎米罱K一致性,所以會(huì)導(dǎo)致多個(gè)客戶端可以同時(shí)進(jìn)行寫操作,就必然會(huì)產(chǎn)生寫沖突的問題,所以并行寫沖突的問題也要解決。
實(shí)時(shí)性是我們的特點(diǎn),這里暫時(shí)不詳細(xì)說。
最后一個(gè)是冪等操作。
這是0.1版本的架構(gòu)框圖,這個(gè)主要面向我們的初期用戶,用來驗(yàn)證我們產(chǎn)品是否被用戶認(rèn)可。這個(gè)架構(gòu)由一個(gè)接入層組成,用來維護(hù)和客戶端的長(zhǎng)連接,如果有一個(gè)請(qǐng)求過來,會(huì)產(chǎn)生數(shù)據(jù)操作到數(shù)據(jù)處理,數(shù)據(jù)處理直接寫Mysql。
Mysql這塊兒直接用了主從同步的模式來保留一定的可用性,然后再進(jìn)行數(shù)據(jù)推送。數(shù)據(jù)推送的時(shí)候,先從Redis集群中進(jìn)行l(wèi)ookup操作,這個(gè)操作的目的是尋找要修改的數(shù)據(jù)節(jié)點(diǎn)被哪些終端所關(guān)注,然后再進(jìn)行push操作。
這里的數(shù)據(jù)采用了物化路徑存儲(chǔ),也就是說,如果存的是/a/b/c的數(shù)據(jù),實(shí)際上是存/a一條/a/b一條,/a/b/c一條。
業(yè)務(wù)得到認(rèn)可之后,需要對(duì)早期用戶有一個(gè)性能的保證,所以就有了這個(gè)0.2版本的架構(gòu)框圖,把之前的Mysql改成了mongodb。使用mongodb的原因是可以動(dòng)態(tài)創(chuàng)建數(shù)據(jù)庫(kù),把用戶的數(shù)據(jù)在APP級(jí)別進(jìn)行隔離,這樣不會(huì)互相影響。同時(shí),mongodb也帶來了讀寫性能的提升。
同時(shí)我們采用了副本集多活,利用mongodb自己的副本集主掛了之后自動(dòng)切從的方案。
機(jī)槍換導(dǎo)彈的意思是之前是一次一次對(duì)數(shù)據(jù)庫(kù)進(jìn)行操作,現(xiàn)在我們做了批量的操作和合并的push。之前的操作一個(gè)push會(huì)影響多個(gè)數(shù)據(jù)節(jié)點(diǎn)發(fā)生變化,會(huì)一條一條的推給關(guān)注的終端,現(xiàn)在可以做一個(gè)合并的push。
當(dāng)我們的產(chǎn)品進(jìn)入bate版測(cè)試之后就需要面向廣大的公測(cè)用戶了,我們逐漸要面對(duì)的就是寫壓力了。因?yàn)閙ongodb的寫操作對(duì)于同一個(gè)數(shù)據(jù)表是鎖表的,所以寫是一個(gè)串行的性能問題,所以我們這里加了一個(gè)寫緩沖隊(duì)列,這是大家都會(huì)想到的解決方案。
我們這里使用了kafka。一條數(shù)據(jù)來了之后,由生產(chǎn)者進(jìn)入kafka,然后由消費(fèi)者把kafka的數(shù)據(jù)拿出來進(jìn)行批量消費(fèi),最后內(nèi)存生成一個(gè)操作樹的緩存,再批量寫入mongodb。這塊兒更類似Nagle算法,達(dá)到一定的操作量或者達(dá)到一定的超時(shí)時(shí)間后,就同步到Mysql數(shù)據(jù)庫(kù)。
可能大家有過加寫緩沖的經(jīng)驗(yàn),這時(shí)候肯定會(huì)面臨讀性能下降的問題。因?yàn)檫@時(shí)候我們?cè)谧x到mongodb的時(shí)候是一個(gè)已經(jīng)過時(shí)的數(shù)據(jù)快照,有一些操作還暫存在kafka,寫緩存隊(duì)列中,所以必須要解決這個(gè)讀不一致的問題。當(dāng)讀操作來的時(shí)候,先從mongodb中讀取到快照,然后再記錄你當(dāng)前執(zhí)行到哪,一共有哪些操作還未執(zhí)行。讀取完之后,在內(nèi)存進(jìn)行一個(gè)回放操作,拿到的就是比較新的快照版本了。
但是這里還有一個(gè)問題,在操作的過程中,還會(huì)有新的寫操作過的內(nèi)容,就算回放完,也是過期的版本。這里有點(diǎn)像redis的主從同步一樣,拿到內(nèi)存的最后版本后還有新過來的寫操作進(jìn)入push和wait隊(duì)列,先把歷史版本推給客戶端,再把之后的寫操作一次推給客戶端。最后在客戶端進(jìn)行計(jì)算達(dá)到的就是最終一致性,用戶拿到的就是最新的數(shù)據(jù)版本。
在beta版發(fā)布一段時(shí)間之后,服務(wù)器的負(fù)載是很平穩(wěn)的上升,延遲是10、11、12ms,每周是這樣一種遞增。但是突然有一天我們發(fā)現(xiàn)延遲暴增到上百ms,甚至到700ms,我們開始各種排查。但是查過之后,kafka、mongodb等等,都一切正常,最后才查到原來是因?yàn)閜ush這里需要查一次redis造成的。也就是說,我們?cè)趓edis中存的是路徑Key,路徑下面是有哪些客戶端節(jié)點(diǎn)關(guān)注了這個(gè)key,所以這里要進(jìn)行一次模糊匹配查詢,當(dāng)一個(gè)實(shí)例的redis數(shù)據(jù)量到達(dá)20w、30w條的時(shí)候,如果用模糊查詢性能會(huì)非常低,延遲會(huì)達(dá)到幾百ms。所以我們這里采用了臨時(shí)方案,用mongodb來代替redis,用mongodb加它的索引來提升模糊查詢的性能。
這里也為我們敲了個(gè)警鐘,我們需要做性能監(jiān)控,才能真正的面對(duì)用戶。后來我們就基于flume做了一套自己的性能監(jiān)控。Flume可以統(tǒng)計(jì)日志,還有對(duì)每一個(gè)系統(tǒng)延遲的調(diào)用,以及異常報(bào)警,都寫入flume,再做一個(gè)flume的后臺(tái)處理。
我們?cè)谠O(shè)計(jì)架構(gòu)的時(shí)候,總是把我們的關(guān)注點(diǎn)放在最容易發(fā)生問題的位置,而往往有時(shí)候雖然你解決了這塊兒的問題,但是由于總量上來了,還會(huì)影響一些原來不關(guān)注的地方出現(xiàn)問題,完全出乎意料。
數(shù)據(jù)同步的細(xì)節(jié)問題剛才是簡(jiǎn)單架構(gòu)框圖的介紹,現(xiàn)在是我們數(shù)據(jù)同步面臨的一些細(xì)節(jié)的介紹。
兩個(gè)客戶端同時(shí)修改本地的副本,需要考慮到數(shù)據(jù)的靜態(tài)一致性,同時(shí)還要考慮到寫隔離的問題。對(duì)于這個(gè)問題其實(shí)有兩個(gè)解決方案:一是中心化鎖機(jī)制;另外一個(gè)是進(jìn)程間協(xié)商機(jī)制。但是鎖機(jī)制會(huì)有單點(diǎn)故障問題。所以我們做了一個(gè)分布式樹形鎖機(jī)制。不過這里有一些需要注意的問題:1、tryLock和release 需要2次的交互;2、需要注意注冊(cè)Lock的有效期;3、要等待Lock超時(shí);4、最好使用動(dòng)態(tài)hash;5、連接異常時(shí)退化。
還有一些性能問題,因?yàn)槊總€(gè)App都有一個(gè)樹形鎖,所以是單進(jìn)程就算你進(jìn)行了這種操作,在理論上是會(huì)有一個(gè)吞吐量的上限的。任何操作都要先去嘗試先獲得鎖,這個(gè)操作其實(shí)是一個(gè)浪費(fèi)的操作。主要性能的點(diǎn)有兩個(gè):一個(gè)是單次push sync量比較大,可以導(dǎo)致阻塞。另外一個(gè)就是異步push sync。
因?yàn)橐陨线@些原因,一個(gè)惡心的架構(gòu)就誕生了。主要因?yàn)榭s減了write操作的過程,還有要保證云端與客戶端的一致性。整個(gè)系統(tǒng)就會(huì)太過于復(fù)雜,不確定因素太多。
但是我們做技術(shù)不能意淫。在真實(shí)的應(yīng)用場(chǎng)景中,有同一客戶端場(chǎng)景和不同客戶端場(chǎng)景。但是兩者所占的比例是不一樣的。不同客戶端的寫沖突有0.3%,同一客戶端寫沖突有4.1%。所以說,其實(shí)沖突的概率是非常小的。用上面那種方式就會(huì)有種“殺雞焉用宰牛刀”的感覺。
所以,我們提出了一個(gè)理念:讓上帝的歸上帝,野狗的歸野狗。具體到實(shí)施上就是讓用戶進(jìn)行可配置化,主要有四種方式:1、默認(rèn)不啟用;2、減少不必要的開銷;3、降低鎖粒度;4、由appld hash改進(jìn)為path hash。在這里技術(shù)的同學(xué)就要注意了,有些問題其實(shí)不需要多么厲害的架構(gòu),如果能在業(yè)務(wù)層面進(jìn)行解決,就盡量將問題在業(yè)務(wù)層面解決,不要做特別復(fù)雜的架構(gòu)去解決一些虛無(wú)縹緲的問題。
要解決這些問題,主要還是依賴寫時(shí)的樹形鎖,達(dá)到順序push的效果。如果沒有這個(gè)操作,就會(huì)出現(xiàn)客戶端數(shù)據(jù)不一致的問題,所以push順序很重要,一定要一致。
主要是需要保證同一客戶端的順序性。以“太空站”這個(gè)游戲?yàn)槔?。飛機(jī)走著走著回發(fā)生回退的現(xiàn)象,造成這個(gè)現(xiàn)象的原因,是因?yàn)榭蛻舳嗽谶M(jìn)行寫處理的時(shí)候是進(jìn)行并行處理的。這個(gè)問題很好解決,可以按照客戶端ID散列到每一個(gè)數(shù)據(jù)處理的進(jìn)程上,在數(shù)據(jù)處理進(jìn)程內(nèi)部達(dá)到一個(gè)數(shù)據(jù)寫一致的效果。進(jìn)程內(nèi)的鎖也要實(shí)現(xiàn)順序性,所以目標(biāo)又變成了解決write的性能。
第四個(gè)問題就是最終一致性的問題,剛才我們說的都是云端和被同步客戶端之間的問題。
但是這塊兒還會(huì)產(chǎn)生的問題模型是客戶端A在本地先做修改,由1修改成2,將2同步到云端以后,云端也修改成2,云端再push到其他的客戶端,對(duì)這個(gè)數(shù)據(jù)有關(guān)注的,也會(huì)修改成2,這樣就解決了最終一致性的問題。
看似很完美,但還是有漏洞。
剛才所做的這一切,只能保證云端和被同步的客戶端的數(shù)據(jù)是一致的,但是這種情況由于客戶端可以都先對(duì)本地進(jìn)行修改,客戶端A修改成2,客戶端B修改成3,在推送到云端的過程中,A進(jìn)行的修改會(huì)寫入,B進(jìn)行的修改也會(huì)寫入。最后執(zhí)行的時(shí)候如果在云端執(zhí)行的時(shí)候是以某種順序推送過來的,假設(shè)云端最后生成的是2那就是說,云端和左側(cè)是一致的,就會(huì)與另一側(cè)的節(jié)點(diǎn)產(chǎn)生不一致。
也就是說,由于并行寫,最后會(huì)有一個(gè)客戶端產(chǎn)生不一致的問題。
這里我們也沒有用到一些復(fù)雜的算法,用了一個(gè)push給自己的模型來化解這個(gè)問題,達(dá)到最終的一致性。在并行寫和推送的時(shí)候仍然推送給自己,由于推送的過程是串行的,只有推送完前面的一次,才會(huì)推送對(duì)這個(gè)節(jié)點(diǎn)的下一次改變操作。這個(gè)推送完畢以后,因?yàn)槭荰CP的,所以會(huì)按順序推送過去,那就可以認(rèn)為,在這個(gè)推送過程中,所有終端都達(dá)到了一致性。
會(huì)產(chǎn)生的問題大家也可以看到就是可能會(huì)出現(xiàn),數(shù)據(jù)由2修改成3,再修改成2。在這里我們需要對(duì)一致性問題和性能做一個(gè)取舍,當(dāng)然還是選擇為了達(dá)到實(shí)時(shí),所以采用這種比較弱的最終一致性方案。
最后一個(gè)問題,是一個(gè)原子性問題,因?yàn)槲覀兪莾绲炔僮鳎圆粫?huì)支持if then,i ++的操作。我們?cè)谶@里用了一個(gè)自旋鎖的CAS機(jī)制,在本地拉到數(shù)據(jù)之后做一個(gè)hash,這個(gè)hash和要修改的值做一個(gè)復(fù)合操作一起發(fā)到云端,而云端也對(duì)這個(gè)數(shù)據(jù)進(jìn)行一個(gè)hash,如果兩個(gè)hash是一致的,那才能認(rèn)為可以操作,才能覆蓋。如果不一致的話,重新從云端再次同步一些數(shù)據(jù)到本地產(chǎn)生一些副本,進(jìn)行上一步的操作,直到成功為止。不過我們也有一個(gè)重試次數(shù),現(xiàn)在的設(shè)置是20次。
今天的演講就到這里了,謝謝大家。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/11710.html
摘要:用小程序云開發(fā)將博客小程序常用功能一網(wǎng)打盡本文介紹博客小程序的詳情頁(yè)的功能按鈕如何實(shí)現(xiàn),具體包括評(píng)論點(diǎn)贊收藏和海報(bào)功能,這里記錄下整個(gè)實(shí)現(xiàn)過程和實(shí)際編碼中的一些坑。考慮到小程序本身的大小限制,使用的方式是最佳的。 用小程序·云開發(fā)將博客小程序常用功能一網(wǎng)打盡 本文介紹mini博客小程序的詳情頁(yè)的功能按鈕如何實(shí)現(xiàn),具體包括評(píng)論、點(diǎn)贊、收藏和海報(bào)功能,這里記錄下整個(gè)實(shí)現(xiàn)過程和實(shí)際編碼中的一...
摘要:用小程序云開發(fā)將博客小程序常用功能一網(wǎng)打盡本文介紹博客小程序的詳情頁(yè)的功能按鈕如何實(shí)現(xiàn),具體包括評(píng)論點(diǎn)贊收藏和海報(bào)功能,這里記錄下整個(gè)實(shí)現(xiàn)過程和實(shí)際編碼中的一些坑??紤]到小程序本身的大小限制,使用的方式是最佳的。 用小程序·云開發(fā)將博客小程序常用功能一網(wǎng)打盡 本文介紹mini博客小程序的詳情頁(yè)的功能按鈕如何實(shí)現(xiàn),具體包括評(píng)論、點(diǎn)贊、收藏和海報(bào)功能,這里記錄下整個(gè)實(shí)現(xiàn)過程和實(shí)際編碼中的一...
摘要:前面給大家講過一個(gè)借助小程序云開發(fā)實(shí)現(xiàn)微信支付的,但是那個(gè)操作稍微有點(diǎn)繁瑣,并且還會(huì)經(jīng)常出現(xiàn)問題,今天就給大家講一個(gè)簡(jiǎn)單的,并且借助官方支付實(shí)現(xiàn)小程序支付功能。只需要一個(gè)簡(jiǎn)單的云函數(shù),就可以輕松的實(shí)現(xiàn)微信小程序支付功能。 前面給大家講過一個(gè)借助小程序云開發(fā)實(shí)現(xiàn)微信支付的,但是那個(gè)操作稍微有點(diǎn)繁瑣,并且還會(huì)經(jīng)常出現(xiàn)問題,今天就給大家講一個(gè)簡(jiǎn)單的,并且借助官方支付api實(shí)現(xiàn)小程序支付功能。...
摘要:筆者最近涉獵了小程序相關(guān)的知識(shí),于是利用周末時(shí)間開發(fā)了一款類似于同事的小程序,深度體驗(yàn)了小程序云開發(fā)模式提供的云函數(shù)數(shù)據(jù)庫(kù)存儲(chǔ)三大能力。 筆者最近涉獵了小程序相關(guān)的知識(shí),于是利用周末時(shí)間開發(fā)了一款類似于同事的小程序,深度體驗(yàn)了小程序云開發(fā)模式提供的云函數(shù)、數(shù)據(jù)庫(kù)、存儲(chǔ)三大能力。關(guān)于云開發(fā),可參考文檔:小程序·云開發(fā)。 個(gè)人感覺云開發(fā)帶來的最大好處是鑒權(quán)流程的簡(jiǎn)化和對(duì)后端的弱化,所以像筆...
摘要:七調(diào)用云函數(shù)發(fā)送郵件我們?cè)谖募飳懸粋€(gè)按鈕,當(dāng)點(diǎn)擊這個(gè)按鈕時(shí)就發(fā)送郵件。到這里我們就完整的實(shí)現(xiàn)了微信小程序云開發(fā)使用云函數(shù)發(fā)送郵件的功能了。 先看效果圖: showImg(https://segmentfault.com/img/remote/1460000020151412); 通過上面的日志,可以看出我們是158開頭的郵箱給250開頭的郵箱發(fā)送郵件,下面是成功接收到的郵件。 sho...
閱讀 4627·2021-09-22 14:57
閱讀 569·2019-08-30 15:56
閱讀 2675·2019-08-30 15:53
閱讀 2248·2019-08-29 14:15
閱讀 1697·2019-08-28 17:54
閱讀 565·2019-08-26 13:37
閱讀 3486·2019-08-26 10:57
閱讀 1053·2019-08-26 10:32