序
本文主要聊一聊主流開(kāi)源產(chǎn)品的replication方式。
replication
replication和partition/sharding是分布式系統(tǒng)必備的兩種能力。具體詳見(jiàn)復(fù)制、分片和路由.
對(duì)于海量數(shù)據(jù)來(lái)說(shuō),replication一方面可以增加冗余,保證系統(tǒng)可用性,一方面還可以提升讀取的效率。
本文主要聚焦于replication,即假設(shè)每個(gè)node都足以存下整個(gè)副本。
replication type
按照有無(wú)leader以及l(fā)eader數(shù)目可以分為:
single leader replication
即一主多從的復(fù)制方式,由leader同步/通知follower,只有l(wèi)eader能接受寫(xiě)操作,follower只能讀不能寫(xiě)。multi leader replication
即多主多從,有多個(gè)leader分布在不同node,同時(shí)接受寫(xiě)入操作,而每個(gè)leader之間相互為follower。比較適合多數(shù)據(jù)中心的場(chǎng)景,不過(guò)對(duì)于并發(fā)寫(xiě)多數(shù)據(jù)中心沖突解決的復(fù)雜度也增加。leaderless replication
無(wú)中心的復(fù)制,不區(qū)分主從副本,任意節(jié)點(diǎn)都可以接收請(qǐng)求,然后又它去通知其他副本進(jìn)行更新。
leader-based replication way
具體詳見(jiàn)副本更新策略,主要有如下幾種
- sync replication
同步復(fù)制,這個(gè)可以保證強(qiáng)一致,不過(guò)follower多的情況下,延遲太大,一般很少使用 - async replication
異步復(fù)制,這個(gè)可能造成讀不一致,但是寫(xiě)入效率高 - semi sync replication
半同步,一般采用quorum的機(jī)制,即當(dāng)寫(xiě)入的節(jié)點(diǎn)個(gè)數(shù)滿足指定條件,即算寫(xiě)入成功,然后通過(guò)并發(fā)請(qǐng)求多個(gè)node來(lái)滿足讀取的一致性
leaderless replication way
無(wú)中心的復(fù)制,可以分為三種拓?fù)浣Y(jié)構(gòu),環(huán)形、星/樹(shù)型、網(wǎng)狀拓?fù)?/p>
replication implementation
主要分為以下幾種
statement/trigger-based replication
這種是基于數(shù)據(jù)庫(kù)的語(yǔ)句或觸發(fā)器來(lái)實(shí)現(xiàn)的復(fù)制,但是存在一定的問(wèn)題,比如一些now()/rand()/seq()等函數(shù)可能造成主從同步的不確定性,比如從節(jié)點(diǎn)的now()/rand()等執(zhí)行結(jié)果跟master不一樣。mysql5.1版本之前用的是這種,5.1+版本,當(dāng)有不確定語(yǔ)句時(shí),就切換為row-based log replicationwrite-ahead-log replication(
WAL
)
WAL是數(shù)據(jù)庫(kù)中一種高效的日志算法,對(duì)于非內(nèi)存數(shù)據(jù)庫(kù)而言,磁盤(pán)I/O操作是數(shù)據(jù)庫(kù)效率的一大瓶頸。在相同的數(shù)據(jù)量下,采用WAL日志的數(shù)據(jù)庫(kù)系統(tǒng)在事務(wù)提交時(shí),磁盤(pán)寫(xiě)操作只有傳統(tǒng)的回滾日志的一半左右,大大提高了數(shù)據(jù)庫(kù)磁盤(pán)I/O操作的效率,從而提高了數(shù)據(jù)庫(kù)的性能。
PG使用的就是這種。row-based-log replication(
logical log
)
WAL跟數(shù)據(jù)庫(kù)存儲(chǔ)引擎是耦合的,而row-based-log也稱(chēng)作logical log,是跟存儲(chǔ)引擎無(wú)關(guān)的,采用的是change data capture的方式,這個(gè)就很方便異構(gòu)數(shù)據(jù)源的數(shù)據(jù)同步。
replication帶來(lái)的問(wèn)題
replication lag
- 同步差異大
比如mongo的oplog太小,跟不上寫(xiě)入速度,造成舊的操作日志就會(huì)被丟棄,主從延遲一直增加導(dǎo)致副本同步失敗。 - 新加入node的同步
比如在線擴(kuò)容增加replication,這個(gè)時(shí)候就涉及新節(jié)點(diǎn)的node的replication問(wèn)題,一般這類(lèi)同步的方式跟正常在線節(jié)點(diǎn)的同步方式是分開(kāi)的,新的node同步到一定的時(shí)候才轉(zhuǎn)為正常的增量同步方式。
master slave failover
一般replication增加冗余常用來(lái)做master的的熱備(支持查詢)/溫備(不支持查詢)
- 當(dāng)主節(jié)點(diǎn)掛的時(shí)候,這個(gè)時(shí)候就涉及選哪個(gè)replication為主的問(wèn)題
- 當(dāng)舊的master恢復(fù)的時(shí)候,這個(gè)時(shí)候就涉及舊master與新master之間的數(shù)據(jù)差異的處理
read consistency
一旦replication支持讀取的話,那么就涉及讀的一致性問(wèn)題,一般理論上除了強(qiáng)一致外,有這幾種最終一致性:
- (1)因果一致性(Causal consistency)
即進(jìn)程A在更新完數(shù)據(jù)后通知進(jìn)程B,那么之后進(jìn)程B對(duì)該項(xiàng)數(shù)據(jù)的范圍都是進(jìn)程A更新后的最新值。 - (2)讀己之所寫(xiě)(Read your writes)
進(jìn)程A更新一項(xiàng)數(shù)據(jù)后,它自己總是能訪問(wèn)到自己更新過(guò)的最新值。 - (3)會(huì)話一致性(Session consistency)
將數(shù)據(jù)一致性框定在會(huì)話當(dāng)中,在一個(gè)會(huì)話當(dāng)中實(shí)現(xiàn)讀己之所寫(xiě)的一致性。即執(zhí)行更新后,客戶端在同一個(gè)會(huì)話中始終能讀到該項(xiàng)數(shù)據(jù)的最新值 - (4)單調(diào)讀一致性(Monotonic read consistency)
如果一個(gè)進(jìn)程從系統(tǒng)中讀取出一個(gè)數(shù)據(jù)項(xiàng)的某個(gè)值后,那么系統(tǒng)對(duì)于該進(jìn)程后續(xù)的任何數(shù)據(jù)訪問(wèn)都不應(yīng)該返回更舊的值。 - (5)單調(diào)寫(xiě)一致性(Monotoic write consistency)
一個(gè)系統(tǒng)需要保證來(lái)自同一個(gè)進(jìn)程的寫(xiě)操作被順序執(zhí)行。
讀取的話,涉及讀己所寫(xiě),因果讀(
針對(duì)操作有序
)、單調(diào)讀(不讀到舊數(shù)據(jù)
)
quorum/RWN方案解決讀沖突
write quorum
假設(shè)某份數(shù)據(jù)需要復(fù)制到3個(gè)節(jié)點(diǎn),為了保證強(qiáng)一致性,不需要所有節(jié)點(diǎn)都確認(rèn)寫(xiě)入操作,只需要其中兩個(gè)節(jié)點(diǎn)(也就是超半數(shù)節(jié)點(diǎn))確認(rèn)就可以了。在這種情況下,如果發(fā)生兩個(gè)相互沖突的寫(xiě)入操作,那么只有其中一個(gè)操作能為超過(guò)半數(shù)的節(jié)點(diǎn)所認(rèn)可,這就是寫(xiě)入仲裁(write quorum),如果用稍微正規(guī)一點(diǎn)的方式說(shuō),那就是W>N/2,這個(gè)不等式的意思是參與寫(xiě)入操作的節(jié)點(diǎn)數(shù)W,必須超過(guò)副本節(jié)點(diǎn)數(shù)N的一半,副本節(jié)點(diǎn)數(shù)又稱(chēng)為復(fù)制因子(replication factor)。
read quorum
讀取仲裁(read quorum),也就是說(shuō)想保證能夠讀到最新的數(shù)據(jù),必須與多少個(gè)節(jié)點(diǎn)聯(lián)系才行。假設(shè)寫(xiě)入操作需要兩個(gè)節(jié)點(diǎn)來(lái)確認(rèn)(W=2),那么我們至少得聯(lián)系兩個(gè)節(jié)點(diǎn),才能保證獲取到最新數(shù)據(jù)。然而,假如某些寫(xiě)入操作只被一個(gè)節(jié)點(diǎn)所確認(rèn)(W=1),那么我們就必須3個(gè)節(jié)點(diǎn)都通信一遍,才能確保獲取到的數(shù)據(jù)是最新的。一個(gè)情況下,由于寫(xiě)入操作沒(méi)有獲得足夠的節(jié)點(diǎn)支持率,所以可能會(huì)產(chǎn)生更新沖突。但是,只要從足夠數(shù)量的節(jié)點(diǎn)中讀出數(shù)據(jù),就一定能偵測(cè)出此類(lèi)沖突。因此,即使在寫(xiě)入操作不具備強(qiáng)一致性的情況下,也可以實(shí)現(xiàn)除具有強(qiáng)一致性的讀取操作來(lái)。
RWN
- R
執(zhí)行讀取操作時(shí)所需聯(lián)系的節(jié)點(diǎn)數(shù)R - W
確認(rèn)寫(xiě)入操作時(shí)所需征詢的節(jié)點(diǎn)數(shù)W - N
復(fù)制因子N
這三者之間的關(guān)系,可以用一個(gè)不等式來(lái)表述,即只有當(dāng)R+W>N的時(shí)候,才能保證讀取操作的強(qiáng)一致性。
主流開(kāi)源產(chǎn)品的replication概覽
產(chǎn)品 | 復(fù)制方式 | 實(shí)現(xiàn)方式 | 其他 |
---|---|---|---|
mysql | 主從半同步 | MySQL 5.0及之前的版本僅支持statement-based的復(fù)制,5.1+版本,當(dāng)有不確定語(yǔ)句時(shí),就切換為row-based log replication | 主從延遲處理 |
kafka | 主從ISR半同步 | leader寫(xiě)入消息并復(fù)制到所有follower,ISR中的副本寫(xiě)入成功返回ack給leader才算commit成功 | 生產(chǎn)者可以選擇是否等待ISR的ack |
elasticsearch | 主從半同步,默認(rèn)replication=sync | consistency可選的值有quorum、one和all。默認(rèn)的設(shè)置為quorum | tradelog及fsync以及refresh |
pg | 主從異步復(fù)制 | 基于Write-ahead log | archive及stream方式 |
redis | 主從異步復(fù)制 | 增量Redis Protocol(全量\增量\長(zhǎng)連接) | Sentinel failover |
mongo | 主從異步,Replica set模式 | 持久化的ring-buffer local.oplog.rs(initial_sync,steady-sync) | Arbiter選主 |
可以看見(jiàn)一些對(duì)一致性要求高的,可以采用半同步的機(jī)制,一般是基于quorum機(jī)制,像es就是基于這種機(jī)制,而kafka是采用ISR機(jī)制,二者都可以配置
其他的基本是異步復(fù)制,對(duì)于新加入的node以及recovery node的同步來(lái)說(shuō),采用不同的同步方式,新加入的一般采用全量同步,而處于正常狀態(tài)的node,一般是增量同步
kafka的ISR(In-Sync Replicas的縮寫(xiě),表示副本同步隊(duì)列
)
所有的副本(replicas)統(tǒng)稱(chēng)為Assigned Replicas,即AR。ISR是AR中的一個(gè)子集,由leader維護(hù)ISR列表,follower從leader同步數(shù)據(jù)有一些延遲,任意一個(gè)超過(guò)閾值都會(huì)把follower剔除出ISR,存入OSR(Outof-Sync Replicas)列表,新加入的follower也會(huì)先存放在OSR中。AR=ISR+OSR。
當(dāng)producer發(fā)送一條消息到broker后,leader寫(xiě)入消息并復(fù)制到所有follower。消息提交之后才被成功復(fù)制到所有的同步副本。消息復(fù)制延遲受最慢的follower限制,重要的是快速檢測(cè)慢副本,如果follower“落后”太多或者失效,leader將會(huì)把它從ISR中刪除。
由此可見(jiàn),Kafka的復(fù)制機(jī)制既不是完全的同步復(fù)制,也不是單純的異步復(fù)制。事實(shí)上,同步復(fù)制要求所有能工作的follower都復(fù)制完,這條消息才會(huì)被commit,這種復(fù)制方式極大的影響了吞吐率。而異步復(fù)制方式下,follower異步的從leader復(fù)制數(shù)據(jù),數(shù)據(jù)只要被leader寫(xiě)入log就被認(rèn)為已經(jīng)commit,這種情況下如果follower都還沒(méi)有復(fù)制完,落后于leader時(shí),突然leader宕機(jī),則會(huì)丟失數(shù)據(jù)。而Kafka的這種使用ISR的方式則很好的均衡了確保數(shù)據(jù)不丟失以及吞吐率。
es的副本一致性
es的一致性主要有兩個(gè)方面:
-
使用lucene索引機(jī)制帶來(lái)的refresh問(wèn)題
在Elasticsearch和磁盤(pán)之間是文件系統(tǒng)緩存。 在內(nèi)存索引緩沖區(qū)中的文檔會(huì)被寫(xiě)入到一個(gè)新的段中,但是這里新段會(huì)被先寫(xiě)入到文件系統(tǒng)緩存--這一步代價(jià)會(huì)比較低,稍后再被刷新到磁盤(pán)--這一步代價(jià)比較高。不過(guò)只要文件已經(jīng)在緩存中, 就可以像其它文件一樣被打開(kāi)和讀取了。
在 Elasticsearch 中,寫(xiě)入和打開(kāi)一個(gè)新段的輕量的過(guò)程叫做 refresh 。 默認(rèn)情況下每個(gè)分片會(huì)每秒自動(dòng)刷新一次。這就是為什么我們說(shuō) Elasticsearch是近實(shí)時(shí)搜索: 文檔的變化并不是立即對(duì)搜索可見(jiàn),但會(huì)在一秒之內(nèi)變?yōu)榭梢?jiàn)。
這些行為可能會(huì)對(duì)新用戶造成困惑: 他們索引了一個(gè)文檔然后嘗試搜索它,但卻沒(méi)有搜到。這個(gè)問(wèn)題的解決辦法是用 refresh API 執(zhí)行一次手動(dòng)刷新.
refresh_interval 可以在既存索引上進(jìn)行動(dòng)態(tài)更新。 在生產(chǎn)環(huán)境中,當(dāng)你正在建立一個(gè)大的新索引時(shí),可以先關(guān)閉自動(dòng)刷新,待開(kāi)始使用該索引時(shí),再把它們調(diào)回來(lái).
-
使用分片和復(fù)制帶來(lái)的副本一致性問(wèn)題(consistency:one、all、quorum)
在有副本配置的情況下,數(shù)據(jù)從發(fā)向Elasticsearch節(jié)點(diǎn),到接到Elasticsearch節(jié)點(diǎn)響應(yīng)返回,流向如下
- 1)客戶端請(qǐng)求發(fā)送給Node1節(jié)點(diǎn),這里也可以發(fā)送給其他節(jié)點(diǎn)
- 2)Node1節(jié)點(diǎn)用數(shù)據(jù)的_id計(jì)算出數(shù)據(jù)應(yīng)該存儲(chǔ)在shard0上,通過(guò)cluster state信息發(fā)現(xiàn)shard0的主分片在Node3節(jié)點(diǎn)上,Node1轉(zhuǎn)發(fā)請(qǐng)求數(shù)據(jù)給Node3,Node3完成數(shù)據(jù)的索引,索引過(guò)程在上篇博客中詳細(xì)介紹了。
- 3)Node3并行轉(zhuǎn)發(fā)數(shù)據(jù)給分配有shard0的副本分片Node1和Node2上。當(dāng)收到任一節(jié)點(diǎn)匯報(bào)副本分片數(shù)據(jù)寫(xiě)入成功以后,Node3即返回給初始的接受節(jié)點(diǎn)Node1,宣布數(shù)據(jù)寫(xiě)入成功。Node1成功返回給客戶端。
小結(jié)
不同產(chǎn)品的replication細(xì)節(jié)不盡相同,但是大的理論是一致的,對(duì)于replication除了關(guān)注上述的replication相關(guān)方式外,還需要額外關(guān)注replication相關(guān)異常場(chǎng)景,才能做到成熟應(yīng)用。