當前位置:首頁 » 操作系統 » 心跳包演算法

心跳包演算法

發布時間: 2022-11-25 11:41:32

『壹』 無線數據終端是什麼

無線數據終端也叫無線數傳終端,即實現無線數據傳輸所使用的終端模塊。

無線數據終端通常與下位機相連,實現無線數據傳輸的目的,有「工業領域的手機」的稱號,因為其傳輸原理和我們平常使用的手機的數據傳輸時基本一致的。

其中比較典型的設備包括無線數傳,無線路由器,無線Modem等設備,下面介紹的就是應用最廣泛的DTU的相關參數作為參考。

為用戶提供高速,穩定可靠,數據終端永遠在線,多種協議轉換的虛擬專用網路。針對網路流量控制的用戶,產品支持語音,簡訊,數據觸發上線以及超時自動斷線的功能。同時也支持雙數據中心備份,以及多數據中心同步接收數據等功能。

(1)心跳包演算法擴展閱讀:

無線數據終端的特點:

1、DNS自動獲取:自動獲取DNS,不再需要人工配置輸入DNS,規避了因為選擇的DNS伺服器異常,導致DTU設備當機的嚴重現象!

2、完善的協議棧: 新系統載入了完善的TCP/IP協議棧,原來系統採用的是輕量級TCP/IP(LPIP)協議棧,協議棧是有裁剪的,新系統採用了完善的TCP/IP協議棧,網路通信性能優異。

3、支持多中心:客戶數據可以同時往客戶指定的監控中心發送,最大支持發送中心達256個。

參考資料來源:網路-無線數傳終端

『貳』 CAP協議以及演算法

兩階段提交

Two-phase Commit(2PC):保證一個事務跨越多個節點時保持 ACID 特性;

兩類節點:協調者(Coordinator)和參與者(Participants),協調者只有一個,參與者可以有多個。

過程:

需要注意的是,在准備階段,參與者執行了事務,但是還未提交。只有在提交階段接收到協調者發來的通知後,才進行提交或者回滾。

存在的問題

Paxos(Lamport):

分布式系統中的節點通信存在兩種模型: 共享內存 (Shared memory)和 消息傳遞 (Messages passing)。

基於消息傳遞通信模型的分布式系統,不可避免的會發生以下錯誤:進程可能會慢、被殺死或者重啟,消息可能會延遲、丟失、重復,在基礎Paxos場景中,先不考慮可能出現消息篡改即 拜占庭錯誤 的情況。

Paxos演算法解決的問題是在一個可能發生上述異常的 分布式系統 中如何就某個值達成一致,保證不論發生以上任何異常,都不會破壞決議的一致性。

主要有三類節點:

過程:

規定一個提議包含兩個欄位:[n, v],其中 n 為序號(具有唯一性),v 為提議值。

下圖演示了兩個 Proposer 和三個 Acceptor 的系統中運行該演算法的初始過程,每個 Proposer 都會向所有 Acceptor 發送提議請求。

當 Acceptor 接收到一個提議請求,包含的提議為 [n1, v1],並且之前還未接收過提議請求,那麼發送一個提議響應,設置當前接收到的提議為 [n1, v1],並且保證以後不會再接受序號小於 n1 的提議。

如下圖,Acceptor X 在收到 [n=2, v=8] 的提議請求時,由於之前沒有接收過提議,因此就發送一個 [no previous] 的提議響應,並且設置當前接收到的提議為 [n=2, v=8],並且保證以後不會再接受序號小於 2 的提議。其它的 Acceptor 類似。

如果 Acceptor 接受到一個提議請求,包含的提議為 [n2, v2],並且之前已經接收過提議 [n1, v1]。如果 n1 > n2,那麼就丟棄該提議請求;否則,發送提議響應,該提議響應包含之前已經接收過的提議 [n1, v1],設置當前接收到的提議為 [n2, v2],並且保證以後不會再接受序號小於 n2 的提議。

如下圖,Acceptor Z 收到 Proposer A 發來的 [n=2, v=8] 的提議請求,由於之前已經接收過 [n=4, v=5] 的提議,並且 n > 2,因此就拋棄該提議請求;Acceptor X 收到 Proposer B 發來的 [n=4, v=5] 的提議請求,因為之前接收到的提議為 [n=2, v=8],並且 2 <= 4,因此就發送 [n=2, v=8] 的提議響應,設置當前接收到的提議為 [n=4, v=5],並且保證以後不會再接受序號小於 4 的提議。Acceptor Y 類似。

當一個 Proposer 接收到超過一半 Acceptor 的提議響應時,就可以發送接受請求。

Proposer A 接受到兩個提議響應之後,就發送 [n=2, v=8] 接受請求。該接受請求會被所有 Acceptor 丟棄,因為此時所有 Acceptor 都保證不接受序號小於 4 的提議。

Proposer B 過後也收到了兩個提議響應,因此也開始發送接受請求。需要注意的是,接受請求的 v 需要取它收到的最大 v 值,也就是 8。因此它發送 [n=4, v=8] 的接受請求。

Acceptor 接收到接受請求時,如果序號大於等於該 Acceptor 承諾的最小序號,那麼就發送通知給所有的 Learner。當 Learner 發現有大多數的 Acceptor 接收了某個提議,那麼該提議的提議值就被 Paxos 選擇出來。

Raft(14年): 簡化,更容易理解,也更容易實現。

引入主節點,通過競選。

節點類型:Follower、Candidate 和 Leader

Leader 會周期性的發送心跳包給 Follower。每個 Follower 都設置了一個隨機的競選超時時間,一般為 150ms~300ms,如果在這個時間內沒有收到 Leader 的心跳包,就會變成 Candidate,進入競選階段。

流程:
① 下圖表示一個分布式系統的最初階段,此時只有 Follower,沒有 Leader。Follower A 等待一個隨機的競選超時時間之後,沒收到 Leader 發來的心跳包,因此進入競選階段。

② 此時 A 發送投票請求給其它所有節點。

③ 其它節點會對請求進行回復,如果超過一半的節點回復了,那麼該 Candidate 就會變成 Leader。

④ 之後 Leader 會周期性地發送心跳包給 Follower,Follower 接收到心跳包,會重新開始計時。

① 如果有多個 Follower 成為 Candidate,並且所獲得票數相同,那麼就需要重新開始投票,例如下圖中 Candidate B 和 Candidate D 都獲得兩票,因此需要重新開始投票。

② 當重新開始投票時,由於每個節點設置的隨機競選超時時間不同,因此能下一次再次出現多個 Candidate 並獲得同樣票數的概率很低。

① 來自客戶端的修改都會被傳入 Leader。注意該修改還未被提交,只是寫入日誌中。

② Leader 會把修改復制到所有 Follower。

③ Leader 會等待大多數的 Follower 也進行了修改,然後才將修改提交。

④ 此時 Leader 會通知的所有 Follower 讓它們也提交修改,此時所有節點的值達成一致。

『叄』 redis最大多少個節點問題

轉自 https://blog.csdn.net/chenxuegui1234/article/details/100171599

現在redis集群架構,redis cluster用的會比較多。

如下圖所示

對於客戶端請求的key,根據公式HASH_SLOT=CRC16(key) mod 16384,計算出映射到哪個分片上,然後Redis會去相應的節點進行操作!

那大家思考過,為什麼有16384個槽么?

ps:CRC16演算法產生的hash值有16bit,該演算法可以產生2^16-=65536個值。換句話說,值是分布在0~65535之間。那作者在做mod運算的時候,為什麼不mod65536,而選擇mod16384?

其實我當初第一次思考這個問題的時候,我心裡是這么想的,作者應該是覺得16384就夠了,然後我就開始查這方面資料。

很幸運的是,這個問題,作者是給出了回答的!

地址如下: https://github.com/antirez/redis/issues/2576

作者原版回答如下:

The reason is:

So 16k was in the right range to ensure enough slots per master with a max of 1000 maters, but a small enough number to propagate the slot configuration as a raw bitmap easily. Note that in small clusters the bitmap would be hard to compress because when N is small the bitmap would have slots/N bits set that is a large percentage of bits set.

因此,能看懂上面那段話的讀者。這篇文章不用看了,因為作者講的很清楚了。本文只是對上面那段話做一些解釋而已。

我們回憶一下Redis Cluster的工作原理!

