go語(yǔ)言實(shí)現(xiàn)raft協(xié)議的簡(jiǎn)單介紹

國(guó)內(nèi)重要的 Go 語(yǔ)言項(xiàng)目:TiDB 3.0 GA,穩(wěn)定性和性能大幅提升

TiDB 是 PingCAP 自主研發(fā)的開源分布式關(guān)系型數(shù)據(jù)庫(kù),具備商業(yè)級(jí)數(shù)據(jù)庫(kù)的數(shù)據(jù)可靠性,可用性,安全性等特性,支持在線彈性水平擴(kuò)展,兼容 MySQL 協(xié)議及生態(tài),創(chuàng)新性實(shí)現(xiàn) OLTP 及 OLAP 融合。

創(chuàng)新互聯(lián)基于分布式IDC數(shù)據(jù)中心構(gòu)建的平臺(tái)為眾多戶提供成都服務(wù)器托管 四川大帶寬租用 成都機(jī)柜租用 成都服務(wù)器租用。

TiDB 3.0 版本顯著提升了大規(guī)模集群的穩(wěn)定性,集群支持 150+ 存儲(chǔ)節(jié)點(diǎn),300+TB 存儲(chǔ)容量長(zhǎng)期穩(wěn)定運(yùn)行。易用性方面引入大量降低用戶運(yùn)維成本的優(yōu)化,包括引入 Information_Schema 中的多個(gè)實(shí)用系統(tǒng)視圖、EXPLAIN ANALYZE、SQL Trace 等。在性能方面,特別是 OLTP 性能方面,3.0 比 2.1 也有大幅提升,其中 TPC-C 性能提升約 4.5 倍,Sysbench 性能提升約 1.5 倍,OLAP 方面,TPC-H 50G Q15 因?qū)崿F(xiàn) View 可以執(zhí)行,至此 TPC-H 22 個(gè) Query 均可正常運(yùn)行。新功能方面增加了窗口函數(shù)、視圖(實(shí)驗(yàn)特性)、分區(qū)表、插件系統(tǒng)、悲觀鎖(實(shí)驗(yàn)特性)。

截止本文發(fā)稿時(shí) TiDB 已在 500+ 用戶的生產(chǎn)環(huán)境中長(zhǎng)期穩(wěn)定運(yùn)行,涵蓋金融、保險(xiǎn)、制造,互聯(lián)網(wǎng), 游戲 等領(lǐng)域,涉及交易、數(shù)據(jù)中臺(tái)、 歷史 庫(kù)等多個(gè)業(yè)務(wù)場(chǎng)景。不同業(yè)務(wù)場(chǎng)景對(duì)關(guān)系型數(shù)據(jù)庫(kù)的訴求可用 “百花齊放”來形容,但對(duì)關(guān)系數(shù)據(jù)庫(kù)最根本的訴求未發(fā)生任何變化,如數(shù)據(jù)可靠性,系統(tǒng)穩(wěn)定性,可擴(kuò)展性,安全性,易用性等。請(qǐng)跟隨我們的腳步梳理 TiDB 3.0 有什么樣的驚喜。

3.0 與 2.1 版本相比,顯著提升了大規(guī)模集群的穩(wěn)定性,支持單集群 150+ 存儲(chǔ)節(jié)點(diǎn),300+TB 存儲(chǔ)容量長(zhǎng)期穩(wěn)定運(yùn)行,主要的優(yōu)化點(diǎn)如下:

1. 優(yōu)化 Raft 副本之間的心跳機(jī)制,按照 Region 的活躍程度調(diào)整心跳頻率,減小冷數(shù)據(jù)對(duì)集群的負(fù)擔(dān)。

2. 熱點(diǎn)調(diào)度策略支持更多參數(shù)配置,采用更高優(yōu)先級(jí),并提升熱點(diǎn)調(diào)度的準(zhǔn)確性。

3. 優(yōu)化 PD 調(diào)度流程,提供調(diào)度限流機(jī)制,提升系統(tǒng)穩(wěn)定性。

4. 新增分布式 GC 功能,提升 GC 的性能,降低大集群 GC 時(shí)間,提升系統(tǒng)穩(wěn)定性。

眾所周知,數(shù)據(jù)庫(kù)查詢計(jì)劃的穩(wěn)定性對(duì)業(yè)務(wù)至關(guān)重要,TiDB 3.0 版本采用多種優(yōu)化手段提升查詢計(jì)劃的穩(wěn)定性,如下:

1. 新增 Fast Analyze 功能,提升收集統(tǒng)計(jì)信息的速度,降低集群資源的消耗及對(duì)業(yè)務(wù)的影響。

2. 新增 Incremental Analyze 功能,提升收集單調(diào)遞增的索引統(tǒng)計(jì)信息的速度,降低集群資源的消耗及對(duì)業(yè)務(wù)的影響。

3. 在 CM-Sketch 中新增 TopN 的統(tǒng)計(jì)信息,緩解 CM-Sketch 哈希沖突導(dǎo)致估算偏大,提升代價(jià)估算的準(zhǔn)確性,提升查詢計(jì)劃的穩(wěn)定性。

4. 引入 Skyline Pruning 框架,利用規(guī)則防止查詢計(jì)劃過度依賴統(tǒng)計(jì)信息,緩解因統(tǒng)計(jì)信息滯后導(dǎo)致選擇的查詢計(jì)劃不是最優(yōu)的情況,提升查詢計(jì)劃的穩(wěn)定性。

5. 新增 SQL Plan Management 功能,支持在查詢計(jì)劃不準(zhǔn)確時(shí)手動(dòng)綁定查詢計(jì)劃,提升查詢計(jì)劃的穩(wěn)定性。

1. OLTP

3.0 與 2.1 版本相比 Sysbench 的 Point Select,Update Index,Update Non-Index 均提升約 1.5 倍,TPC-C 性能提升約 4.5 倍。主要的優(yōu)化點(diǎn)如下:

1. TiDB 持續(xù)優(yōu)化 SQL 執(zhí)行器,包括:優(yōu)化 NOT EXISTS 子查詢轉(zhuǎn)化為 Anti Semi Join,優(yōu)化多表 Join 時(shí) Join 順序選擇等。

2. 優(yōu)化 Index Join 邏輯,擴(kuò)大 Index Join 算子的適用場(chǎng)景并提升代價(jià)估算的準(zhǔn)確性。

3. TiKV 批量接收和發(fā)送消息功能,提升寫入密集的場(chǎng)景的 TPS 約 7%,讀密集的場(chǎng)景提升約 30%。

4. TiKV 優(yōu)化內(nèi)存管理,減少 Iterator Key Bound Option 的內(nèi)存分配和拷貝,多個(gè) Column Families 共享 block cache 提升 cache 命中率等手段大幅提升性能。

5. 引入 Titan 存儲(chǔ)引擎插件,提升 Value 值超過 1KB 時(shí)性能,緩解 RocksDB 寫放大問題,減少磁盤 IO 的占用。

6. TiKV 新增多線程 Raftstore 和 Apply 功能,提升單節(jié)點(diǎn)內(nèi)可擴(kuò)展性,進(jìn)而提升單節(jié)點(diǎn)內(nèi)并發(fā)處理能力和資源利用率,降低延時(shí),大幅提升集群寫入能力。

TiDB Lightning 性能與 2019 年年初相比提升 3 倍,從 100GB/h 提升到 300GB/h,即 28MB/s 提升到 85MB/s,優(yōu)化點(diǎn),如下:

1. 提升 SQL 轉(zhuǎn)化成 KV Pairs 的性能,減少不必要的開銷。

2. 提升單表導(dǎo)入性能,單表支持批量導(dǎo)入。

3. 提升 TiKV-Importer 導(dǎo)入數(shù)據(jù)性能,支持將數(shù)據(jù)和索引分別導(dǎo)入。

4. TiKV-Importer 支持上傳 SST 文件限速功能。

RBAC(Role-Based Access Control,基于角色的權(quán)限訪問控制) 是商業(yè)系統(tǒng)中最常見的權(quán)限管理技術(shù)之一,通過 RBAC 思想可以構(gòu)建最簡(jiǎn)單“用戶-角色-權(quán)限”的訪問權(quán)限控制模型。RBAC 中用戶與角色關(guān)聯(lián),權(quán)限與角色關(guān)聯(lián),角色與權(quán)限之間一般是多對(duì)多的關(guān)系,用戶通過成為什么樣的角色獲取該角色所擁有的權(quán)限,達(dá)到簡(jiǎn)化權(quán)限管理的目的,通過此版本的迭代 RBAC 功能開發(fā)完成。

IP 白名單功能(企業(yè)版特性) :TiDB 提供基于 IP 白名單實(shí)現(xiàn)網(wǎng)絡(luò)安全訪問控制,用戶可根據(jù)實(shí)際情況配置相關(guān)的訪問策略。

Audit log 功能(企業(yè)版特性) :Audit log 記錄用戶對(duì)數(shù)據(jù)庫(kù)所執(zhí)行的操作,通過記錄 Audit log 用戶可以對(duì)數(shù)據(jù)庫(kù)進(jìn)行故障分析,行為分析,安全審計(jì)等,幫助用戶獲取數(shù)據(jù)執(zhí)行情況。

加密存儲(chǔ)(企業(yè)版特性) :TiDB 利用 RocksDB 自身加密功能,實(shí)現(xiàn)加密存儲(chǔ)的功能,保證所有寫入到磁盤的數(shù)據(jù)都經(jīng)過加密,降低數(shù)據(jù)泄露的風(fēng)險(xiǎn)。

完善權(quán)限語(yǔ)句的權(quán)限檢查 ,新增 ANALYZE,USE,SET GLOBAL,SHOW PROCESSLIST 語(yǔ)句權(quán)限檢查。

1. 新增 SQL 方式查詢慢查詢,豐富 TiDB 慢查詢?nèi)罩緝?nèi)容,如:Coprocessor 任務(wù)數(shù),平均/最長(zhǎng)/90% 執(zhí)行/等待時(shí)間,執(zhí)行/等待時(shí)間最長(zhǎng)的 TiKV 地址,簡(jiǎn)化慢查詢定位工作,提高排查慢查詢問題效率,提升產(chǎn)品易用性。

2. 新增系統(tǒng)配置項(xiàng)合法性檢查,優(yōu)化系統(tǒng)監(jiān)控項(xiàng)等,提升產(chǎn)品易用性。

3. 新增對(duì) TableReader、IndexReader 和 IndexLookupReader 算子內(nèi)存使用情況統(tǒng)計(jì)信息,提高 Query 內(nèi)存使用統(tǒng)計(jì)的準(zhǔn)確性,提升處理內(nèi)存消耗較大語(yǔ)句的效率。

4. 制定日志規(guī)范,重構(gòu)日志系統(tǒng),統(tǒng)一日志格式,方便用戶理解日志內(nèi)容,有助于通過工具對(duì)日志進(jìn)行定量分析。

5. 新增 EXPLAIN ANALYZE 功能,提升SQL 調(diào)優(yōu)的易用性。

6. 新增 SQL 語(yǔ)句 Trace 功能,方便排查問題。

7. 新增通過 unix_socket 方式連接數(shù)據(jù)庫(kù)。