這里要先將節點握手講清楚。我們讓兩個redis節點之間進行通信的時候,需要在客戶端執行下面一個命令

<pre style="box-sizing: border-box; outline: 0px; margin: 0px 0px 24px; padding: 8px; font-weight: 400; position: relative; white-space: pre-wrap; overflow-wrap: break-word; overflow-x: auto; font-family: Consolas, Inconsolata, Courier, monospace; font-size: 14px; line-height: 22px; color: rgb(0, 0, 0); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;">127.0.0.1:7000>cluster meet 127.0.0.1:7001
</pre>

如下圖所示

意思很簡單,讓7000節點和7001節點知道彼此存在!

在握手成功後,兩個節點之間會 定期 發送ping/pong消息,交換 數據信息 ,如下圖所示。

在這里,我們需要關注三個重點。

到底在交換什麼數據信息?

交換的數據信息,由消息體和消息頭組成。

消息體無外乎是一些節點標識啊,IP啊,埠號啊,發送時間啊。這與本文關系不是太大,我不細說。

我們來看消息頭,結構如下

注意看紅框的內容,type表示消息類型。

另外,消息頭裡面有個myslots的char數組,長度為16383/8,這其實是一個bitmap,每一個位代表一個槽,如果該位為1,表示這個槽是屬於這個節點的。

到底數據信息究竟多大?

在消息頭中,最占空間的是myslots[CLUSTER_SLOTS/8]。這塊的大小是:

16384÷8÷1024=2kb

那在消息體中,會攜帶一定數量的其他節點信息用於交換。

那這個其他節點的信息,到底是幾個節點的信息呢?

約為集群總節點數量的1/10,至少攜帶3個節點的信息。

這里的重點是: 節點數量越多,消息體內容越大。

消息體大小是10個節點的狀態信息約1kb。

那定期的頻率是什麼樣的?

redis集群內節點,每秒都在發ping消息。規律如下

因此,每秒單節點發出ping消息數量為

1+10*num(node.pong_received>cluster_node_timeout/2)

那大致帶寬損耗如下所示,圖片來自《Redis開發與運維》

講完基礎知識以後,我們可以來看作者的回答了。

(1)如果槽位為65536,發送心跳信息的消息頭達8k,發送的心跳包過於龐大。

如上所述,在消息頭中,最占空間的是myslots[CLUSTER_SLOTS/8]。

當槽位為65536時,這塊的大小是:

65536÷8÷1024=8kb

因為每秒鍾,redis節點需要發送一定數量的ping消息作為心跳包,如果槽位為65536,這個ping消息的消息頭太大了,浪費帶寬。

(2)redis的集群主節點數量基本不可能超過1000個。

如上所述,集群節點越多,心跳包的消息體內攜帶的數據越多。如果節點過1000個,也會導致網路擁堵。因此redis作者,不建議redis cluster節點數量超過1000個。

那麼,對於節點數在1000以內的redis cluster集群,16384個槽位夠用了。沒有必要拓展到65536個。

(3)槽位越小,節點少的情況下,壓縮率高

Redis主節點的配置信息中,它所負責的哈希槽是通過一張bitmap的形式來保存的,在傳輸過程中,會對bitmap進行壓縮,但是如果bitmap的填充率slots / N很高的話(N表示節點數),bitmap的壓縮率就很低。

如果節點數很少,而哈希槽數量很多的話,bitmap的壓縮率就很低。

ps:文件壓縮率指的是,文件壓縮前後的大小比。

綜上所述,作者決定取16384個槽,不多不少,剛剛好!

『肆』 網路(五):socket



服務端socket要做五件事,客戶端socket要做三件事:

接下來雙方就可以通過 read() 和 write() 函數通信了,雙方也都可以通過 close() 函數主動斷開連接。


上面的例子中,我們預期的效果是客戶端點擊一次發送,給服務端發送兩條數據,服務端觸發兩次「收到客戶端數據的回調」,然後分別列印:

但實際上兩條數據被合並成一條數據發送給服務端了,服務端只觸發了一次「收到客戶端數據的回調」,也只列印了一次:

這就是 數據粘包——多條數據被合並成了一條數據傳輸。

我們知道TCP有個 發送緩存 ,有些情況下TCP並不是有一條數據就發一條數據,而是等發送緩存滿了,再把發送緩存里的多條數據一起發送出去,這就會導致數據粘包。

此外TCP還採用了 Nagle優化演算法 來打包數據,它會將多次間隔較小且數據量較小的數據自動合並成一個比較大的數據一塊兒傳輸,這也會導致數據粘包。

處理數據粘包也很簡單,核心思路就是: 發送方在發送數據的時候先給每條數據都添加一個包頭,包頭里存放的關鍵信息就是真實數據的長度,當然也可以存放更多的業務信息,此外包頭的尾部還需要拼接一個包頭結束標識——回車換行符,以便將來接收方讀取數據時可以根據這個包頭結束標識優先讀取到包頭數據。接收方調用指定的讀取方法優先讀取到包頭數據,然後根據包頭里的長度信息再去精準讀取指定長度的真實數據,這樣就可以讀取到一條完整的數據了,然後再讀取下一條數據就不會粘包了。

正常來說,socket連接一旦建立之後就會一直掛在那裡,直到某一端主動斷開連接。但實際上,運營商在檢測到鏈路上有一段時間無數據傳輸時,就會自動斷開這種處於非活躍狀態的連接,這就是所謂的運營商NAT超時,超時時間為5分鍾。 因此我們就需要做心跳保活——即客戶端每隔一定的時間間隔就向服務端發送一個心跳數據包,用來保證當前socket連接處於活躍狀態,避免運營商把我們的連接中斷,這個時間間隔我們取的是3分鍾,伺服器在收到心跳包時不當做真實數據處理即可。

客戶端主動斷開連接時(如App退出登錄或者App進入後台等場景),我們不需要做斷線重連;其它情況下如果連接斷開了(如伺服器出了問題或者網斷了等場景),我們就需要做斷線重連,來盡量使連接處於正常連接的狀態,這樣才能保證業務的正常運行。 具體做法就是,當客戶端檢測到跟服務端斷開連接時就啟動第一次斷線重連,2秒後啟動第二次斷線重連,再隔4秒後啟動第三次斷線重連,如果三次斷線重連還沒成功,就認為是伺服器出了問題,不再重連。

『伍』 為什麼redis集群的最大槽數是16384個

在redis節點發送心跳包時需要把所有的槽放到這個心跳包里,以便讓節點知道當前集群信息,16384=16k,在發送心跳包時使用 char 進行bitmap壓縮後是2k( 2 * 8 (8 bit) * 1024(1k) = 16K ),也就是說使用2k的空間創建了16k的槽數。

雖然使用CRC16演算法最多可以分配65535(2^16-1)個槽位,65535=65k,壓縮後就是8k( 8 * 8 (8 bit) * 1024(1k) =65K ),也就是說需要需要8k的心跳包,作者認為這樣做不太值得;並且一般情況下一個redis集群不會有超過1000個master節點,所以16k的槽位是個比較合適的選擇。

『陸』 Raft 演算法(詳細版)

在分布式系統中,一致性演算法至關重要。在所有一致性演算法中,Paxos 最負盛名,它由萊斯利·蘭伯特(Leslie Lamport)於 1990 年提出,是一種基於消息傳遞的一致性演算法,被認為是類似演算法中最有效的。

Paxos 演算法雖然很有效,但復雜的原理使它實現起來非常困難,截止目前,實現 Paxos 演算法的開源軟體很少,比較出名的有 Chubby、LibPaxos。此外,Zookeeper 採用的 ZAB(Zookeeper Atomic Broadcast)協議也是基於 Paxos 演算法實現的,不過 ZAB 對 Paxos 進行了很多改進與優化,兩者的設計目標也存在差異——ZAB 協議主要用於構建一個高可用的分布式數據主備系統,而 Paxos 演算法則是用於構建一個分布式的一致性狀態機系統。

由於 Paxos 演算法過於復雜、實現困難,極大地制約了其應用,而分布式系統領域又亟需一種高效而易於實現的分布式一致性演算法,在此背景下,Raft 演算法應運而生。

Raft 演算法在斯坦福 Diego Ongaro 和 John Ousterhout 於 2013 年發表的《In Search of an Understandable Consensus Algorithm》中提出。相較於 Paxos,Raft 通過邏輯分離使其更容易理解和實現,目前,已經有十多種語言的 Raft 演算法實現框架,較為出名的有 etcd、Consul 。

根據官方文檔解釋,一個 Raft 集群包含若干節點,Raft 把這些節點分為三種狀態:Leader、 Follower、Candidate,每種狀態負責的任務也是不一樣的。正常情況下,集群中的節點只存在 Leader 與 Follower 兩種狀態。

Leader(領導者) :負責日誌的同步管理,處理來自客戶端的請求,與Follower保持heartBeat的聯系;

Follower(追隨者) :響應 Leader 的日誌同步請求,響應Candidate的邀票請求,以及把客戶端請求到Follower的事務轉發(重定向)給Leader;

Candidate(候選者) :負責選舉投票,集群剛啟動或者Leader宕機時,狀態為Follower的節點將轉為Candidate並發起選舉,選舉勝出(獲得超過半數節點的投票)後,從Candidate轉為Leader狀態。

通常,Raft 集群中只有一個 Leader,其它節點都是 Follower。Follower 都是被動的,不會發送任何請求,只是簡單地響應來自 Leader 或者 Candidate 的請求。Leader 負責處理所有的客戶端請求(如果一個客戶端和 Follower 聯系,那麼 Follower 會把請求重定向給 Leader)。

為簡化邏輯和實現,Raft 將一致性問題分解成了三個相對獨立的子問題。

選舉(Leader Election) :當 Leader 宕機或者集群初創時,一個新的 Leader 需要被選舉出來;

日誌復制(Log Replication) :Leader 接收來自客戶端的請求並將其以日誌條目的形式復制到集群中的其它節點,並且強制要求其它節點的日誌和自己保持一致;

安全性(Safety) :如果有任何的伺服器節點已經應用了一個確定的日誌條目到它的狀態機中,那麼其它伺服器節點不能在同一個日誌索引位置應用一個不同的指令。

根據 Raft 協議,一個應用 Raft 協議的集群在剛啟動時,所有節點的狀態都是 Follower。由於沒有 Leader,Followers 無法與 Leader 保持心跳(Heart Beat),因此,Followers 會認為 Leader 已經下線,進而轉為 Candidate 狀態。然後,Candidate 將向集群中其它節點請求投票,同意自己升級為 Leader。如果 Candidate 收到超過半數節點的投票(N/2 + 1),它將獲勝成為 Leader。

第一階段:所有節點都是 Follower。

上面提到,一個應用 Raft 協議的集群在剛啟動(或 Leader 宕機)時,所有節點的狀態都是 Follower,初始 Term(任期)為 0。同時啟動選舉定時器,每個節點的選舉定時器超時時間都在 100~500 毫秒之間且並不一致(避免同時發起選舉)。

第二階段:Follower 轉為 Candidate 並發起投票。

沒有 Leader,Followers 無法與 Leader 保持心跳(Heart Beat),節點啟動後在一個選舉定時器周期內未收到心跳和投票請求,則狀態轉為候選者 Candidate 狀態,且 Term 自增,並向集群中所有節點發送投票請求並且重置選舉定時器。

注意,由於每個節點的選舉定時器超時時間都在 100-500 毫秒之間,且彼此不一樣,以避免所有 Follower 同時轉為 Candidate 並同時發起投票請求。換言之,最先轉為 Candidate 並發起投票請求的節點將具有成為 Leader 的「先發優勢」。

第三階段:投票策略。

節點收到投票請求後會根據以下情況決定是否接受投票請求(每個 follower 剛成為 Candidate 的時候會將票投給自己):

請求節點的 Term 大於自己的 Term,且自己尚未投票給其它節點,則接受請求,把票投給它;

請求節點的 Term 小於自己的 Term,且自己尚未投票,則拒絕請求,將票投給自己。

第四階段:Candidate 轉為 Leader。

一輪選舉過後,正常情況下,會有一個 Candidate 收到超過半數節點(N/2 + 1)的投票,它將勝出並升級為 Leader。然後定時發送心跳給其它的節點,其它節點會轉為 Follower 並與 Leader 保持同步,到此,本輪選舉結束。

注意:有可能一輪選舉中,沒有 Candidate 收到超過半數節點投票,那麼將進行下一輪選舉。

在一個 Raft 集群中,只有 Leader 節點能夠處理客戶端的請求(如果客戶端的請求發到了 Follower,Follower 將會把請求重定向到 Leader) ,客戶端的每一個請求都包含一條被復制狀態機執行的指令。Leader 把這條指令作為一條新的日誌條目(Entry)附加到日誌中去,然後並行得將附加條目發送給 Followers,讓它們復制這條日誌條目。

當這條日誌條目被 Followers 安全復制,Leader 會將這條日誌條目應用到它的狀態機中,然後把執行的結果返回給客戶端。如果 Follower 崩潰或者運行緩慢,再或者網路丟包,Leader 會不斷得重復嘗試附加日誌條目(盡管已經回復了客戶端)直到所有的 Follower 都最終存儲了所有的日誌條目,確保強一致性。

第一階段:客戶端請求提交到 Leader。

如下圖所示,Leader 收到客戶端的請求,比如存儲數據 5。Leader 在收到請求後,會將它作為日誌條目(Entry)寫入本地日誌中。需要注意的是,此時該 Entry 的狀態是未提交(Uncommitted),Leader 並不會更新本地數據,因此它是不可讀的。

第二階段:Leader 將 Entry 發送到其它 Follower

Leader 與 Followers 之間保持著心跳聯系,隨心跳 Leader 將追加的 Entry(AppendEntries)並行地發送給其它的 Follower,並讓它們復制這條日誌條目,這一過程稱為復制(Replicate)。

有幾點需要注意:

1. 為什麼 Leader 向 Follower 發送的 Entry 是 AppendEntries 呢?

因為 Leader 與 Follower 的心跳是周期性的,而一個周期間 Leader 可能接收到多條客戶端的請求,因此,隨心跳向 Followers 發送的大概率是多個 Entry,即 AppendEntries。當然,在本例中,我們假設只有一條請求,自然也就是一個Entry了。

2. Leader 向 Followers 發送的不僅僅是追加的 Entry(AppendEntries)。

在發送追加日誌條目的時候,Leader 會把新的日誌條目緊接著之前條目的索引位置(prevLogIndex), Leader 任期號(Term)也包含在其中。如果 Follower 在它的日誌中找不到包含相同索引位置和任期號的條目,那麼它就會拒絕接收新的日誌條目,因為出現這種情況說明 Follower 和 Leader 不一致。

3. 如何解決 Leader 與 Follower 不一致的問題?

在正常情況下,Leader 和 Follower 的日誌保持一致,所以追加日誌的一致性檢查從來不會失敗。然而,Leader 和 Follower 一系列崩潰的情況會使它們的日誌處於不一致狀態。Follower可能會丟失一些在新的 Leader 中有的日誌條目,它也可能擁有一些 Leader 沒有的日誌條目,或者兩者都發生。丟失或者多出日誌條目可能會持續多個任期。

要使 Follower 的日誌與 Leader 恢復一致,Leader 必須找到最後兩者達成一致的地方(說白了就是回溯,找到兩者最近的一致點),然後刪除從那個點之後的所有日誌條目,發送自己的日誌給 Follower。所有的這些操作都在進行附加日誌的一致性檢查時完成。

Leader 為每一個 Follower 維護一個 nextIndex,它表示下一個需要發送給 Follower 的日誌條目的索引地址。當一個 Leader 剛獲得權力的時候,它初始化所有的 nextIndex 值,為自己的最後一條日誌的 index 加 1。如果一個 Follower 的日誌和 Leader 不一致,那麼在下一次附加日誌時一致性檢查就會失敗。在被 Follower 拒絕之後,Leader 就會減小該 Follower 對應的 nextIndex 值並進行重試。最終 nextIndex 會在某個位置使得 Leader 和 Follower 的日誌達成一致。當這種情況發生,附加日誌就會成功,這時就會把 Follower 沖突的日誌條目全部刪除並且加上 Leader 的日誌。一旦附加日誌成功,那麼 Follower 的日誌就會和 Leader 保持一致,並且在接下來的任期繼續保持一致。

第三階段:Leader 等待 Followers 回應。

Followers 接收到 Leader 發來的復制請求後,有兩種可能的回應:

寫入本地日誌中,返回 Success;