8. 新增快速恢復(fù)被刪除表功能,當(dāng)誤刪除數(shù)據(jù)時(shí)可通過此功能快速恢復(fù)數(shù)據(jù)。

TiDB 3.0 新增 TiFlash 組件,解決復(fù)雜分析及 HTAP 場(chǎng)景。TiFlash 是列式存儲(chǔ)系統(tǒng),與行存儲(chǔ)系統(tǒng)實(shí)時(shí)同步,具備低延時(shí),高性能,事務(wù)一致性讀等特性。 通過 Raft 協(xié)議從 TiKV 中實(shí)時(shí)同步行存數(shù)據(jù)并轉(zhuǎn)化成列存儲(chǔ)格式持久化到一組獨(dú)立的節(jié)點(diǎn),解決行列混合存儲(chǔ)以及資源隔離性問題。TiFlash 可用作行存儲(chǔ)系統(tǒng)(TiKV)實(shí)時(shí)鏡像,實(shí)時(shí)鏡像可獨(dú)立于行存儲(chǔ)系統(tǒng),將行存儲(chǔ)及列存儲(chǔ)從物理隔離開,提供完善的資源隔離方案,HTAP 場(chǎng)景最優(yōu)推薦方案;亦可用作行存儲(chǔ)表的索引,配合行存儲(chǔ)對(duì)外提供智能的 OLAP 服務(wù),提升約 10 倍復(fù)雜的混合查詢的性能。

TiFlash 目前處于 Beta 階段,計(jì)劃 2019 年 12 月 31 日之前 GA,歡迎大家申請(qǐng)?jiān)囉谩?/p>

未來我們會(huì)繼續(xù)投入到系統(tǒng)穩(wěn)定性,易用性,性能,彈性擴(kuò)展方面,向用戶提供極致的彈性伸縮能力,極致的性能體驗(yàn),極致的用戶體驗(yàn)。

穩(wěn)定性方面 V4.0 版本將繼續(xù)完善 V3.0 未 GA 的重大特性,例如:悲觀事務(wù)模型,View,Table Partition,Titan 行存儲(chǔ)引擎,TiFlash 列存儲(chǔ)引擎;引入近似物理備份恢復(fù)解決分布數(shù)據(jù)庫(kù)備份恢復(fù)難題;優(yōu)化 PD 調(diào)度功能等。

性能方面 V4.0 版本將繼續(xù)優(yōu)化事務(wù)處理流程,減少事務(wù)資源消耗,提升性能,例如:1PC,省去獲取 commit ts 操作等。

彈性擴(kuò)展方面,PD 將提供彈性擴(kuò)展所需的元信息供外部系統(tǒng)調(diào)用,外部系統(tǒng)可根據(jù)元信息及負(fù)載情況動(dòng)態(tài)伸縮集群規(guī)模,達(dá)成節(jié)省成本的目標(biāo)。

我們相信戰(zhàn)勝“未知”最好的武器就是社區(qū)的力量,基礎(chǔ)軟件需要堅(jiān)定地走開源路線。截止發(fā)稿我們已經(jīng)完成 41 篇源碼閱讀文章。TiDB 開源社區(qū)總計(jì) 265 位 Contributor,6 位 Committer,在這里我們對(duì)社區(qū)貢獻(xiàn)者表示由衷的感謝,希望更多志同道合的人能加入進(jìn)來,也希望大家在 TiDB 這個(gè)開源社區(qū)能夠有所收獲。

TiDB 3.0 GA Release Notes:

Raft協(xié)議簡(jiǎn)析

一致性算法之所以可以保證在有節(jié)點(diǎn)掛掉時(shí)也能夠繼續(xù)服務(wù), 就是因?yàn)橛蠷eplicated state machines的存在。 在分布式系統(tǒng)中, 有兩種方式來實(shí)現(xiàn)這個(gè)復(fù)制狀態(tài)機(jī)

一致性算法一般有一下特點(diǎn):

Raft算法首先會(huì)選舉一個(gè)leader, 然后又leader來管理replicated log。 leader會(huì)從client處接收請(qǐng)求, 然后轉(zhuǎn)發(fā)給別的節(jié)點(diǎn), 并且告訴這些節(jié)點(diǎn)什么時(shí)候可以把這些請(qǐng)求應(yīng)用在狀態(tài)機(jī)上(落地)。 當(dāng)一個(gè)leader掛了的時(shí)候, 會(huì)馬上選舉出一個(gè)新的leader。 由上可知, Raft將一致性問題拆分成了3個(gè)獨(dú)立的子問題:

Logs 組織的形式如圖所示。 當(dāng)leader收到一個(gè)Log entry時(shí), 每個(gè)log entry都會(huì)存儲(chǔ)一個(gè)帶有term number的state machine命令。 Term number是用來探測(cè)log之間的不一致性并且保證Figure3的一些特性。 每個(gè)log也還帶有他們?cè)趌og里的索引數(shù)字。

當(dāng)leader認(rèn)為這個(gè)entry是可以成功應(yīng)用到狀態(tài)機(jī)上, 那么這個(gè)entry就被成為commited。Raft保證所有commited entry最終都會(huì)被應(yīng)用到所有的節(jié)點(diǎn)上。

當(dāng)一個(gè)entry被成功復(fù)制到半數(shù)以上的節(jié)點(diǎn)后, 這個(gè)log就可以認(rèn)為成功寫入了。并且會(huì)將leader之前的寫入也視為commit。

設(shè)計(jì)Raft日志機(jī)制不僅簡(jiǎn)化了系統(tǒng)的行為, 也保證了正確性。 保證了Figure3的以下特性

1. 當(dāng)不同log中的兩個(gè)entry有相同的term和index, 那么這兩個(gè)entry就是相同的

2. 當(dāng)不同log中的兩個(gè)entry有相同的term和index, 那么這兩個(gè)entry之前的所有entry也都是相同的

當(dāng)leader發(fā)現(xiàn)follower的log跟自己的不同時(shí), 他會(huì)針對(duì)每個(gè)follower維護(hù)一個(gè)nextIndex。 這個(gè)nextIndex就是Leader下次會(huì)發(fā)第幾個(gè)entry給這個(gè)follower。 而follower也會(huì)拒絕這個(gè)append entry的rpc。

Safety

如Figure 4所示, 如果term T commit的數(shù)據(jù)在之后的term U丟失了, 那么

1. 這個(gè)log一定不在Leader-U的log中(因?yàn)閘eader不會(huì)覆蓋舊數(shù)據(jù))

2. Leader-T把這個(gè)日志復(fù)制給了大多數(shù)節(jié)點(diǎn)并且Leader-U收到了大多數(shù)節(jié)點(diǎn)的投票, 所以至少有一個(gè)節(jié)點(diǎn)是既收到了這個(gè)entry并且又給Leader-U投票了

3. 那么這個(gè)投票的節(jié)點(diǎn)也一定收到了所有Leader-T的commited entries

4. 因?yàn)檫@個(gè)投票節(jié)點(diǎn)把票投給了U, 那么說明Leader-U也至少有所有這個(gè)節(jié)點(diǎn)的entries

由此可知, 如果投票節(jié)點(diǎn)和Leader-U 共享了之前的log, 那么Leader-U肯定會(huì)有所有投票節(jié)點(diǎn)的entry。 另外, Leader-U的last-log-term必須比T大, 而投票節(jié)點(diǎn)的term至少是T。之前term的leader在復(fù)制entry給Leader-U時(shí)也必須包含了之前的這個(gè)entry。 投票節(jié)點(diǎn)和之前term的leader都會(huì)有這個(gè)entry, 所以Leader-U也不會(huì)沒有這個(gè)entry。可下結(jié)論所有大于T的term的leader絕對(duì)包含了所有term T commited的entries。

Raft協(xié)議

raft協(xié)議是一個(gè)共識(shí)算法,主要包括leader election,log replication,safety三個(gè)關(guān)鍵部分,另外還包括membership changes和snapshot。

復(fù)制狀態(tài)機(jī)是分布式系統(tǒng)中解決fault tolerance問題的常用手段。raft通過log replication來保證集群的多個(gè)server,會(huì)有同樣的數(shù)據(jù)輸入到各自的狀態(tài)機(jī)。如圖1所示。

關(guān)鍵術(shù)語(yǔ):

Apply:將entry輸入到狀態(tài)機(jī)

committed:entry可以被安全的Apply到狀態(tài)機(jī),一般情況下entry被同步到集群的大多數(shù)節(jié)點(diǎn)上時(shí),就可以認(rèn)為是committed(有特殊情況)。

每個(gè)server都有一個(gè)log,log中包含一系列的entry(entry中有相應(yīng)的命令,即客戶端請(qǐng)求),狀態(tài)機(jī)按照log中的順序執(zhí)行這些命令。

如果每個(gè)server輸入狀態(tài)機(jī)的數(shù)據(jù)相同,狀態(tài)機(jī)產(chǎn)生的結(jié)果也是相同的。因此共識(shí)算法的目的就是保證多個(gè)server的log一致。

leader上的consensus module接收到客戶端的命令,將這些命令作為entry添加到log中,并且和其他follower上的consensus module通信,將log entry同步到其他follower,以確保多個(gè)server之間日志文件的最終一致。

當(dāng)達(dá)到一定條件,即該條entry committed時(shí),leader會(huì)將命令輸入狀態(tài)機(jī),并將輸出返回給客戶端,同時(shí)通過心跳通知其他follower可以Apply該entry。

共識(shí)算法有以下特點(diǎn):

1.safety,在所有非拜占庭條件下(包括網(wǎng)絡(luò)延遲,分區(qū),丟包,duplication,reordering等),不會(huì)返回錯(cuò)誤的結(jié)果

2.大部分節(jié)點(diǎn)正常話,系統(tǒng)就可以正常工作

3.不依靠物理時(shí)鐘來確保日志的一致,錯(cuò)誤的物理時(shí)鐘和消息延遲最多會(huì)造成可用性問題

4.集群中的大多數(shù)節(jié)點(diǎn)在一輪rpc調(diào)用中正常響應(yīng)的話,一個(gè)客戶端的請(qǐng)求就會(huì)被正常返回,不會(huì)受部分慢節(jié)點(diǎn)的影響

任何時(shí)刻,一個(gè)server處于以下三個(gè)狀態(tài)之一:leader,follower,candidate。

一般情況下,有1個(gè)leader,其他節(jié)點(diǎn)都是follower,follower是被動(dòng)的,不會(huì)發(fā)送請(qǐng)求,只會(huì)響應(yīng)leader和candidate的請(qǐng)求。

leader處理所有客戶端的請(qǐng)求(如果客戶端請(qǐng)求了follower,follower將請(qǐng)求重定向到leader)。

candidate狀態(tài)用于選舉一個(gè)新的leader,狀態(tài)轉(zhuǎn)換如下圖

raft將物理時(shí)間分隔為一個(gè)個(gè)的任意長(zhǎng)度的term,term是連續(xù)的。

每個(gè)term從election開始,一個(gè)或者多個(gè)candidate嘗試競(jìng)選為leader,如果一個(gè)candidate贏得了選舉,就會(huì)成為term的余下時(shí)間內(nèi)的leader。

一些情況下,會(huì)產(chǎn)生split vote,term會(huì)以沒有l(wèi)eader的狀態(tài)結(jié)束,開始新一輪的term以及選舉