一致性檢查失敗,拒絕寫入,返回 False,原因和解決辦法上面已做了詳細說明。

需要注意的是,此時該 Entry 的狀態也是未提交(Uncommitted)。完成上述步驟後,Followers 會向 Leader 發出 Success 的回應,當 Leader 收到大多數 Followers 的回應後,會將第一階段寫入的 Entry 標記為提交狀態(Committed),並把這條日誌條目應用到它的狀態機中。

第四階段:Leader 回應客戶端。

完成前三個階段後,Leader會向客戶端回應 OK,表示寫操作成功。

第五階段,Leader 通知 Followers Entry 已提交

Leader 回應客戶端後,將隨著下一個心跳通知 Followers,Followers 收到通知後也會將 Entry 標記為提交狀態。至此,Raft 集群超過半數節點已經達到一致狀態,可以確保強一致性。

需要注意的是,由於網路、性能、故障等各種原因導致「反應慢」、「不一致」等問題的節點,最終也會與 Leader 達成一致。

前面描述了 Raft 演算法是如何選舉 Leader 和復制日誌的。然而,到目前為止描述的機制並不能充分地保證每一個狀態機會按照相同的順序執行相同的指令。例如,一個 Follower 可能處於不可用狀態,同時 Leader 已經提交了若乾的日誌條目;然後這個 Follower 恢復(尚未與 Leader 達成一致)而 Leader 故障;如果該 Follower 被選舉為 Leader 並且覆蓋這些日誌條目,就會出現問題,即不同的狀態機執行不同的指令序列。

鑒於此,在 Leader 選舉的時候需增加一些限制來完善 Raft 演算法。這些限制可保證任何的 Leader 對於給定的任期號(Term),都擁有之前任期的所有被提交的日誌條目(所謂 Leader 的完整特性)。關於這一選舉時的限制,下文將詳細說明。

在所有基於 Leader 機制的一致性演算法中,Leader 都必須存儲所有已經提交的日誌條目。為了保障這一點,Raft 使用了一種簡單而有效的方法,以保證所有之前的任期號中已經提交的日誌條目在選舉的時候都會出現在新的 Leader 中。換言之,日誌條目的傳送是單向的,只從 Leader 傳給 Follower,並且 Leader 從不會覆蓋自身本地日誌中已經存在的條目。

Raft 使用投票的方式來阻止一個 Candidate 贏得選舉,除非這個 Candidate 包含了所有已經提交的日誌條目。Candidate 為了贏得選舉必須聯系集群中的大部分節點。這意味著每一個已經提交的日誌條目肯定存在於至少一個伺服器節點上。如果 Candidate 的日誌至少和大多數的伺服器節點一樣新(這個新的定義會在下面討論),那麼它一定持有了所有已經提交的日誌條目(多數派的思想)。投票請求的限制中請求中包含了 Candidate 的日誌信息,然後投票人會拒絕那些日誌沒有自己新的投票請求。

Raft 通過比較兩份日誌中最後一條日誌條目的索引值和任期號,確定誰的日誌比較新。如果兩份日誌最後條目的任期號不同,那麼任期號大的日誌更加新。如果兩份日誌最後的條目任期號相同,那麼日誌比較長的那個就更加新。

如同 4.1 節介紹的那樣,Leader 知道一條當前任期內的日誌記錄是可以被提交的,只要它被復制到了大多數的 Follower 上(多數派的思想)。如果一個 Leader 在提交日誌條目之前崩潰了,繼任的 Leader 會繼續嘗試復制這條日誌記錄。然而,一個 Leader 並不能斷定被保存到大多數 Follower 上的一個之前任期里的日誌條目 就一定已經提交了。這很明顯,從日誌復制的過程可以看出。

鑒於上述情況,Raft 演算法不會通過計算副本數目的方式去提交一個之前任期內的日誌條目。只有 Leader 當前任期里的日誌條目通過計算副本數目可以被提交;一旦當前任期的日誌條目以這種方式被提交,那麼由於日誌匹配特性,之前的日誌條目也都會被間接的提交。在某些情況下,Leader 可以安全地知道一個老的日誌條目是否已經被提交(只需判斷該條目是否存儲到所有節點上),但是 Raft 為了簡化問題使用了一種更加保守的方法。

當 Leader 復制之前任期里的日誌時,Raft 會為所有日誌保留原始的任期號,這在提交規則上產生了額外的復雜性。但是,這種策略更加容易辨別出日誌,即使隨著時間和日誌的變化,日誌仍維護著同一個任期編號。此外,該策略使得新 Leader 只需要發送較少日誌條目。

raft 的讀寫都在 leader 節點中進行,它保證了讀的都是最新的值,它是符合強一致性的(線性一致性),raft 除了這個還在【客戶端交互】那塊也做了一些保證,詳情可以參考論文。但是 zookeeper 不同,zookeeper 寫在 leader,讀可以在 follower 進行,可能會讀到了舊值,它不符合強一致性(只考慮寫一致性,不考慮讀一致性),但是 zookeeper 去 follower 讀可以有效提升讀取的效率。

對比於 zab、raft,我們發現他們選舉、setData 都是需要過半機制才行,所以他們針對網路分區的處理方法都是一樣的。

一個集群的節點經過網路分區後,如一共有 A、B、C、D、E 5個節點,如果 A 是 leader,網路分區為 A、B、C 和 D、E,在A、B、C分區還是能正常提供服務的,而在 D、E 分區因為不能得到大多數成員確認(雖然分區了,但是因為配置的原因他們還是能知道所有的成員數量,比如 zk 集群啟動前需要配置所有成員地址,raft 也一樣),是不能進行選舉的,所以保證只會有一個 leader。

如果分區為 A、B 和 C、D、E ,A、B 分區雖然 A 還是 leader,但是卻不能提供事務服務(setData),C、D、E 分區能重新選出 leader,還是能正常向外提供服務。

1)我們所說的日誌(log)與狀態機(state machine)不是一回事,日誌指還沒有提交到狀態機中的數據。
2)新 leader 永遠不會通過計算副本數量提交舊日誌,他只能復制舊日誌都其他 follower 上,對於舊日誌的提交,只能是新 leader 接收新的寫請求寫新日誌,順帶著把舊日誌提交了。

『柒』 RocketMQ(三)——系統架構

RocketMQ架構上主要分為四部分構成:

消息生產者,負責生產消息。Procer通過MQ的負載均衡模塊選擇相應的Broker集群隊列進行消息投遞,投遞的過程支持快速失敗並且低延遲

RocketMQ中的消息生產者都是以生產者組(Procer Group)的形式出現的。生產者組是同一類生產者的集合,這類Procer發送相同Topic類型的消息。一個生產者組可以同時發送多個主題的消息。

消息消費者,負責消費消息。一個消息消費者會從Broker伺服器中獲取到消息,並對消息進行相關業務處理

RocketMQ中的消息消費者都是以消費者組(Consumer Group)的形式出現的。消費者組是統一類消費者的集合,這類Consumer消費的是同一個Topic類型的消息。消費者組使得在消息消費方法,實現負載均衡(講一個Topic中不同的Queue平均分配給同一個Consumer Group的不同Consumer,並不是負載均衡)和容錯(一個Consumer掛了,該Consumer Group中的其他Consumer可以接著消費元Consumer消費的Queue)的目標變得非常容易

消費者組中Consumer的數量應小於等於Topic的Queue數量。如果超出Queue數量,則多出的Consumer將不能消費消息。

不過一個Topic類型的消息可以被多個消費者組同時消費。

NameServer是一個Broker與Topic路由的注冊中心,支持Broker的動態注冊與發現。
RocketMQ的思想來自於Kafuka,而Kafka是以來了Zookeeper的。所以,在RocketMQ的早期版本也依賴Zookeeper。從3.0開始去掉了Zookeeper的依賴,使用了自己的NameServer。

NameServer通常也是以集群的方式部署,不過,NameServer是無狀態的,即NameServer集群中的各個節點之間是無差異的,各個節點相互不進行信息通訊。那各個節點中的數據是如何進行數據同步的呢?在Broker節點啟動時,輪詢NameServer列表,與每個NameServer節點建立長連接,發起注冊請求。在NameServer內部維護者一個Broker列表,用來動態存儲Broker信息