raft確保一個(gè)term中最多只會(huì)有一個(gè)leader。

term是邏輯時(shí)鐘,每個(gè)server存儲(chǔ)一個(gè)current term number,current term number隨著時(shí)間單調(diào)遞增,當(dāng)節(jié)點(diǎn)之間通信時(shí),會(huì)交換current term number,

如果一個(gè)server發(fā)現(xiàn)自己的current term number小于其他節(jié)點(diǎn)的,該server會(huì)將自己的term更新為更大的term,

如果一個(gè)candidate或者leader發(fā)現(xiàn)有節(jié)點(diǎn)的term大于自己的term,就會(huì)轉(zhuǎn)變?yōu)閒ollower(有特殊情況),

如果一個(gè)節(jié)點(diǎn)接收到一個(gè)有著過期term number的請(qǐng)求,則會(huì)拒絕這個(gè)請(qǐng)求。

raft的server之間使用RPC通信,主要為兩種類型的RPC,

RequestVote RPC:用于candidate選舉

AppendEntries RPC:用于leader發(fā)送log entry給follower,或者心跳

另外還有一種InstallSnapshot RPC,用于傳輸snapshot

raft協(xié)議首先需要選舉一個(gè)唯一的leader,leader接受客戶端的命令,將這些命令復(fù)制到其他follower,通知follower什么時(shí)候可以將這些日志輸入到狀態(tài)機(jī)。data flow是單向的,從leader到follower。

raft使用心跳機(jī)制觸發(fā)leader election,當(dāng)一個(gè)server start up,起始狀態(tài)是follower,只要收到leader和candidate的正確RPC請(qǐng)求,server就會(huì)保持follower的狀態(tài)。

如果follower在一定時(shí)間內(nèi)(election timeout)沒有收到心跳,follower會(huì)認(rèn)為當(dāng)前沒有l(wèi)eader,并開始競(jìng)選。

開始競(jìng)選時(shí),follower增加自己的current term并將狀態(tài)轉(zhuǎn)換為candidate,然后會(huì)選舉自己并發(fā)送RequestVote RPC請(qǐng)求給集群中的其他server。

一個(gè)candidate會(huì)保持自己的狀態(tài)直到下面三種情況之一發(fā)生:

1.贏得選舉

一個(gè)candidate在接收到集群中大多數(shù)節(jié)點(diǎn)對(duì)當(dāng)前term的投票之后,贏得選舉。每個(gè)server在一個(gè)term中,最多只會(huì)給一個(gè)candidate投票,first-come-first-served,

2.其他節(jié)點(diǎn)成為leader

如果candidate收到其他節(jié)點(diǎn)的RPC請(qǐng)求,而且請(qǐng)求中的term大于等于candidate的current term,candidate會(huì)認(rèn)為已經(jīng)選出leader,并返回到follower狀態(tài)。

如果RPC請(qǐng)求中的term小于candidate的current term,candidate會(huì)拒絕該RPC請(qǐng)求。

3.一定時(shí)間內(nèi)(election timeout)沒有選舉出leader

每個(gè)candidate都會(huì)time out,并且增加自己的term,開始新一輪的選舉

election timeout是在一個(gè)固定范圍內(nèi)(例如150ms-300ms內(nèi))隨機(jī)的

上述機(jī)制保證在一個(gè)term中,只有一個(gè)candidate會(huì)成為leader,當(dāng)一個(gè)candidate成為leader,它會(huì)發(fā)送心跳信息給所有其他的節(jié)點(diǎn)。

當(dāng)一個(gè)leader被選舉出來之后,client發(fā)送請(qǐng)求給leader,leader將將請(qǐng)求作為一個(gè)新的entry添加到log中,然后并行的發(fā)送AppendEntries RPC請(qǐng)求(攜帶該entry)給follower。

leader判斷當(dāng)前是否可以安全地將entry apply到狀態(tài)機(jī)中,此時(shí)該entry被叫做committed。然后leader將請(qǐng)求Apply到狀態(tài)機(jī),并返回執(zhí)行結(jié)果。

log entry中會(huì)保存接受到entry時(shí)的term,以及一個(gè)用于標(biāo)記log entry位置的index。

raft保證committed entries是持久化的,并最終會(huì)被所有的狀態(tài)機(jī)執(zhí)行。

當(dāng)一個(gè)entry被leader replicate到集群中的大多數(shù)節(jié)點(diǎn)上時(shí),該entry就是committed。

如果某條entry是committed的,該entry之前的entry也都是committed的,包括之前的leader創(chuàng)建的entry。

leader會(huì)記錄committed的日志的最高index,并將該index包含在之后的AppendEntries RPC中(包括 heartbeats),

follower知道某個(gè)entry是committed,就會(huì)將該entry apply到狀態(tài)機(jī)中。

AppendEntries Consistency Check:

當(dāng)發(fā)送一個(gè)AppendEntries RPC,leader將新entries之前最近的log entry的index和term包含在RPC請(qǐng)求中。如果follower發(fā)現(xiàn)自己的log中沒有該index和term的entry,就會(huì)拒絕新的entries。

類似于一個(gè)歸納的過程,最初的空的log滿足Log Matching Property,當(dāng)有新的log entry時(shí),consistency check同樣保證了新的log entry滿足Log Matching Property。

這樣,當(dāng)AppendEntries請(qǐng)求返回成功的響應(yīng)時(shí),leader就知道follower的log在new entries之前的部分和自己的log一樣。

一個(gè)新的leader被選舉出來之后,follower的log可能和新的leader不一樣,follower可能有l(wèi)eader沒有的entry,也可能有老的leader沒有commit的entry。

為了讓follower的log和leader的完全一致,leader需要找到follower的log和自己的log分叉的地方,刪除follower在分叉點(diǎn)之后的log entry,然后leader向follower發(fā)送自己在分叉點(diǎn)之后的log entry。

上述操作通過AppendEntries RPC來實(shí)現(xiàn),leader會(huì)記錄每個(gè)follower的nextIndex,即leader應(yīng)該發(fā)送給這個(gè)follower的下一個(gè)log entry的index。

如果follower的log和leader的不一樣,AppendEntries RPC會(huì)失敗,leader減小nextIndex并重試。

如果需要,這個(gè)協(xié)議也可以優(yōu)化,如果AppendEntries RPC失敗,follower可以返回沖突的term,以及該term的第一個(gè)index。這樣原來一個(gè)不同的entry就需要一個(gè)AppendEntries請(qǐng)求,現(xiàn)在一個(gè)term需要一個(gè)AppendEntries請(qǐng)求。

這樣多個(gè)節(jié)點(diǎn)之間的日志就會(huì)收斂一致。同時(shí),leader從不會(huì)覆蓋或者刪除自己的log entry,符合Leader Append-Only Property。

上述部分并不能完全保證每個(gè)狀態(tài)機(jī)以相同的順序執(zhí)行相同的命令。

例如,一個(gè)follower可能在當(dāng)前l(fā)eader commit一些log entry的時(shí)候不可用,然后該follower被選舉為新的leader后,就可能覆蓋之前committed的日志,從而造成不同的狀態(tài)機(jī)執(zhí)行了不同的命令。

下面討論leader election的限制,這些限制能保證任何term的leader都會(huì)包含之前term中committed的log entry。

RequestVote RPC請(qǐng)求包含candidate的log,如果voter的log比candidate的log更加up-to-date,voter會(huì)拒絕這次投票。

up-to-date:兩個(gè)log,如果term不同,term更大的更新,如果term相同,日志更長(zhǎng)的更新

一個(gè)leader不能立即判斷出一個(gè)之前term的entry是否應(yīng)該committed,即使該entry被存儲(chǔ)到了大多數(shù)節(jié)點(diǎn)上。

(a)S1是leader,寫入一條命令,index是2

(b)S1 crash,S5選舉為leader,寫入一條命令,index是3

(c)S5 crash,S1選舉為leader,寫入一條命令,index是4,并將index為2的log entry同步到S3,commit和apply index為2的log entry

(d)S1 crash,S5選舉為leader,會(huì)覆蓋掉index2,造成多個(gè)server的狀態(tài)機(jī)apply不一樣的log entry

因此,raft不會(huì)因?yàn)橹皌erm的log entry被存儲(chǔ)到了大多數(shù)節(jié)點(diǎn)上,就將該entry commit(raft never commits log entries from pervious terms by counting replicas),只有當(dāng)前term的log entry被存儲(chǔ)到大多數(shù)節(jié)點(diǎn)上時(shí),才會(huì)判斷該entry為commit

(only log entries from the leader's current term are committed by counting replicas)。這樣,由于Log Matching Property,所有之前的entries都會(huì)間接地被commit掉。

raft使用two-phase的方案來處理configuration change,集群首先會(huì)切換到一個(gè)名叫joint consensus的中間狀態(tài),一旦joint consensus被committed了,集群就會(huì)使用新的configuration。

joint consensus將老的和新的configuration結(jié)合在一起:

集群configuration也是以log entry的方式存儲(chǔ)和同步到其他server上。

當(dāng)leader接收到configuration從C-old變?yōu)镃-new的請(qǐng)求之后,將C-old,new的entry存儲(chǔ)到log中,并同步到其他server上。

follower接收到entry后,無論該entry是否已經(jīng)committed,都會(huì)使用entry包含的configuration替換當(dāng)前的configuration。

如果leader crash,新的leader的configuration只可能是C-old或者C-old,new。

C-old,new被committed之后,leader創(chuàng)建一條C-new的entry,并同步到其他server上。

follower接收到該entry之后,無論該entry是否已經(jīng)committed,都會(huì)使用C-new替換之前的configuration。

當(dāng)C-new被committed之后,C-old中的節(jié)點(diǎn)就可以被shut down。

上述方案需要解決三個(gè)問題:

1.新加入的server需要很長(zhǎng)時(shí)間才能追上leader,在這段時(shí)間內(nèi)無法committed,為此raft引入了non-voting 成員

2.老的leader可能不在新的configuration中。為此,leader在C-new committed之后,leader需要變成follower

3.removed servers可能會(huì)影響集群。這些節(jié)點(diǎn)不會(huì)接收到心跳,然后time out,然后開始新一輪的選舉。這會(huì)造成當(dāng)前的leader變成follower,然后重新選舉leader。上述過程會(huì)不斷重復(fù)。

為此,server需要忽略RequestVote RPC,如果當(dāng)前的leader沒有time out。

snapshotting是log compacting的最簡(jiǎn)單的辦法,狀態(tài)機(jī)將當(dāng)前系統(tǒng)狀態(tài)被寫進(jìn)snapshot,之前的log entry會(huì)被刪除。

每個(gè)server會(huì)獨(dú)立的take snapshot,snapshot會(huì)包含log中已經(jīng)committed的log entry。

snapshot中會(huì)包含少量的元數(shù)據(jù),

last included index:狀態(tài)機(jī)apply的最后一個(gè)log entry,也就是snapshot替換掉的最后一個(gè)log entry 的index。

last included term:上述entry的term

元數(shù)據(jù)用于snapshot之后的第一個(gè)log entry的AppendEntries consistency check,由于該entry需要之前的log的index和term。

元數(shù)據(jù)也包含最近的configuration。

對(duì)于一個(gè)剛加進(jìn)集群的server,leader使用InstallSnapshot RPC發(fā)送snapshot給follower。

raft需要把所有的請(qǐng)求發(fā)送給leader,當(dāng)一個(gè)client start,client連接集群中的任意一個(gè)節(jié)點(diǎn),如果該節(jié)點(diǎn)不是leader,則會(huì)拒絕client的請(qǐng)求,并返回leader的信息(AppendEntries請(qǐng)求包含了leader的網(wǎng)絡(luò)地址)。

如果leader crash,client請(qǐng)求會(huì)timeout,然后隨機(jī)選擇一個(gè)節(jié)點(diǎn)繼續(xù)重試。

raft協(xié)議需要實(shí)現(xiàn)線性語(yǔ)義(linearizable semantics),每個(gè)操作會(huì)且只會(huì)執(zhí)行一次(exactly once),但是僅靠之前提到的幾點(diǎn),raft協(xié)議的可能會(huì)讓一個(gè)命令執(zhí)行多次。

例如,leader在commit一個(gè)log entry,但是還沒有來得及返回給client之后,就crash掉,client會(huì)在新的leader上重復(fù)發(fā)送相同的請(qǐng)求,造成該請(qǐng)求執(zhí)行兩次。

解決方法是client給每個(gè)命令一個(gè)序列號(hào),狀態(tài)機(jī)記錄每個(gè)client最近執(zhí)行的序列號(hào)。如果狀態(tài)機(jī)收到一個(gè)命令,該命令的序列號(hào)是之前執(zhí)行過的,就立即返回而不再執(zhí)行該命令。

只讀操作可能會(huì)讀到過期的數(shù)據(jù)。因?yàn)閏lient訪問一個(gè)leader時(shí),集群中選舉出了其他leader,該leader馬上就會(huì)變成follower。linear semantics不能返回過期數(shù)據(jù)。

raft的解決方案分兩步,

首先,一個(gè)leader必須確認(rèn)哪些entry是committed,Leader Completeness Property保證一個(gè)leader擁有所有committed的entry,但是在term的開始階段,leader并不知道哪些是已經(jīng)committed的。因此,leader需要在term的開始,先commit一個(gè)no-op entry。

然后,leader必須檢查當(dāng)前是否有其他leader被選舉出來,將要取代自己的leader位置。raft在返回read-only請(qǐng)求的響應(yīng)之前,需要和集群中的大多數(shù)節(jié)點(diǎn)發(fā)送心跳。

NET中有沒有類似ZooKeeper這樣的分布式服務(wù)框架

本文是JasonWilder對(duì)于常見的服務(wù)發(fā)現(xiàn)項(xiàng)目Zookeeper,Doozer,Etcd所寫的一篇博客,其原文地址如下:Open-SourceServiceDiscovery。服務(wù)發(fā)現(xiàn)是大多數(shù)分布式系統(tǒng)以及面向服務(wù)架構(gòu)(SOA)的一個(gè)核心組成部分。這個(gè)難題,簡(jiǎn)單來說,可以認(rèn)為是:當(dāng)一項(xiàng)服務(wù)存在于多個(gè)主機(jī)節(jié)點(diǎn)上時(shí),client端如何決策獲取相應(yīng)正確的IP和port。在傳統(tǒng)情況下,當(dāng)出現(xiàn)服務(wù)存在于多個(gè)主機(jī)節(jié)點(diǎn)上時(shí),都會(huì)使用靜態(tài)配置的方法來實(shí)現(xiàn)服務(wù)信息的注冊(cè)。但是當(dāng)大型系統(tǒng)中,需要部署服務(wù)的時(shí)候,事情就顯得復(fù)雜得多。在一個(gè)實(shí)時(shí)的系統(tǒng)中,由于自動(dòng)或者人工的服務(wù)擴(kuò)展,或者服務(wù)的新添加部署,還有主機(jī)的宕機(jī)或者被替換,服務(wù)的location信息可能會(huì)很頻繁的變化。在這樣的場(chǎng)景下,為了避免不必要的服務(wù)中斷,動(dòng)態(tài)的服務(wù)注冊(cè)和發(fā)現(xiàn)就顯得尤為重要。關(guān)于服務(wù)發(fā)現(xiàn)的話題,已經(jīng)很多次被人所提及,而且也的確不斷的在發(fā)展。現(xiàn)在,筆者介紹一下該領(lǐng)域內(nèi)一些open-source或者被經(jīng)常被世人廣泛討論的解決方案,嘗試?yán)斫馑鼈兊降资侨绾喂ぷ鞯摹L貏e的是,我們會(huì)較為專注于每一個(gè)解決方案的一致性算法,到底是強(qiáng)一致性,還是弱一致性;運(yùn)行時(shí)依賴;client的集成選擇;以后最后這些特性的折中情況。本文首先從幾個(gè)強(qiáng)一致性的項(xiàng)目于開始,比如Zookeeper,Doozer,Etcd,這些項(xiàng)目主要用于服務(wù)間的協(xié)調(diào),同時(shí)又可用于服務(wù)的注冊(cè)。隨后,本文將討論一些在服務(wù)注冊(cè)以及發(fā)現(xiàn)方面比較有意思的項(xiàng)目,比如:Airbnb的SmartStack,Netflix的Eureka,Bitly的NSQ,Serf,SpotifyandDNS,最后是SkyDNS。問題陳述在定位服務(wù)的時(shí)候,其實(shí)會(huì)有兩個(gè)方面的問題:服務(wù)注冊(cè)(ServiceRegistration)和服務(wù)發(fā)現(xiàn)(ServiceDiscovery)。服務(wù)注冊(cè)——一個(gè)服務(wù)將其位置信息在中心注冊(cè)節(jié)點(diǎn)注冊(cè)的過程。該服務(wù)一般會(huì)將它的主機(jī)IP地址以及端口號(hào)進(jìn)行注冊(cè),有時(shí)也會(huì)有服務(wù)訪問的認(rèn)證信息,使用協(xié)議,版本號(hào),以及關(guān)于環(huán)境的一些細(xì)節(jié)信息。服務(wù)發(fā)現(xiàn)——client端的應(yīng)用實(shí)例查詢中心注冊(cè)節(jié)點(diǎn)以獲知服務(wù)位置的過程。每一個(gè)服務(wù)的服務(wù)注冊(cè)以及服務(wù)發(fā)現(xiàn),都需要考慮一些關(guān)于開發(fā)以及運(yùn)營(yíng)方面的問題:監(jiān)控——當(dāng)一個(gè)已注冊(cè)完畢的服務(wù)失效的時(shí)候,如何處理。一些情況下,在一個(gè)設(shè)定的超時(shí)定時(shí)(timeout)后,該服務(wù)立即被一個(gè)其他的進(jìn)程在中心注冊(cè)節(jié)點(diǎn)處注銷。這種情況下,服務(wù)通常需要執(zhí)行一個(gè)心跳機(jī)制,來確保自身的存活狀態(tài);而客戶端必然需要能夠可靠處理失效的服務(wù)。負(fù)載均衡——如果多個(gè)相同地位的服務(wù)都注冊(cè)完畢,如何在這些服務(wù)之間均衡所有client的請(qǐng)求負(fù)載?如果有一個(gè)master節(jié)點(diǎn)的話,是否可以正確處理client訪問的服務(wù)的位置。集成方式——信息注冊(cè)節(jié)點(diǎn)是否需要提供一些語(yǔ)言綁定的支持,比如說,只支持Java?集成的過程是否需要將注冊(cè)過程以及發(fā)現(xiàn)過程的代碼嵌入到你的應(yīng)用程序中,或者使用一個(gè)類似于集成助手的進(jìn)程?運(yùn)行時(shí)依賴——是否需要JVM,ruby或者其他在你的環(huán)境中并不兼容的運(yùn)行時(shí)?可用性考慮——如果系統(tǒng)失去一個(gè)節(jié)點(diǎn)的話,是否還能正常工作?系統(tǒng)是否可以實(shí)時(shí)更新或升級(jí),而不造成任何系統(tǒng)的癱瘓?既然集群的信息注冊(cè)節(jié)點(diǎn)是架構(gòu)中的中心部分,那該模塊是否會(huì)存在單點(diǎn)故障問題?強(qiáng)一致性的Registries首先介紹的三個(gè)服務(wù)注冊(cè)系統(tǒng)都采用了強(qiáng)一致性協(xié)議,實(shí)際上為達(dá)到通用的效果,使用了一致性的數(shù)據(jù)存儲(chǔ)。盡管我們把它們看作服務(wù)的注冊(cè)系統(tǒng),其實(shí)它們還可以用于協(xié)調(diào)服務(wù)來協(xié)助leader選舉,以及在一個(gè)分布式clients的集合中做centralizedlocking。ZookeeperZookeeper是一個(gè)集中式的服務(wù),該服務(wù)可以維護(hù)服務(wù)配置信息,命名空間,提供分布式的同步,以及提供組化服務(wù)。Zookeeper是由Java語(yǔ)言實(shí)現(xiàn),實(shí)現(xiàn)了強(qiáng)一致性(CP),并且是使用Zab協(xié)議在ensemble集群之間協(xié)調(diào)服務(wù)信息的變化。Zookeeper在ensemble集群中運(yùn)行3個(gè),5個(gè)或者7個(gè)成員。眾多client端為了可以訪問ensemble,需要使用綁定特定的語(yǔ)言。這種訪問形式被顯性的嵌入到了client的應(yīng)用實(shí)例以及服務(wù)中。服務(wù)注冊(cè)的實(shí)現(xiàn)主要是通過命令空間(namespace)下的ephemeralnodes。ephemeralnodes只有在client建立連接后才存在。當(dāng)client所在節(jié)點(diǎn)啟動(dòng)之后,該client端會(huì)使用一個(gè)后臺(tái)進(jìn)程獲取client的位置信息,并完成自身的注冊(cè)。如果該client失效或者失去連接的時(shí)候,該ephemeralnode就從樹中消息。服務(wù)發(fā)現(xiàn)是通過列舉以及查看具體服務(wù)的命名空間來完成的。Client端收到目前所有注冊(cè)服務(wù)的信息,無論一個(gè)服務(wù)是否不可用或者系統(tǒng)新添加了一個(gè)同類的服務(wù)。Client端同時(shí)也需要自行處理所有的負(fù)載均衡工作,以及服務(wù)的失效工作。Zookeeper的API用起來可能并沒有那么方便,因?yàn)檎Z(yǔ)言的綁定之間可能會(huì)造成一些細(xì)小的差異。如果使用的是基于JVM的語(yǔ)言的話,CuratorServiceDiscoveryExtension可能會(huì)對(duì)你有幫助。由于Zookeeper是一個(gè)CP強(qiáng)一致性的系統(tǒng),因此當(dāng)網(wǎng)絡(luò)分區(qū)(Partition)出故障的時(shí)候,你的部分系統(tǒng)可能將出出現(xiàn)不能注冊(cè)的情況,也可能出現(xiàn)不能找到已存在的注冊(cè)信息,即使它們可能在Partition出現(xiàn)期間仍然正常工作。特殊的是,在任何一個(gè)non-quorum端,任何讀寫都會(huì)返回一個(gè)錯(cuò)誤信息。DoozerDoozer是一個(gè)一致的分布式數(shù)據(jù)存儲(chǔ)系統(tǒng),Go語(yǔ)言實(shí)現(xiàn),通過Paxos算法來實(shí)現(xiàn)共識(shí)的強(qiáng)一致性系統(tǒng)。這個(gè)項(xiàng)目開展了數(shù)年之后,停滯了一段時(shí)間,而且現(xiàn)在也關(guān)閉了一些fork數(shù),使得fork數(shù)降至160。.不幸的是,現(xiàn)在很難知道該項(xiàng)目的實(shí)際發(fā)展?fàn)顟B(tài),以及它是否適合使用于生產(chǎn)環(huán)境。Doozer在集群中運(yùn)行3,5或者7個(gè)節(jié)點(diǎn)。和Zookeeper類似,Client端為了訪問集群,需要在自身的應(yīng)用或者服務(wù)中使用特殊的語(yǔ)言綁定。Doozer的服務(wù)注冊(cè)就沒有Zookeeper這么直接,因?yàn)镈oozer沒有那些ephemeralnode的概念。一個(gè)服務(wù)可以在一條路徑下注冊(cè)自己,如果該服務(wù)不可用的話,它也不會(huì)自動(dòng)地被移除。現(xiàn)有很多種方式來解決這樣的問題。一個(gè)選擇是給注冊(cè)進(jìn)程添加一個(gè)時(shí)間戳和心跳機(jī)制,隨后在服務(wù)發(fā)現(xiàn)進(jìn)程中處理那些超時(shí)的路徑,也就是注冊(cè)的服務(wù)信息,當(dāng)然也可以通過另外一個(gè)清理進(jìn)程來實(shí)現(xiàn)。服務(wù)發(fā)現(xiàn)和Zookeeper很類似,Doozer可以羅列出指定路徑下的所有入口,隨后可以等待該路徑下的任意改動(dòng)。如果你在注冊(cè)期間使用一個(gè)時(shí)間戳和心跳,你就可以在服務(wù)發(fā)現(xiàn)期間忽略或者刪除任何過期的入口,也就是服務(wù)信息。和Zookeeper一樣,Doozer是一個(gè)CP強(qiáng)一致性系統(tǒng),當(dāng)發(fā)生網(wǎng)絡(luò)分區(qū)故障時(shí),會(huì)導(dǎo)致同樣的后果。EtcdEtcd是一個(gè)高可用的K-V存儲(chǔ)系統(tǒng),主要應(yīng)用于共享配置、服務(wù)發(fā)現(xiàn)等場(chǎng)景。Etcd可以說是被Zookeeper和Doozer催生而出。整個(gè)系統(tǒng)使用Go語(yǔ)言實(shí)現(xiàn),使用Raft算法來實(shí)現(xiàn)選舉一致,同時(shí)又具有一個(gè)基于HTTP+JSON的API。Etcd,和Doozer和Zookeeper相似,通常在集群中運(yùn)行3,5或者7個(gè)節(jié)點(diǎn)。client端可以使用一種特定的語(yǔ)言進(jìn)行綁定,同時(shí)也可以通過使用HTTP客戶端自行實(shí)現(xiàn)一種。服務(wù)注冊(cè)環(huán)節(jié)主要依賴于使用一個(gè)keyTTL來確保key的可用性,該keyTTL會(huì)和服務(wù)端的心跳捆綁在一起。如果一個(gè)服務(wù)在更新key的TTL時(shí)失敗了,那么Etcd會(huì)對(duì)它進(jìn)行超時(shí)處理。如果一個(gè)服務(wù)變?yōu)椴豢捎脿顟B(tài),client會(huì)需要處理這樣的連接失效,然后嘗試另連接一個(gè)服務(wù)實(shí)例。服務(wù)發(fā)現(xiàn)環(huán)節(jié)設(shè)計(jì)到羅列在一個(gè)目錄下的所有key值,隨后等待在該目錄上的所有變動(dòng)信息。由于API接口是基于HTTP的,所以client應(yīng)用會(huì)的Etcd集群保持一個(gè)long-polling的連接。由于Etcd使用Raft一致性協(xié)議,故它應(yīng)該是一個(gè)強(qiáng)一致性系統(tǒng)。Raft需要一個(gè)leader被選舉,然后所有的client請(qǐng)求會(huì)被該leader所處理。然而,Etcd似乎也支持從non-leaders中進(jìn)行讀取信息,使用的方式是在讀情況下提高可用性的未公開的一致性參數(shù)。在網(wǎng)絡(luò)分區(qū)故障期間,寫操作還是會(huì)被leader處理,而且同樣會(huì)出現(xiàn)失效的情況。