Broker節點為了證明自己是活著的,為了維護與NameServer間的長連接,會將最新的信息以心跳包的方式上報給NameServer,每30秒發送一次心跳。心跳包中包含BrokerId、Broker地址(IP+Port)、Broker名稱、Broker所屬集群名稱等等。NameServer在接收到心跳包後,會更新心跳時間戳,記錄這個Broker的最新存活時間。

由於Broker關機、宕機或網路抖動等原因,NameServer沒有收到Broker的心跳,NameServer可能會將其從Broker列表中剔除
NameServer中有一個定時任務,每隔10秒就會掃描一次Broker表,查看每一個Broker的最新心跳時間戳距離當前時間是否超過120秒,如果超過,則會判定Broekr失效,然後將其從Broker列表中剔除。

RocketMQ的路由發現採用的是Pull模型。當Topic路由信息出現變化時,NameServer不會主動推送給客戶端,而是客戶端定時拉取最新的路由。默認每30秒拉取一次最新的路由

客戶端再配置時必須要寫上NameServer集群的地址,那麼客戶端道理連接在哪個NameServer節點呢?客戶端首先會生產一個隨機數,然後再與NameServer節點數取模,此時得到的就是要連接的節點索引,然後就會進行連接。如果連接失敗,則會採用round-robin策略,逐個嘗試去連接其他節點。
首先採用的是 隨機策略 進行選擇,失敗後採用的是輪詢策略。

Broker充當著消息中轉角色,負責存儲消息、轉發消息。Broker在RocketMQ系統中負責接收並存儲從生產者發送來的消息,同時為消費者的拉取請求作準備。Broker同時也存儲著消息相關的元數據,包括消費者組、消費進度偏移offset、主題、隊列等

Remoting Mole :整個Broker的實體,負責處理來自clients端的請求。而這個Broker實體則由以下模塊構成。
Client Manager :客戶端管理器。負責接收、解析客戶端(Procer/Consumer)請求,管理客戶端。
Store Service :存儲服務。提供方便簡單的API介面,處理消息存儲到物理硬碟和消息查詢功能。
HA Service :高可用服務,提供Master Broker和Slave Broker之間的數據同步功能。
Index Service :索引服務。根據特定的Message Key,對投遞到Broker的消息進行索引服務,同時也提供根據Message Key對消息進行快速查詢的功能

為了增強Broker性能與吞吐量,Broker一般都是以集群形式出現的。各集群節點中可能存放著相同Topic的不同Queue。
如果某Broker節點宕機,如何保證數據不丟失呢?
其解決方案是,將每個Broekr集群節點進行橫向擴展,即將Broker節點再建為一個HA集群,解決單點問題。
Broker節點集群是一個主從集群,即有Master和Slave兩種角色。Master負責處理讀寫操作請求,Slave負責對Master中的數據進行備份。當Master掛掉了,Slaver會自動切換為Master去工作。所以這個Broker集群式主備集群。Master與Slave的對應關系是通過指定相同的BrokerName、不同的BrokerId來確定的。BrokerId為0表示Master,非0表示Slave。每個Broker與NameServer集群中的所有節點建立長連接,定時注冊Topic信息到所有NameServer。

①啟動NameServer,NameServer啟動後開始監聽埠,等待Broker、Procer、Consumer連接
②啟動Broker時,Broker會與所有的NameServer保持長連接,每30秒向NameServer定時發送心跳包
③發送消息前,可以先創建Topic ,創建Topic時需要指定該Topic要存儲在哪些Broker上,當然,在創建Topic時也會將Topic與Broker的關系寫入到NameServer中。也可以在發送消息時自動創建Topic。
④Procer發送消息,啟動時先跟NameServer集群中的其中一台建立長連接,並從NameServer中獲取路由信息,即當前發送Topic的Queue與Broker地址的映射關系。然後根據演算法策略從隊選擇一個Queue,與隊列所在的Broker建立長連接從而向Broker發送消息。
⑤Consumer與Procer類似,跟其中一台NameServer建立長連接,獲取其所訂閱Topic的路由信息,然後根據演算法策略從路由信息中獲取到其要消費的Queue,然後與Broker建立長連接,消費其中的消息。Consumer會向Broker發送心跳,以確保Broker的存活狀態

手動創建Topic時,有兩種模式:

自動創建Topic時,默認採用的是Broker模式,會為每個Broker默認創建四個Queue

從物理上講,讀/寫隊列是同一個隊列。所以,不存在讀/寫隊列數據同步問題。讀/寫隊列是邏輯上進行區分的概念 。一般來說,讀/寫隊列數量是相同的。

讀/寫隊列數量不同是有問題的。
但這樣可以方便縮容
perm用於設置對當前創建Topic的操作許可權:2表示只寫,4表示只讀,6表示讀寫

『捌』 如何正確計算自己的減脂心率

1、初級公式:針對健康狀況較差的人群。目標心率=(200-年齡)*(60%~80%)60%~70%主要用於減脂;70%~80%主要用於提高心肺功能

補充一點自我感覺自我感覺是掌握運動量和運動強度的重要指標,包括輕度呼吸急促、感到有點心跳、周身微熱、面色微紅、津津小汗,這表明運動適量;

如果有明顯的心慌、氣短、心口發熱、頭暈、大汗、疲憊不堪,表明運動超限;

如果你的運動始終保持在「面不改色心不跳」的程度,心率距「靶心率」相差太遠,那就說明你的鍛煉不可能達到增強體質和耐力的目的,還需要再加點量。

『玖』 一文吃透 WebSocket 原理

踩著年末的尾巴,提前布局來年,為來年的工作做個好的鋪墊,所以就開始了面試歷程,因為項目中使用到了 WebSocket ,面試官在深挖項目經驗的時候,也難免提到 WebSocket 相關的知識點,因為之前並沒有考慮這么深,所以,回答的還是有所欠缺,因此,趕緊趁熱再熟悉熟悉,也藉此機會,整理出來供大家咀嚼,每個項目都有其值得挖掘的閃光點,要用有愛的眼睛去發現。

WebSocket 是一種在單個TCP連接上進行全雙工通信的協議。 WebSocket 使得客戶端和伺服器之間的數據交換變得更加簡單,允許服務端主動向客戶端推送數據。

在 WebSocket API 中,瀏覽器和伺服器只需要完成一次握手,兩者之間就直接可以創建持久性的連接, 並進行雙向數據傳輸。(維基網路)

WebSocket 本質上一種計算機網路應用層的協議,用來彌補 http 協議在持久通信能力上的不足。

WebSocket 協議在2008年誕生,2011年成為國際標准。現在最新版本瀏覽器都已經支持了。

它的最大特點就是,伺服器可以主動向客戶端推送信息,客戶端也可以主動向伺服器發送信息,是真正的雙向平等對話,屬於伺服器推送技術的一種。

WebSocket 的其他特點包括:


我們已經有了 HTTP 協議,為什麼還需要另一個協議?它能帶來什麼好處?

因為 HTTP 協議有一個缺陷:通信只能由客戶端發起,不具備伺服器推送能力。

舉例來說,我們想了解查詢今天的實時數據,只能是客戶端向伺服器發出請求,伺服器返回查詢結果。HTTP 協議做不到伺服器主動向客戶端推送信息。

這種單向請求的特點,註定了如果伺服器有連續的狀態變化,客戶端要獲知就非常麻煩。我們只能使用"輪詢":每隔一段時候,就發出一個詢問,了解伺服器有沒有新的信息。最典型的場景就是聊天室。輪詢的效率低,非常浪費資源(因為必須不停連接,或者 HTTP 連接始終打開)。

在 WebSocket 協議出現以前,創建一個和服務端進雙通道通信的 web 應用,需要依賴HTTP協議,進行不停的輪詢,這會導致一些問題:

http 協議本身是沒有持久通信能力的,但是我們在實際的應用中,是很需要這種能力的,所以,為了解決這些問題, WebSocket 協議由此而生,於2011年被IETF定為標准RFC6455,並被RFC7936所補充規范。並且在 HTML5 標准中增加了有關 WebSocket 協議的相關 api ,所以只要實現了 HTML5 標準的客戶端,就可以與支持 WebSocket 協議的伺服器進行全雙工的持久通信了。

WebSocket 與 HTTP 的關系圖:

下面一張圖說明了 HTTP 與 WebSocket 的主要區別:

不同點:

與http協議一樣, WebSocket 協議也需要通過已建立的TCP連接來傳輸數據。具體實現上是通過http協議建立通道,然後在此基礎上用真正 WebSocket 協議進行通信,所以WebSocket協議和http協議是有一定的交叉關系的。首先, WebSocket 是一個持久化的協議,相對於 HTTP 這種非持久的協議來說。簡單的舉個例子吧,用目前應用比較廣泛的 PHP 生命周期來解釋。

HTTP 的生命周期通過 Request 來界定,也就是一個 Request 一個 Response ,那麼在 HTTP1.0 中,這次 HTTP 請求就結束了。

在 HTTP1.1 中進行了改進,使得有一個 keep-alive,也就是說,在一個 HTTP 連接中,可以發送多個 Request,接收多個 Response。但是請記住 Request = Response, 在 HTTP 中永遠是這樣,也就是說一個 Request 只能有一個 Response。而且這個 Response 也是被動的,不能主動發起。首先 WebSocket 是基於 HTTP 協議的,或者說借用了 HTTP 協議來完成一部分握手。

首先我們來看個典型的 WebSocket 握手

熟悉 HTTP 的童鞋可能發現了,這段類似 HTTP 協議的握手請求中,多了這么幾個東西。

這個就是 WebSocket 的核心了,告訴 Apache 、 Nginx 等伺服器:注意啦,我發起的請求要用 WebSocket 協議,快點幫我找到對應的助理處理~而不是那個老土的 HTTP 。

這里開始就是 HTTP 最後負責的區域了,告訴客戶,我已經成功切換協議啦~

依然是固定的,告訴客戶端即將升級的是 WebSocket 協議,而不是 mozillasocket ,lurnarsocket 或者 shitsocket 。

然後, Sec-WebSocket-Accept 這個則是經過伺服器確認,並且加密過後的 Sec-WebSocket-Key 。伺服器:好啦好啦,知道啦,給你看我的 ID CARD 來證明行了吧。後面的, Sec-WebSocket-Protocol 則是表示最終使用的協議。至此,HTTP 已經完成它所有工作了,接下來就是完全按照 WebSocket 協議進行了。總結, WebSocket 連接的過程是:

優點:

缺點:

心跳就是客戶端定時的給服務端發送消息,證明客戶端是在線的, 如果超過一定的時間沒有發送則就是離線了。

當客戶端第一次發送請求至服務端時會攜帶唯一標識、以及時間戳,服務端到db或者緩存去查詢改請求的唯一標識,如果不存在就存入db或者緩存中, 第二次客戶端定時再次發送請求依舊攜帶唯一標識、以及時間戳,服務端到db或者緩存去查詢改請求的唯一標識,如果存在就把上次的時間戳拿取出來,使用當前時間戳減去上次的時間, 得出的毫秒秒數判斷是否大於指定的時間,若小於的話就是在線,否則就是離線;

通過查閱資料了解到 nginx 代理的 websocket 轉發,無消息連接會出現超時斷開問題。網上資料提到解決方案兩種,一種是修改nginx配置信息,第二種是 websocket 發送心跳包。下面就來總結一下本次項目實踐中解決的 websocket 的斷線 和 重連 這兩個問題的解決方案。主動觸發包括主動斷開連接,客戶端主動發送消息給後端

主動斷開連接,根據需要使用,基本很少用到。

下面主要講一下客戶端也就是前端如何實現心跳包:

首先了解一下心跳包機制

跳包之所以叫心跳包是因為:它像心跳一樣每隔固定時間發一次,以此來告訴伺服器,這個客戶端還活著。事實上這是為了保持長連接,至於這個包的內容,是沒有什麼特別規定的,不過一般都是很小的包,或者只包含包頭的一個空包。

在 TCP 的機制裡面,本身是存在有心跳包的機制的,也就是 TCP 的選項: SO_KEEPALIVE 。系統默認是設置的2小時的心跳頻率。但是它檢查不到機器斷電、網線拔出、防火牆這些斷線。而且邏輯層處理斷線可能也不是那麼好處理。一般,如果只是用於保活還是可以的。

心跳包一般來說都是在邏輯層發送空的 echo 包來實現的。下一個定時器,在一定時間間隔下發送一個空包給客戶端,然後客戶端反饋一個同樣的空包回來,伺服器如果在一定時間內收不到客戶端發送過來的反饋包,那就只有認定說掉線了。

在長連接下,有可能很長一段時間都沒有數據往來。理論上說,這個連接是一直保持連接的,但是實際情況中,如果中間節點出現什麼故障是難以知道的。更要命的是,有的節點(防火牆)會自動把一定時間之內沒有數據交互的連接給斷掉。在這個時候,就需要我們的心跳包了,用於維持長連接,保活。

心跳檢測步驟:

針對這種異常的中斷解決方案就是處理重連,下面我們給出的重連方案是使用js庫處理:引入 reconnecting-websocket.min.js ,ws建立鏈接方法使用js庫api方法:

斷網監測支持使用js庫: offline.min.js

以上方案,只是拋磚引玉,如果大家有更好的解決方案歡迎評論區分享交流。

WebSocket 是為了在 web 應用上進行雙通道通信而產生的協議,相比於輪詢HTTP請求的方式,WebSocket 有節省伺服器資源,效率高等優點。WebSocket 中的掩碼是為了防止早期版本中存在中間緩存污染攻擊等問題而設置的,客戶端向服務端發送數據需要掩碼,服務端向客戶端發送數據不需要掩碼。WebSocket 中 Sec-WebSocket-Key 的生成演算法是拼接服務端和客戶端生成的字元串,進行SHA1哈希演算法,再用base64編碼。WebSocket 協議握手是依靠 HTTP 協議的,依靠於 HTTP 響應101進行協議升級轉換。

『拾』 Uber實時推送平台是如何打造的

原文:Uber』s Real-Time Push Platform

譯者:LZM

Uber 建立的出行平台每天在處理全球數以百萬計的打車訂單。

實時打車市場是一個十分活躍的市場。一次行程包括多個參與者(乘客、司機),他們需要能在 APP 上實時查看、修改當前旅程的狀態。因此,Uber 需要保證每個參與者和他們的 APP 實時同步相關信息,無論是接車時間、達到時間還是行駛路線和附近的司機。

今天,手機端呈現的功能日益豐富,而這些功能對實時信息同步的需求也逐漸增多。本文將介紹 Uber 工程團隊如何把 Uber 平台信息同步機制從輪詢轉為基於 gRPC 的雙向消息流協議。

在 Uber 後台,一個行程連接了現實世界中的乘客和司機。在行程過程中,這兩個實體需要實時更新後台系統的信息。

我們思考一個場景:乘客發出打車請求,而司機在系統上等待接單。Uber 配對系統在後台自動匹配二者,向司機發送訂單。到此為止,每一方(乘客、司機、後台)應該彼此同步他們的內容。

如果一個新訂單帶來,司機 APP 會每隔幾秒輪詢一次信息以及時更新訂單狀態。與此同時,乘客 APP 也會每隔幾秒輪詢一個信息來查看司機時候接單。

輪詢的頻率由數據改變的速率決定。對於一個大型 APP(例如 Uber APP),這個變化速率從幾秒到幾個小時不等,變化范圍十分寬泛。

80% 的後台 API 請求都是來自客戶端的輪詢請求。激進的輪詢策略能讓 APP 的消息保持最新,但也會導致伺服器資源耗盡。任何輪詢過程中的 bug 都可能頻繁導致後台負載顯著加劇,甚至崩潰。隨著需要動態實時數據的功能的增加,這個方法變得不再可行。

輪詢會導致更快的電池消耗、應用程序延遲和網路級擁塞。這在城市中使用 2G/3G 網路或網路不穩定的地方尤其明顯。在這些地方,應用程序每次嘗試拉取信息時,都會重試多次。

隨著功能增加,開發者們嘗試重載輪詢 API 或重建一個新的 API。在高峰期,APP 同時向多個 API 發送輪詢請求。每個 API 負載數個功能。這些輪詢 API 本質上成為一組分片負載 API。但是,在 API 級別上保持一致性和邏輯分離仍然是一個越來越大的挑戰。

冷啟動問題是其中最具挑戰性的問題之一。每當 APP 啟動,所有功能都希望從後台獲取最新狀態,以渲染用戶界面。這導致多個 API 並發競爭,APP 不能成功渲染出正常界面,直到關鍵組件的消息被返回。在沒有優先順序的情況下,因為所有的 API 都有一些關鍵信息,所以應用載入時間會持續增加。糟糕的網路條件會進一步惡化冷啟動問題。