如何實(shí)現(xiàn)支持?jǐn)?shù)億用戶的長(zhǎng)連消息系統(tǒng)

此文是根據(jù)周洋在【高可用架構(gòu)群】中的分享內(nèi)容整理而成,轉(zhuǎn)發(fā)請(qǐng)注明出處。

周洋,360手機(jī)助手技術(shù)經(jīng)理及架構(gòu)師,負(fù)責(zé)360長(zhǎng)連接消息系統(tǒng),360手機(jī)助手架構(gòu)的開發(fā)與維護(hù)。

不知道咱們?nèi)好裁磿r(shí)候改為“Python高可用架構(gòu)群”了,所以不得不說,很榮幸能在接下來的一個(gè)小時(shí)里在Python群里討論golang....

360消息系統(tǒng)介紹

360消息系統(tǒng)更確切的說是長(zhǎng)連接push系統(tǒng),目前服務(wù)于360內(nèi)部多個(gè)產(chǎn)品,開發(fā)平臺(tái)數(shù)千款app,也支持部分聊天業(yè)務(wù)場(chǎng)景,單通道多app復(fù)用,支持上行數(shù)據(jù),提供接入方不同粒度的上行數(shù)據(jù)和用戶狀態(tài)回調(diào)服務(wù)。

目前整個(gè)系統(tǒng)按不同業(yè)務(wù)分成9個(gè)功能完整的集群,部署在多個(gè)idc上(每個(gè)集群覆蓋不同的idc),實(shí)時(shí)在線數(shù)億量級(jí)。通常情況下,pc,手機(jī),甚至是智能硬件上的360產(chǎn)品的push消息,基本上是從我們系統(tǒng)發(fā)出的。

關(guān)于push系統(tǒng)對(duì)比與性能指標(biāo)的討論

很多同行比較關(guān)心go語(yǔ)言在實(shí)現(xiàn)push系統(tǒng)上的性能問題,單機(jī)性能究竟如何,能否和其他語(yǔ)言實(shí)現(xiàn)的類似系統(tǒng)做對(duì)比么?甚至問如果是創(chuàng)業(yè),第三方云推送平臺(tái),推薦哪個(gè)?

其實(shí)各大廠都有類似的push系統(tǒng),市場(chǎng)上也有類似功能的云服務(wù)。包括我們公司早期也有erlang,nodejs實(shí)現(xiàn)的類似系統(tǒng),也一度被公司要求做類似的對(duì)比測(cè)試。我感覺在討論對(duì)比數(shù)據(jù)的時(shí)候,很難保證大家環(huán)境和需求的統(tǒng)一,我只能說下我這里的體會(huì),數(shù)據(jù)是有的,但這個(gè)數(shù)據(jù)前面估計(jì)會(huì)有很多定語(yǔ)~

第一個(gè)重要指標(biāo):?jiǎn)螜C(jī)的連接數(shù)指標(biāo)

做過長(zhǎng)連接的同行,應(yīng)該有體會(huì),如果在穩(wěn)定連接情況下,連接數(shù)這個(gè)指標(biāo),在沒有網(wǎng)絡(luò)吞吐情況下對(duì)比,其實(shí)意義往往不大,維持連接消耗cpu資源很小,每條連接tcp協(xié)議棧會(huì)占約4k的內(nèi)存開銷,系統(tǒng)參數(shù)調(diào)整后,我們單機(jī)測(cè)試數(shù)據(jù),最高也是可以達(dá)到單實(shí)例300w長(zhǎng)連接。但做更高的測(cè)試,我個(gè)人感覺意義不大。

因?yàn)閷?shí)際網(wǎng)絡(luò)環(huán)境下,單實(shí)例300w長(zhǎng)連接,從理論上算壓力就很大:實(shí)際弱網(wǎng)絡(luò)環(huán)境下,移動(dòng)客戶端的斷線率很高,假設(shè)每秒有1000分之一的用戶斷線重連。300w長(zhǎng)連接,每秒新建連接達(dá)到3w,這同時(shí)連入的3w用戶,要進(jìn)行注冊(cè),加載離線存儲(chǔ)等對(duì)內(nèi)rpc調(diào)用,另外300w長(zhǎng)連接的用戶心跳需要維持,假設(shè)心跳300s一次,心跳包每秒需要1w tps。單播和多播數(shù)據(jù)的轉(zhuǎn)發(fā),廣播數(shù)據(jù)的轉(zhuǎn)發(fā),本身也要響應(yīng)內(nèi)部的rpc調(diào)用,300w長(zhǎng)連接情況下,gc帶來的壓力,內(nèi)部接口的響應(yīng)延遲能否穩(wěn)定保障。這些集中在一個(gè)實(shí)例中,可用性是一個(gè)挑戰(zhàn)。所以線上單實(shí)例不會(huì)hold很高的長(zhǎng)連接,實(shí)際情況也要根據(jù)接入客戶端網(wǎng)絡(luò)狀況來決定。

第二個(gè)重要指標(biāo):消息系統(tǒng)的內(nèi)存使用量指標(biāo)

這一點(diǎn)上,使用go語(yǔ)言情況下,由于協(xié)程的原因,會(huì)有一部分額外開銷。但是要做兩個(gè)推送系統(tǒng)的對(duì)比,也有些需要確定問題。比如系統(tǒng)從設(shè)計(jì)上是否需要全雙工(即讀寫是否需要同時(shí)進(jìn)行)如果半雙工,理論上對(duì)一個(gè)用戶的連接只需要使用一個(gè)協(xié)程即可(這種情況下,對(duì)用戶的斷線檢測(cè)可能會(huì)有延時(shí)),如果是全雙工,那讀/寫各一個(gè)協(xié)程。兩種場(chǎng)景內(nèi)存開銷是有區(qū)別的。

另外測(cè)試數(shù)據(jù)的大小往往決定我們對(duì)連接上設(shè)置的讀寫buffer是多大,是全局復(fù)用的,還是每個(gè)連接上獨(dú)享的,還是動(dòng)態(tài)申請(qǐng)的。另外是否全雙工也決定buffer怎么開。不同的策略,可能在不同情況的測(cè)試中表現(xiàn)不一樣。

第三個(gè)重要指標(biāo):每秒消息下發(fā)量

這一點(diǎn)上,也要看我們對(duì)消息到達(dá)的QoS級(jí)別(回復(fù)ack策略區(qū)別),另外看架構(gòu)策略,每種策略有其更適用的場(chǎng)景,是純粹推?還是推拉結(jié)合?甚至是否開啟了消息日志?日志庫(kù)的實(shí)現(xiàn)機(jī)制、以及緩沖開多大?flush策略……這些都影響整個(gè)系統(tǒng)的吞吐量。

另外為了HA,增加了內(nèi)部通信成本,為了避免一些小概率事件,提供閃斷補(bǔ)償策略,這些都要考慮進(jìn)去。如果所有的都去掉,那就是比較基礎(chǔ)庫(kù)的性能了。

所以我只能給出大概數(shù)據(jù),24核,64G的服務(wù)器上,在QoS為message at least,純粹推,消息體256B~1kB情況下,單個(gè)實(shí)例100w實(shí)際用戶(200w+)協(xié)程,峰值可以達(dá)到2~5w的QPS...內(nèi)存可以穩(wěn)定在25G左右,gc時(shí)間在200~800ms左右(還有優(yōu)化空間)。

我們正常線上單實(shí)例用戶控制在80w以內(nèi),單機(jī)最多兩個(gè)實(shí)例。事實(shí)上,整個(gè)系統(tǒng)在推送的需求上,對(duì)高峰的輸出不是提速,往往是進(jìn)行限速,以防push系統(tǒng)瞬時(shí)的高吞吐量,轉(zhuǎn)化成對(duì)接入方業(yè)務(wù)服務(wù)器的ddos攻擊所以對(duì)于性能上,我感覺大家可以放心使用,至少在我們這個(gè)量級(jí)上,經(jīng)受過考驗(yàn),go1.5到來后,確實(shí)有之前投資又增值了的感覺。

消息系統(tǒng)架構(gòu)介紹

下面是對(duì)消息系統(tǒng)的大概介紹,之前一些同學(xué)可能在gopher china上可以看到分享,這里簡(jiǎn)單講解下架構(gòu)和各個(gè)組件功能,額外補(bǔ)充一些當(dāng)時(shí)遺漏的信息:

架構(gòu)圖如下,所有的service都 written by golang.

幾個(gè)大概重要組件介紹如下:

dispatcher service根據(jù)客戶端請(qǐng)求信息,將應(yīng)網(wǎng)絡(luò)和區(qū)域的長(zhǎng)連接服務(wù)器的,一組IP傳送給客戶端。客戶端根據(jù)返回的IP,建立長(zhǎng)連接,連接Room service.

room Service,長(zhǎng)連接網(wǎng)關(guān),hold用戶連接,并將用戶注冊(cè)進(jìn)register service,本身也做一些接入安全策略、白名單、IP限制等。

register service是我們?nèi)謘ession存儲(chǔ)組件,存儲(chǔ)和索引用戶的相關(guān)信息,以供獲取和查詢。

coordinator service用來轉(zhuǎn)發(fā)用戶的上行數(shù)據(jù),包括接入方訂閱的用戶狀態(tài)信息的回調(diào),另外做需要協(xié)調(diào)各個(gè)組件的異步操作,比如kick用戶操作,需要從register拿出其他用戶做異步操作.

saver service是存儲(chǔ)訪問層,承擔(dān)了對(duì)redis和mysql的操作,另外也提供部分業(yè)務(wù)邏輯相關(guān)的內(nèi)存緩存,比如廣播信息的加載可以在saver中進(jìn)行緩存。另外一些策略,比如客戶端sdk由于被惡意或者意外修改,每次加載了消息,不回復(fù)ack,那服務(wù)端就不會(huì)刪除消息,消息就會(huì)被反復(fù)加載,形成死循環(huán),可以通過在saver中做策略和判斷。(客戶端總是不可信的)。

center service提供給接入方的內(nèi)部api服務(wù)器,比如單播或者廣播接口,狀態(tài)查詢接口等一系列api,包括運(yùn)維和管理的api。

舉兩個(gè)常見例子,了解工作機(jī)制:比如發(fā)一條單播給一個(gè)用戶,center先請(qǐng)求Register獲取這個(gè)用戶之前注冊(cè)的連接通道標(biāo)識(shí)、room實(shí)例地址,通過room service下發(fā)給長(zhǎng)連接 Center Service比較重的工作如全網(wǎng)廣播,需要把所有的任務(wù)分解成一系列的子任務(wù),分發(fā)給所有center,然后在所有的子任務(wù)里,分別獲取在線和離線的所有用戶,再批量推到Room Service。通常整個(gè)集群在那一瞬間壓力很大。

deployd/agent service用于部署管理各個(gè)進(jìn)程,收集各組件的狀態(tài)和信息,zookeeper和keeper用于整個(gè)系統(tǒng)的配置文件管理和簡(jiǎn)單調(diào)度

關(guān)于推送的服務(wù)端架構(gòu)

常見的推送模型有長(zhǎng)輪訓(xùn)拉取,服務(wù)端直接推送(360消息系統(tǒng)目前主要是這種),推拉結(jié)合(推送只發(fā)通知,推送后根據(jù)通知去拉取消息).

拉取的方式不說了,現(xiàn)在并不常用了,早期很多是nginx+lua+redis,長(zhǎng)輪訓(xùn),主要問題是開銷比較大,時(shí)效性也不好,能做的優(yōu)化策略不多。

直接推送的系統(tǒng),目前就是360消息系統(tǒng)這種,消息類型是消耗型的,并且對(duì)于同一個(gè)用戶并不允許重復(fù)消耗,如果需要多終端重復(fù)消耗,需要抽象成不同用戶。

推的好處是實(shí)時(shí)性好,開銷小,直接將消息下發(fā)給客戶端,不需要客戶端走從接入層到存儲(chǔ)層主動(dòng)拉取.

但純推送模型,有個(gè)很大問題,由于系統(tǒng)是異步的,他的時(shí)序性無法精確保證。這對(duì)于push需求來說是夠用的,但如果復(fù)用推送系統(tǒng)做im類型通信,可能并不合適。

對(duì)于嚴(yán)格要求時(shí)序性,消息可以重復(fù)消耗的系統(tǒng),目前也都是走推拉結(jié)合的模型,就是只使用我們的推送系統(tǒng)發(fā)通知,并附帶id等給客戶端做拉取的判斷策略,客戶端根據(jù)推送的key,主動(dòng)從業(yè)務(wù)服務(wù)器拉取消息。并且當(dāng)主從同步延遲的時(shí)候,跟進(jìn)推送的key做延遲拉取策略。同時(shí)也可以通過消息本身的QoS,做純粹的推送策略,比如一些“正在打字的”低優(yōu)先級(jí)消息,不需要主動(dòng)拉取了,通過推送直接消耗掉。

哪些因素決定推送系統(tǒng)的效果?

首先是sdk的完善程度,sdk策略和細(xì)節(jié)完善度,往往決定了弱網(wǎng)絡(luò)環(huán)境下最終推送質(zhì)量.

SDK選路策略,最基本的一些策略如下:有些開源服務(wù)可能會(huì)針對(duì)用戶hash一個(gè)該接入?yún)^(qū)域的固定ip,實(shí)際上在國(guó)內(nèi)環(huán)境下不可行,最好分配器(dispatcher)是返回散列的一組,而且端口也要參開,必要時(shí)候,客戶端告知是retry多組都連不上,返回不同idc的服務(wù)器。因?yàn)槲覀儠?huì)經(jīng)常檢測(cè)到一些case,同一地區(qū)的不同用戶,可能對(duì)同一idc內(nèi)的不同ip連通性都不一樣,也出現(xiàn)過同一ip不同端口連通性不同,所以用戶的選路策略一定要靈活,策略要足夠完善.另外在選路過程中,客戶端要對(duì)不同網(wǎng)絡(luò)情況下的長(zhǎng)連接ip做緩存,當(dāng)網(wǎng)絡(luò)環(huán)境切換時(shí)候(wifi、2G、3G),重新請(qǐng)求分配器,緩存不同網(wǎng)絡(luò)環(huán)境的長(zhǎng)連接ip。

客戶端對(duì)于數(shù)據(jù)心跳和讀寫超時(shí)設(shè)置,完善斷線檢測(cè)重連機(jī)制

針對(duì)不同網(wǎng)絡(luò)環(huán)境,或者客戶端本身消息的活躍程度,心跳要自適應(yīng)的進(jìn)行調(diào)整并與服務(wù)端協(xié)商,來保證鏈路的連通性。并且在弱網(wǎng)絡(luò)環(huán)境下,除了網(wǎng)絡(luò)切換(wifi切3G)或者讀寫出錯(cuò)情況,什么時(shí)候重新建立鏈路也是一個(gè)問題。客戶端發(fā)出的ping包,不同網(wǎng)絡(luò)下,多久沒有得到響應(yīng),認(rèn)為網(wǎng)絡(luò)出現(xiàn)問題,重新建立鏈路需要有個(gè)權(quán)衡。另外對(duì)于不同網(wǎng)絡(luò)環(huán)境下,讀取不同的消息長(zhǎng)度,也要有不同的容忍時(shí)間,不能一刀切。好的心跳和讀寫超時(shí)設(shè)置,可以讓客戶端最快的檢測(cè)到網(wǎng)絡(luò)問題,重新建立鏈路,同時(shí)在網(wǎng)絡(luò)抖動(dòng)情況下也能完成大數(shù)據(jù)傳輸。

結(jié)合服務(wù)端做策略

另外系統(tǒng)可能結(jié)合服務(wù)端做一些特殊的策略,比如我們?cè)谶x路時(shí)候,我們會(huì)將同一個(gè)用戶盡量映射到同一個(gè)room service實(shí)例上。斷線時(shí),客戶端盡量對(duì)上次連接成功的地址進(jìn)行重試。主要是方便服務(wù)端做閃斷情況下策略,會(huì)暫存用戶閃斷時(shí)實(shí)例上的信息,重新連入的 時(shí)候,做單實(shí)例內(nèi)的遷移,減少延時(shí)與加載開銷.

客戶端保活策略

很多創(chuàng)業(yè)公司愿意重新搭建一套push系統(tǒng),確實(shí)不難實(shí)現(xiàn),其實(shí)在協(xié)議完備情況下(最簡(jiǎn)單就是客戶端不回ack不清數(shù)據(jù)),服務(wù)端會(huì)保證消息是不丟的。但問題是為什么在消息有效期內(nèi),到達(dá)率上不去?往往因?yàn)樽约篴pp的push service存活能力不高。選用云平臺(tái)或者大廠的,往往sdk會(huì)做一些保活策略,比如和其他app共生,互相喚醒,這也是云平臺(tái)的push service更有保障原因。我相信很多云平臺(tái)旗下的sdk,多個(gè)使用同樣sdk的app,為了實(shí)現(xiàn)服務(wù)存活,是可以互相喚醒和保證活躍的。另外現(xiàn)在push sdk本身是單連接,多app復(fù)用的,這為sdk實(shí)現(xiàn),增加了新的挑戰(zhàn)。

綜上,對(duì)我來說,選擇推送平臺(tái),優(yōu)先會(huì)考慮客戶端sdk的完善程度。對(duì)于服務(wù)端,選擇條件稍微簡(jiǎn)單,要求部署接入點(diǎn)(IDC)越要多,配合精細(xì)的選路策略,效果越有保證,至于想知道哪些云服務(wù)有多少點(diǎn),這個(gè)群里來自各地的小伙伴們,可以合伙測(cè)測(cè)。

go語(yǔ)言開發(fā)問題與解決方案