很明顯,我們需要一個徹頭徹尾的、對消息同步機制的改變。我們開啟了建立一個全新的實時推送平台的旅程。在這個平台上,伺服器可以根據需要向應用程序發送數據。當我們採用這種新架構時,我們發現效率有顯著的改進,同時也解決了不同的問題和挑戰。

接下來,來看看我們對推送平台的幾代改進以及該平台是如何演變的。

雖然使用消息推送是取代輪詢的自然選擇,但在如何構建推送機制上有很多需要考慮的問題。四個主要設計原則如下:

1)從輪詢到推送的簡單遷移

目前存在大量端設備在進行輪詢。新系統必須利用現有的、分配給輪詢 API 的負載和邏輯,而不是完全推倒重來。

2)簡易開發

與開發輪詢 API 相比,開發人員在推送數據方面不應該做截然不同的事情。

3)可靠性

所有消息應該通過網路可靠地發送到客戶的 APP 上,並在發送失敗時重試。

4)高效率

隨著 Uber 在發展中國家的迅速發展,數據使用成本對我們的用戶來說是一個挑戰,對於每天要在 Uber 平台上呆上幾個小時的司機來說尤其如此。新協議必須最小化伺服器和移動應用程序之間的數據傳輸量。

我們將這個消息推送系統命名為 RAMEN (Realtime Asynchronous MEssaging Network,實時非同步消息網路)。

任何時候,實時信息都在變化。消息的生命周期開始於決定生成一條信息的那一刻。微服務 Fireball 用於決定何時推送消息。很大部分決策都由配置文件決定。Fireball 在系統間監聽多種類型的事件,並決定是否推送給該消息涉及的客戶。

例如,當一個司機加單,司機和行程的狀態都會改變,並觸發 Fireball。之後,根據配置文件的內容,Fireball 決定何類消息應該推送給客戶。通常,一個觸發器會向多個用戶發送多個消息。

任何事件都可能被觸發器捕獲,例如一些客戶行為(如發出打車請求、打開 APP)、定時器到期、消息匯流排上的後端業務事件或是地理上的駛出 / 駛入事件。所有這些觸發器都被過濾並轉換為對各種後台 API 的調用。這些 API 需要客戶的上下文信息,如設備定位、設備的操作系統以及 APP 的版本號,來生成一個響應。Fireball 獲取設備上下文 RAMEN 伺服器,並在調用 API 時將它們添加到頭部。

所有來自 Uber APP 的伺服器調用都由我們的 API 網關提供。推送有效負載以同樣的方式生成。一旦 Fireball 決定了推送消息的對象和時間,API 網關就負責決定推送什麼。網關會調用各類域服務來生成正確的推送負載。

網關中的所有 API 在如何生成有效負載方面是相似的。這些 API 分為拉取式和推送式兩種。。拉取式 API 由移動設備調用來執行任何 HTTP 操作。推送 API 由 Fireball 調用,它有一個額外的 「推送」 中間件,可以攔截拉取式 API 的響應,並將其轉發給推送消息系統。

將 API 網關介乎於二者之間有以下好處:

l  拉式和推式 API 共享端設備上的大部分業務邏輯。一個給定的負載可以從拉式 API 無縫切換到推式 API。例如,無論你的 APP 是通過拉式 API 調用拉出一個客戶對象,還是 Fireball 通過推式 API 調用發送一個客戶對象,他們都使用相同的邏輯。

l  網關負責處理大量業務邏輯,如推送消息的速率、路由和消息驗證。

在適當的時候,Fireball 和網關一起生成發送給客戶的推送消息。負責將這些信息傳遞到移動設備的是 「推送消息傳遞系統」。

每條消息推送會根據不同的配置執行,這些配置項包括:

1)優先順序

由於為不同的用例生成了數百個不同的消息負載,因此需要對發送到 APP 的內容進行優先排序。我們將在下一節中看到,我們採用的協議限制在單個連接上發送多個並發負載。此外,接收設備的帶寬是有限的。為了給人一種相對優先順序的感覺,我們將信息大致分為三個不同的優先順序:

l  高優先順序:核心功能數據

l  中優先順序:其他有助於提升客戶體驗的功能數據

l  低優先順序:需要發送的數據規模大且使用頻率不高

優先順序配置用於管理平台的多種行為。例如,連接建立後,消息按照優先順序降序排列在套接字(socket)中。在 RPC 失敗的情況下,通過伺服器端重試,高優先順序消息變得更加可靠,並且支持跨區域復制。

2)存活時間

推送消息是為了改善實時體驗。因此,每個消息都有一個預先定義的生存時間,從幾秒到半個小時不等。消息傳遞系統將消息持久化並在發生錯誤時重試傳遞消息,直到有效值過期為止。

3)去重復

當通過觸發器機制或重傳機制多次生成相同的消息時,此配置項確定是否應該刪除重復的消息推送。對於我們的大多數用例,發送給定類型的最新推送消息足以滿足用戶體驗,這允許我們降低總體數據傳輸速率。

消息推送系統的最後一個組件是實際的有效負載交付服務。該服務維持著與世界各地數百萬 APP 程序的活躍連接,並在它們到達時將有效信息同步。世界各地的移動網路提供了不同級別的可靠性,因此傳輸系統需要足夠魯棒以適應故障。我們的系統保證 「至少一次」 交貨。

為了保證可靠傳輸,我們必須基於 TCP 協議,建立從應用程序到數據中心的持久連接。對於 2015 年的一個應用協議,我們的選擇是使用帶有長輪詢、網路套接字或最終伺服器發送事件 (SSE) 的 HTTP/1.1。

基於各種考慮,如安全性、移動 SDK 的支持和數據大小的影響,我們決定使用 SSE。Uber 已經支持了 HTTP + JSON API 棧,它的簡單性和可操作性使它成為我們當時的選擇。

然而,SSE 是一種單向協議,即數據只能從伺服器發送到應用程序。為了提供之前提到的 「至少一次」 的保障,需要確認和重傳機制以構建到應用程序協議之上的交付協議中。在 SSE 的基礎上,我們定義了一個非常優雅和簡單的協議方案。

客戶端開始接收第一個 HTTP 請求的消息 /ramen/receive?seq=0,在任何新會話開始時序列號為 0。伺服器以 HTTP 200 和 「Content-Type: text/event-stream」 響應客戶端以維護 SSE 連接。接下來,伺服器將按照優先順序降序發送所有掛起的消息並依次遞增序列號。由於底層傳輸協議是 TCP 協議,如果沒有交付帶有 seq#3 的消息,那麼該連接應該已斷開、超時或失敗。

客戶端期望在下一個看到的帶有最大序列號重新連接 (在本例中 seq=2)。這就告訴了伺服器,即使編號 3 寫到了套接字上,它也沒有被正常傳遞。然後伺服器將重新發送相同的消息或以 seq=3 開始的任何更高優先順序的消息。該協議構建了流連接所需的可恢復性,伺服器負責大部分的存儲工作,在客戶端實現起來非常簡單。

為了獲知鏈接是否存活,伺服器每 4 秒會發送一個心跳包,這類數據包大小隻有一個比特。如果超過 7 秒沒有收到來自伺服器的消息或心跳,客戶端會認定服務終端並重新發起鏈接。

在上面的協議中,每當客戶端重新以一個更高的序列號發起連接時,它就充當伺服器刷新舊消息的確認機制。在一個環境良好的網路中,用戶可能會保持連接數分鍾,從而導致伺服器不斷積累舊消息。為了緩解這個問題,應用程序會每 30 秒一次調用 /ramen/ack?seq=N,不管連接質量如何。協議的簡單性允許用許多不同的語言和平台非常快速地編寫客戶端。

在設備上下文存儲上,RAMEN 伺服器在每次建立連接時存儲設備上下文,並將此上下文暴露給 Fireball。每個設備上下文的 id 是用戶及其設備參數對應的唯一哈希值。這允許隔離推送消息,即使用戶在不同的設置下同時使用多個設備或應用程序。

第一代 RAMEN 伺服器使用 Node.js 編寫,並使用 Uber 內部的一致性哈西 / 分片框架 Ringpop。Ringpop 是一個去中心化的分片系統。所有連接都使用用戶的 UUID 進行分片,並使用 Redis 作為持久性數據存儲。