下面講下,go開發(fā)過程中遇到挑戰(zhàn)和優(yōu)化策略,給大家看下當(dāng)年的一張圖,在第一版優(yōu)化方案上線前一天截圖~

可以看到,內(nèi)存最高占用69G,GC時(shí)間單實(shí)例最高時(shí)候高達(dá)3~6s.這種情況下,試想一次悲劇的請(qǐng)求,經(jīng)過了幾個(gè)正在執(zhí)行g(shù)c的組件,后果必然是超時(shí)... gc照成的接入方重試,又加重了系統(tǒng)的負(fù)擔(dān)。遇到這種情況當(dāng)時(shí)整個(gè)系統(tǒng)最差情況每隔2,3天就需要重啟一次~

當(dāng)時(shí)出現(xiàn)問題,現(xiàn)在總結(jié)起來,大概以下幾點(diǎn)

1.散落在協(xié)程里的I/O,Buffer和對(duì)象不復(fù)用。

當(dāng)時(shí)(12年)由于對(duì)go的gc效率理解有限,比較奔放,程序里大量short live的協(xié)程,對(duì)內(nèi)通信的很多io操作,由于不想阻塞主循環(huán)邏輯或者需要及時(shí)響應(yīng)的邏輯,通過單獨(dú)go協(xié)程來實(shí)現(xiàn)異步。這回會(huì)gc帶來很多負(fù)擔(dān)。

針對(duì)這個(gè)問題,應(yīng)盡量控制協(xié)程創(chuàng)建,對(duì)于長(zhǎng)連接這種應(yīng)用,本身已經(jīng)有幾百萬并發(fā)協(xié)程情況下,很多情況沒必要在各個(gè)并發(fā)協(xié)程內(nèi)部做異步io,因?yàn)槌绦虻牟⑿卸仁怯邢蓿碚撋献鰠f(xié)程內(nèi)做阻塞操作是沒問題。

如果有些需要異步執(zhí)行,比如如果不異步執(zhí)行,影響對(duì)用戶心跳或者等待response無法響應(yīng),最好通過一個(gè)任務(wù)池,和一組常駐協(xié)程,來消耗,處理結(jié)果,通過channel再傳回調(diào)用方。使用任務(wù)池還有額外的好處,可以對(duì)請(qǐng)求進(jìn)行打包處理,提高吞吐量,并且可以加入控量策略.

2.網(wǎng)絡(luò)環(huán)境不好引起激增

go協(xié)程相比較以往高并發(fā)程序,如果做不好流控,會(huì)引起協(xié)程數(shù)量激增。早期的時(shí)候也會(huì)發(fā)現(xiàn),時(shí)不時(shí)有部分主機(jī)內(nèi)存會(huì)遠(yuǎn)遠(yuǎn)大于其他服務(wù)器,但發(fā)現(xiàn)時(shí)候,所有主要profiling參數(shù)都正常了。

后來發(fā)現(xiàn),通信較多系統(tǒng)中,網(wǎng)絡(luò)抖動(dòng)阻塞是不可免的(即使是內(nèi)網(wǎng)),對(duì)外不停accept接受新請(qǐng)求,但執(zhí)行過程中,由于對(duì)內(nèi)通信阻塞,大量協(xié)程被 創(chuàng)建,業(yè)務(wù)協(xié)程等待通信結(jié)果沒有釋放,往往瞬時(shí)會(huì)迎來協(xié)程暴漲。但這些內(nèi)存在系統(tǒng)穩(wěn)定后,virt和res都并沒能徹底釋放,下降后,維持高位。

處理這種情況,需要增加一些流控策略,流控策略可以選擇在rpc庫(kù)來做,或者上面說的任務(wù)池來做,其實(shí)我感覺放在任務(wù)池里做更合理些,畢竟rpc通信庫(kù)可以做讀寫數(shù)據(jù)的限流,但它并不清楚具體的限流策略,到底是重試還是日志還是緩存到指定隊(duì)列。任務(wù)池本身就是業(yè)務(wù)邏輯相關(guān)的,它清楚針對(duì)不同的接口需要的流控限制策略。

3.低效和開銷大的rpc框架

早期rpc通信框架比較簡(jiǎn)單,對(duì)內(nèi)通信時(shí)候使用的也是短連接。這本來短連接開銷和性能瓶頸超出我們預(yù)期,短連接io效率是低一些,但端口資源夠,本身吞吐可以滿足需要,用是沒問題的,很多分層的系統(tǒng),也有http短連接對(duì)內(nèi)進(jìn)行請(qǐng)求的

但早期go版本,這樣寫程序,在一定量級(jí)情況,是支撐不住的。短連接大量臨時(shí)對(duì)象和臨時(shí)buffer創(chuàng)建,在本已經(jīng)百萬協(xié)程的程序中,是無法承受的。所以后續(xù)我們對(duì)我們的rpc框架作了兩次調(diào)整。

第二版的rpc框架,使用了連接池,通過長(zhǎng)連接對(duì)內(nèi)進(jìn)行通信(復(fù)用的資源包括client和server的:編解碼Buffer、Request/response),大大改善了性能。

但這種在一次request和response還是占用連接的,如果網(wǎng)絡(luò)狀況ok情況下,這不是問題,足夠滿足需要了,但試想一個(gè)room實(shí)例要與后面的數(shù)百個(gè)的register,coordinator,saver,center,keeper實(shí)例進(jìn)行通信,需要建立大量的常駐連接,每個(gè)目標(biāo)機(jī)幾十個(gè)連接,也有數(shù)千個(gè)連接被占用。

非持續(xù)抖動(dòng)時(shí)候(持續(xù)逗開多少無解),或者有延遲較高的請(qǐng)求時(shí)候,如果針對(duì)目標(biāo)ip連接開少了,會(huì)有瞬時(shí)大量請(qǐng)求阻塞,連接無法得到充分利用。第三版增加了Pipeline操作,Pipeline會(huì)帶來一些額外的開銷,利用tcp的全雙特性,以盡量少的連接完成對(duì)各個(gè)服務(wù)集群的rpc調(diào)用。

4.Gc時(shí)間過長(zhǎng)

Go的Gc仍舊在持續(xù)改善中,大量對(duì)象和buffer創(chuàng)建,仍舊會(huì)給gc帶來很大負(fù)擔(dān),尤其一個(gè)占用了25G左右的程序。之前go team的大咖郵件也告知我們,未來會(huì)讓使用協(xié)程的成本更低,理論上不需要在應(yīng)用層做更多的策略來緩解gc.

改善方式,一種是多實(shí)例的拆分,如果公司沒有端口限制,可以很快部署大量實(shí)例,減少gc時(shí)長(zhǎng),最直接方法。不過對(duì)于360來說,外網(wǎng)通常只能使用80和433。因此常規(guī)上只能開啟兩個(gè)實(shí)例。當(dāng)然很多人給我建議能否使用SO_REUSEPORT,不過我們內(nèi)核版本確實(shí)比較低,并沒有實(shí)踐過。

另外能否模仿nginx,fork多個(gè)進(jìn)程監(jiān)控同樣端口,至少我們目前沒有這樣做,主要對(duì)于我們目前進(jìn)程管理上,還是獨(dú)立的運(yùn)行的,對(duì)外監(jiān)聽不同端口程序,還有配套的內(nèi)部通信和管理端口,實(shí)例管理和升級(jí)上要做調(diào)整。

解決gc的另兩個(gè)手段,是內(nèi)存池和對(duì)象池,不過最好做仔細(xì)評(píng)估和測(cè)試,內(nèi)存池、對(duì)象池使用,也需要對(duì)于代碼可讀性與整體效率進(jìn)行權(quán)衡。

這種程序一定情況下會(huì)降低并行度,因?yàn)橛贸貎?nèi)資源一定要加互斥鎖或者原子操作做CAS,通常原子操作實(shí)測(cè)要更快一些。CAS可以理解為可操作的更細(xì)行為粒度的鎖(可以做更多CAS策略,放棄運(yùn)行,防止忙等)。這種方式帶來的問題是,程序的可讀性會(huì)越來越像C語(yǔ)言,每次要malloc,各地方用完后要free,對(duì)于對(duì)象池free之前要reset,我曾經(jīng)在應(yīng)用層嘗試做了一個(gè)分層次結(jié)構(gòu)的“無鎖隊(duì)列”

上圖左邊的數(shù)組實(shí)際上是一個(gè)列表,這個(gè)列表按大小將內(nèi)存分塊,然后使用atomic操作進(jìn)行CAS。但實(shí)際要看測(cè)試數(shù)據(jù)了,池技術(shù)可以明顯減少臨時(shí)對(duì)象和內(nèi)存的申請(qǐng)和釋放,gc時(shí)間會(huì)減少,但加鎖帶來的并行度的降低,是否能給一段時(shí)間內(nèi)的整體吞吐量帶來提升,要做測(cè)試和權(quán)衡…

在我們消息系統(tǒng),實(shí)際上后續(xù)去除了部分這種黑科技,試想在百萬個(gè)協(xié)程里面做自旋操作申請(qǐng)復(fù)用的buffer和對(duì)象,開銷會(huì)很大,尤其在協(xié)程對(duì)線程多對(duì)多模型情況下,更依賴于golang本身調(diào)度策略,除非我對(duì)池增加更多的策略處理,減少忙等,感覺是在把runtime做的事情,在應(yīng)用層非常不優(yōu)雅的實(shí)現(xiàn)。普遍使用開銷理論就大于收益。

但對(duì)于rpc庫(kù)或者codec庫(kù),任務(wù)池內(nèi)部,這些開定量協(xié)程,集中處理數(shù)據(jù)的區(qū)域,可以嘗試改造~

對(duì)于有些固定對(duì)象復(fù)用,比如固定的心跳包什么的,可以考慮使用全局一些對(duì)象,進(jìn)行復(fù)用,針對(duì)應(yīng)用層數(shù)據(jù),具體設(shè)計(jì)對(duì)象池,在部分環(huán)節(jié)去復(fù)用,可能比這種無差別的設(shè)計(jì)一個(gè)通用池更能進(jìn)行效果評(píng)估.

消息系統(tǒng)的運(yùn)維及測(cè)試

下面介紹消息系統(tǒng)的架構(gòu)迭代和一些迭代經(jīng)驗(yàn),由于之前在其他地方有過分享,后面的會(huì)給出相關(guān)鏈接,下面實(shí)際做個(gè)簡(jiǎn)單介紹,感興趣可以去鏈接里面看

架構(gòu)迭代~根據(jù)業(yè)務(wù)和集群的拆分,能解決部分灰度部署上線測(cè)試,減少點(diǎn)對(duì)點(diǎn)通信和廣播通信不同產(chǎn)品的相互影響,針對(duì)特定的功能做獨(dú)立的優(yōu)化.

消息系統(tǒng)架構(gòu)和集群拆分,最基本的是拆分多實(shí)例,其次是按照業(yè)務(wù)類型對(duì)資源占用情況分類,按用戶接入網(wǎng)絡(luò)和對(duì)idc布點(diǎn)要求分類(目前沒有條件,所有的產(chǎn)品都部署到全部idc)

系統(tǒng)的測(cè)試go語(yǔ)言在并發(fā)測(cè)試上有獨(dú)特優(yōu)勢(shì)。

對(duì)于壓力測(cè)試,目前主要針對(duì)指定的服務(wù)器,選定線上空閑的服務(wù)器做長(zhǎng)連接壓測(cè)。然后結(jié)合可視化,分析壓測(cè)過程中的系統(tǒng)狀態(tài)。但壓測(cè)早期用的比較多,但實(shí)現(xiàn)的統(tǒng)計(jì)報(bào)表功能和我理想有一定差距。我覺得最近出的golang開源產(chǎn)品都符合這種場(chǎng)景,go寫網(wǎng)絡(luò)并發(fā)程序給大家?guī)淼谋憷尨蠹野岩酝鶠榱私档蛷?fù)雜度,拆解或者分層協(xié)作的組件,又組合在了一起。

QA

Q1:協(xié)議棧大小,超時(shí)時(shí)間定制原則?

移動(dòng)網(wǎng)絡(luò)下超時(shí)時(shí)間按產(chǎn)品需求通常2g,3G情況下是5分鐘,wifi情況下5~8分鐘。但對(duì)于個(gè)別場(chǎng)景,要求響應(yīng)非常迅速的場(chǎng)景,如果連接idle超過1分鐘,都會(huì)有ping,pong,來校驗(yàn)是否斷線檢測(cè),盡快做到重新連接。

Q2:消息是否持久化?

消息持久化,通常是先存后發(fā),存儲(chǔ)用的redis,但落地用的mysql。mysql只做故障恢復(fù)使用。

Q3:消息風(fēng)暴怎么解決的?

如果是發(fā)送情況下,普通產(chǎn)品是不需要限速的,對(duì)于較大產(chǎn)品是有發(fā)送隊(duì)列做控速度,按人數(shù),按秒進(jìn)行控速度發(fā)放,發(fā)送成功再發(fā)送下一條。

Q4:golang的工具鏈支持怎么樣?我自己寫過一些小程序千把行之內(nèi),確實(shí)很不錯(cuò),但不知道代碼量上去之后,配套的debug工具和profiling工具如何,我看上邊有分享說golang自帶的profiling工具還不錯(cuò),那debug呢怎么樣呢,官方一直沒有出debug工具,gdb支持也不完善,不知你們用的什么?

是這樣的,我們正常就是println,我感覺基本上可以定位我所有問題,但也不排除由于并行性通過println無法復(fù)現(xiàn)的問題,目前來看只能靠經(jīng)驗(yàn)了。只要常見并發(fā)嘗試,經(jīng)過分析是可以找到的。go很快會(huì)推出調(diào)試工具的~

Q5:協(xié)議棧是基于tcp嗎?

是否有協(xié)議拓展功能?協(xié)議棧是tcp,整個(gè)系統(tǒng)tcp長(zhǎng)連接,沒有考慮擴(kuò)展其功能~如果有好的經(jīng)驗(yàn),可以分享~

Q6:問個(gè)問題,這個(gè)系統(tǒng)是接收上行數(shù)據(jù)的吧,系統(tǒng)接收上行數(shù)據(jù)后是轉(zhuǎn)發(fā)給相應(yīng)系統(tǒng)做處理么,是怎么轉(zhuǎn)發(fā)呢,如果需要給客戶端返回調(diào)用結(jié)果又是怎么處理呢?

系統(tǒng)上行數(shù)據(jù)是根據(jù)協(xié)議頭進(jìn)行轉(zhuǎn)發(fā),協(xié)議頭里面標(biāo)記了產(chǎn)品和轉(zhuǎn)發(fā)類型,在coordinator里面跟進(jìn)產(chǎn)品和轉(zhuǎn)發(fā)類型,回調(diào)用戶,如果用戶需要阻塞等待回復(fù)才能后續(xù)操作,那通過再發(fā)送消息,路由回用戶。因?yàn)檎麄€(gè)系統(tǒng)是全異步的。

Q7:問個(gè)pushsdk的問題。pushsdk的單連接,多app復(fù)用方式,這樣的情況下以下幾個(gè)問題是如何解決的:1)系統(tǒng)流量統(tǒng)計(jì)會(huì)把所有流量都算到啟動(dòng)連接的應(yīng)用吧?而啟動(dòng)應(yīng)用的連接是不固定的吧?2)同一個(gè)pushsdk在不同的應(yīng)用中的版本號(hào)可能不一樣,這樣暴露出來的接口可能有版本問題,如果用單連接模式怎么解決?

流量只能算在啟動(dòng)的app上了,但一般這種安裝率很高的app承擔(dān)可能性大,常用app本身被檢測(cè)和殺死可能性較少,另外消息下發(fā)量是有嚴(yán)格控制 的。整體上用戶還是省電和省流量的。我們pushsdk盡量向上兼容,出于這個(gè)目的,push sdk本身做的工作非常有限,抽象出來一些常見的功能,純推的系統(tǒng),客戶端策略目前做的很少,也有這個(gè)原因。

Q8:生產(chǎn)系統(tǒng)的profiling是一直打開的么?

不是一直打開,每個(gè)集群都有采樣,但需要開啟哪個(gè)可以后臺(tái)控制。這個(gè)profling是通過接口調(diào)用。

Q9:面前系統(tǒng)中的消息消費(fèi)者可不可以分組?類似于Kafka。

客戶端可以訂閱不同產(chǎn)品的消息,接受不同的分組。接入的時(shí)候進(jìn)行bind或者unbind操作

Q10:為什么放棄erlang,而選擇go,有什么特別原因嗎?我們現(xiàn)在用的erlang?

erlang沒有問題,原因是我們上線后,其他團(tuán)隊(duì)才做出來,經(jīng)過qa一個(gè)部門對(duì)比測(cè)試,在沒有顯著性能提升下,選擇繼續(xù)使用go版本的push,作為公司基礎(chǔ)服務(wù)。

Q11:流控問題有排查過網(wǎng)卡配置導(dǎo)致的idle問題嗎?

流控是業(yè)務(wù)級(jí)別的流控,我們上線前對(duì)于內(nèi)網(wǎng)的極限通信量做了測(cè)試,后續(xù)將請(qǐng)求在rpc庫(kù)內(nèi),控制在小于內(nèi)部通信開銷的上限以下.在到達(dá)上限前作流控。

Q12:服務(wù)的協(xié)調(diào)調(diào)度為什么選擇zk有考慮過raft實(shí)現(xiàn)嗎?golang的raft實(shí)現(xiàn)很多啊,比如Consul和ectd之類的。

3年前,還沒有后兩者或者后兩者沒聽過應(yīng)該。zk當(dāng)時(shí)公司內(nèi)部成熟方案,不過目前來看,我們不準(zhǔn)備用zk作結(jié)合系統(tǒng)的定制開發(fā),準(zhǔn)備用自己寫的keeper代替zk,完成配置文件自動(dòng)轉(zhuǎn)數(shù)據(jù)結(jié)構(gòu),數(shù)據(jù)結(jié)構(gòu)自動(dòng)同步指定進(jìn)程,同時(shí)里面可以完成很多自定義的發(fā)現(xiàn)和控制策略,客戶端包含keeper的sdk就可以實(shí)現(xiàn)以上的所有監(jiān)控?cái)?shù)據(jù),profling數(shù)據(jù)收集,配置文件更新,啟動(dòng)關(guān)閉等回調(diào)。完全抽象成語(yǔ)keeper通信sdk,keeper之間考慮用raft。

Q13:負(fù)載策略是否同時(shí)在服務(wù)側(cè)與CLIENT側(cè)同時(shí)做的 (DISPATCHER 會(huì)返回一組IP)?另外,ROOM SERVER/REGISTER SERVER連接狀態(tài)的一致性|可用性如何保證? 服務(wù)側(cè)保活有無特別關(guān)注的地方? 安全性方面是基于TLS再加上應(yīng)用層加密?

會(huì)在server端做,比如重啟操作前,會(huì)下發(fā)指令類型消息,讓客戶端進(jìn)行主動(dòng)行為。部分消息使用了加密策略,自定義的rsa+des,另外滿足我們安全公司的需要,也定制開發(fā)很多安全加密策略。一致性是通過冷備解決的,早期考慮雙寫,但實(shí)時(shí)狀態(tài)雙寫同步代價(jià)太高而且容易有臟數(shù)據(jù),比如register掛了,調(diào)用所有room,通過重新刷入指定register來解決。

Q14:這個(gè)keeper有開源打算嗎?

還在寫,如果沒耦合我們系統(tǒng)太多功能,一定會(huì)開源的,主要這意味著,我們所有的bind在sdk的庫(kù)也需要開源~

Q15:比較好奇lisence是哪個(gè)如果開源?

請(qǐng)教個(gè)etcd中的raft算法問題

etcd是一個(gè)高可用的鍵值存儲(chǔ)系統(tǒng),主要用于共享配置和服務(wù)發(fā)現(xiàn)。 etcd是由CoreOS開發(fā)并維護(hù)的,靈感來自于 ZooKeeper 和 Doozer,它使用Go語(yǔ)言編寫,并通過Raft一致性算法處理日志復(fù)制以保證強(qiáng)一致性。 Raft是一個(gè)來自Stanford的新的一致性算法,。

網(wǎng)頁(yè)名稱:go語(yǔ)言實(shí)現(xiàn)raft協(xié)議的簡(jiǎn)單介紹
本文URL:http://m.kartarina.com/article40/hieheo.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供ChatGPT品牌網(wǎng)站建設(shè)企業(yè)網(wǎng)站制作做網(wǎng)站面包屑導(dǎo)航軟件開發(fā)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

成都網(wǎng)頁(yè)設(shè)計(jì)公司
主站蜘蛛池模板: 亚洲Av无码国产情品久久| 精品久久久无码中文字幕天天| 精品无码黑人又粗又大又长| 一区二区无码免费视频网站| 成人麻豆日韩在无码视频| 亚洲午夜无码久久久久软件| 亚洲一级特黄大片无码毛片| 久久青青草原亚洲av无码app| 精品久久久久久无码中文字幕一区 | 粉嫩高中生无码视频在线观看| 久久久久亚洲av无码专区喷水| 国产丰满乱子伦无码专区| 无码中文字幕一区二区三区| 无码一区二区三区| 亚洲av中文无码| 无码人妻丰满熟妇片毛片| 中文字幕无码不卡在线| 91嫩草国产在线无码观看| 丰满日韩放荡少妇无码视频 | JAVA性无码HD中文| 国产成人精品无码一区二区| 无码人妻少妇伦在线电影| 亚洲精品无码永久在线观看男男 | 亚洲精品无码少妇30P| 无码中文人妻视频2019| 亚洲精品无码av人在线观看| 中文字幕无码久久久| 国产精品99无码一区二区| heyzo专区无码综合| 亚洲色偷拍区另类无码专区| 狠狠爱无码一区二区三区| 精品亚洲av无码一区二区柚蜜| 亚洲日韩精品无码AV海量| 无码国内精品久久综合88| 亚洲欧洲AV无码专区| 亚洲av无码一区二区三区观看| 国产亚洲3p无码一区二区| 亚洲av日韩av高潮潮喷无码| 无码人妻精品一区二区三区久久| 午夜福利无码不卡在线观看| 日韩AV无码中文无码不卡电影|