在接下來的一年半時間里,消息推送平台在整個公司得到了廣泛的應用。高峰期時,RAMEN 系統通過維持高達 60 萬個並發數據流連接,每秒向三種不同類型的應用程序推送超過 70000 個 QPS 消息。該系統很快成為伺服器 - 客戶端 API 基礎結構中最重要的部分。

隨著通信量和持久連接的快速增加,我們的技術選擇也需要擴展。基於 Ringpop 的分布式分片是一個非常簡單的架構,不會隨著 ring 中的節點數量的增加而動態擴展。Ringpop 庫使用一種 gossip 協議來評估成員資格。gossip 協議的收斂時間也隨著環的大小增加而增加。

此外,Node.js 是單線程的,並且會有更高級別的事件循環延遲,從而進一步延遲成員信息的收斂。這些問題可能引發拓撲信息不一致,進而導致消息丟失、超時和錯誤。

2017 年初,我們決定重新啟動 RAMEN 協議的伺服器實現,以繼續擴大應用規模。在這次迭代中,我們使用了以下技術:Netty、Apache Zookeeper、Apache Helix、Redis 和 Apache Cassandra。

1)Netty: Netty 是一個用於構建網路伺服器和客戶端的高性能庫。Netty 的 bytebuf 允許零拷貝緩沖區,這使得系統非常高效。

2)Apache ZooKeeper: Apache ZooKeeper 對網路連接進行一致性哈希,可以直接傳輸數據,不需要任何存儲層。但是與分散的拓撲管理不同,我們選擇了 ZooKeeper 的集中共享。ZooKeeper 是一個非常強大的分布式同步和配置管理系統,可以快速檢測連接節點的故障。

3)Apache Helix: Helix 是一個健壯的集群管理框架,運行在 ZooKeeper 之上,允許定義自定義拓撲和重新平衡演算法。它還很好地從核心業務邏輯中抽象出拓撲邏輯。它使用 ZooKeeper 來監控已連接的工作者,並傳播分片狀態信息的變化。它還允許我們編寫一個自定義的 Leader-Follower 拓撲和自定義的漸進再平衡演算法。

4)Redis 和 Apache Cassandra: 當我們為多區域雲架構做准備時,有必要對消息進行正確的復制和存儲。Cassandra 是一個持久的跨區域復制存儲。Redis 被用作 Cassandra 之上的容量緩存,以避免分片系統在部署或故障轉移事件中常見的群發問題。

5)Streamgate: 這個服務在 Netty 上實現了 RAMEN 協議,並擁有所有與處理連接、消息和存儲相關的邏輯。該服務還實現了一個 Apache Helix 參與者來建立與 ZooKeeper 的連接並維護心跳。

6)StreamgateFE (Streamgate Front End): 該服務充當 Apache Helix 的旁觀者,從 ZooKeeper 上偵聽拓撲變化。它實現了反向代理。來自客戶機 (火球、網關或移動應用程序) 的每個請求都使用拓撲信息進行分片,並路由到正確的 Streamgate 工作程序。

7)Helix Controllers: 顧名思義,這是一個 5 節點的獨立服務,單獨負責運行 Apache Helix Controller 進程,是拓撲管理的大腦。無論何時任何 Streamgate 節點啟動或停止,它都會檢測到更改並重新分配分片分區。

在過去的幾年中,我們一直在使用這種架構,並且實現了 99.99% 的伺服器端可靠性。我們推動基礎設施的使用持續增長,支持 iOS、Android 和 Web 平台上的十多種不同類型的應用程序。我們已經使用超過 1.5M 的並發連接來操作這個系統,並且每秒推送超過 250,000 條消息。

伺服器端基礎設施一直保持穩定運行。隨著我們為更多新城市提供各種各樣的網路服務和應用程序,我們的重點將是繼續提高向移動設備消息推送機制的長尾可靠性。我們一直在試驗新協議、開發新方法,以彌合和現實需求的差距。在檢查以往的不足時,我們發現以下方面是導致可靠性下降的原因。

1)缺乏認證

RAMEN 協議在減少數據傳輸進行了優化,僅在每 30 秒或客戶端重新連接時才發送確認消息。這將導致延遲確認,在某些情況下無法確認消息達到,因此很難區分是真正的消息丟失還是確認失敗。

2)連接不穩定

維持客戶端和伺服器的正常連接至關重要。跨不同平台的客戶端實現方式在處理錯誤、超時、後退或應用生命周期事件 (打開或關閉)、網路狀態更改、主機名和數據中心故障轉移等方面有許多細微差別。這導致了不同版本間的性能差異。

3)傳輸限制

由於該協議在 SSE 協議基礎上實現,因此數據傳輸是單向的。但是,許多新的應用程序要求我們啟用雙向消息傳輸機制。沒有實時的往返行程時間測量,確定網路狀況、傳輸速度、緩解線路阻塞都是不可能的。SSE 也是一個基於文本的協議,它限制了我們傳輸二進制有效負載的能力,不需要使用像 base64 這樣的文本編碼,從而獲得更大的有效負載。

2019 年底,我們開始開發下一代 RAMEN 協議以解決上述缺點。經過大量考量,我們選擇在 gRPC 的基礎上進行構建。gRPC 是一個被廣泛採用的 RPC 棧,具有跨多種語言的客戶端和伺服器的標准化實現,對許多不同的 RPC 方法提供了一流的支持,並具有與 QUIC 傳輸層協議的互操作性。

新的、基於 gRPC 的 RAMEN 協議擴展了以前基於 SSE 的協議,有幾個關鍵的區別:

l  確認消息立即通過反向流發送,提高了確認的可靠性,而數據傳輸量幾乎沒有增加。

l  實時確認機制允許我們測量 RTT,了解實時的網路狀況。我們可以區分真正的消息損失和網路損失。

l  在協議之上提供了抽象層,以支持流多路傳輸等功能。它還允許我們試驗應用級網路優先順序和流控制演算法,從而在數據使用和通信延遲方面帶來更高的效率。

l  協議對消息有效負載進行抽象,以支持不同類型的序列化。將來,我們會探索其他序列化方法,但要將 gRPC 保留在傳輸層。

l  不同語言的客戶端實現也讓我們能夠快速支持不同類型的應用程序和設備。

目前,這項開發工作處於 beta 版階段,很快就能上線。

消息推送平台是 Uber 出行體驗的組成部分之一。今天有數百種功能建立在該平台的基礎服務之上。我們總結了消息推送平台在 Uber 出行生態中取得巨大成功的幾個關鍵原因。

1)職能分離

消息觸發、創建和傳遞系統之間明確的職責分離允許我們在業務需求發生變化時將注意力轉移到平台的不同部分。通過將交付組件分離到 Apache Helix 中,數據流的拓撲邏輯和核心業務邏輯被很好的區分開,這允許在完全相同的架構上使用不同的有線協議支持 gRPC。

2)行業標准技術

構建在行業標准技術之上使我們的實現更加魯棒且低成本。上述系統的維護開銷非常小。我們能夠以一個非常高效的團隊規模來傳遞平台的價值。根據我們的經驗,Helix 和 Zookeeper 非常穩定。

我們可以在不同的網路條件下擴展到數百萬用戶的規模,支持數百個功能和幾十個應用程序。該協議的簡單性使其易於擴展和快速迭代。

原文:

https://eng.uber.com/real-time-push-platform/

熱點內容
ios開發文件上傳 發布:2025-05-17 21:10:40 瀏覽:983
g92編程 發布:2025-05-17 21:00:31 瀏覽:170
匯編語言第三版腳本之家 發布:2025-05-17 20:54:26 瀏覽:399
資源配置最佳狀態叫什麼 發布:2025-05-17 20:48:58 瀏覽:84
定義dns伺服器的ip 發布:2025-05-17 20:32:37 瀏覽:954
android判斷圖片 發布:2025-05-17 20:32:33 瀏覽:833
安卓12什麼時候適配小米 發布:2025-05-17 20:31:47 瀏覽:71
c語言字元串初始化 發布:2025-05-17 20:18:43 瀏覽:37
安卓融e聯推送需要什麼許可權 發布:2025-05-17 20:18:39 瀏覽:269
我的世界無限武魂伺服器 發布:2025-05-17 20:17:09 瀏覽:372