關於推送系統設計的一些總結與思考(二)

2021-08-26 20:28:36 字數 3782 閱讀 7265

** 

在該模式中,服務端會給每個有訊息要接收的使用者分配乙個隊裡,隊裡順序存放所有要傳送給該使用者的訊息。訊息傳送方在傳送訊息時,先將待傳送訊息傳送到推送系統服務端,推送服務端將該訊息快取到接收方的訊息佇列中,然後通過長連線伺服器給接收方傳送一條有新訊息到來的通知,通知的長度一般都比較短,客戶端在收到通知之後,將自己隊裡中的全部訊息拉下來,並進行處理、交付給使用方,然後再給服務端傳送訊息,刪除自己已接收的那些訊息。 

在拉模式中有兩種觸發訊息拉取的時機:

(1) 第一次拉取時,其傳入的引數為:起始id為空,最大拉取條數為8,倒序拉取時從最近的一條訊息開始拉取,返回的結果中將包含:9~16共8條訊息;如下圖所示: 

(2) 假設此時又有一條新訊息(id為17)進來,則此時伺服器端和客戶端的快取訊息狀態如下圖所示(藍色斜線表示這部分訊息已經被客戶端拉取走): 

(3) 第二次拉取時,其傳入的引數為:起始id為9,最大拉取條數為8,返回的結果中將包含1~8共8條訊息,引數中傳遞起始id將會被從返回結果中去掉;如下圖所示: 

在流量和省電方面,介於直推模式和混合模式之間;

實現最為複雜,尤其客戶端的去重操作會特別麻煩;

** 個人認為,訊息送達的可靠性保障是指推送系統將訊息及時、可靠送達至接收客戶端的能力,它包括推送系統保證訊息傳送過程中不會出現的重複、丟失、亂序的問題和及時送達訊息等幾個方面所能達到的服務質量。 

推送能保證訊息到達的絕對可靠性嗎?

推送系統是盡最大可能保障訊息送達至接收方,但是僅靠推送系統本身很難保證訊息送達的絕對可靠性性,有一些極端異常場景很難處理,如果要專門針對這些異常進行處理,將會對整個系統的複雜性、以及系統的效能造成影響,對這部分的處理投入和最終取得的效果嚴重不成比例,因此絕大多數推送系統都採取不處理的方式,也都無法提送訊息送達的絕對可靠性。 

推送系統在以下兩個地方難以保證訊息送達的不重不漏:

** 使用tcp通訊的資料需要經歷三個階段才能從傳送方的應用程式到達接收方的應用程式中: 

(1) 傳送方應用程式將訊息交付到作業系統所分配的socket傳送快取中; 

(2) 傳送方作業系統將其socket快取中的訊息傳送到接收方作業系統的socket快取中; 

(3) 接收方作業系統將socket快取中的內容交付給接收方的應用程式。 

如上圖所示,傳送方在進行第1步系統呼叫時,一旦訊息被作業系統寫入到本機的socket快取中,就會立即返回,而不會等到2、3步執行完畢,此時,訊息還未被傳送出去,至於訊息什麼時候能成功傳送出去,則由作業系統來決定,應用程式無法感知和控制,如下圖所示: 

如果在上述訊息傳送時在2、3步出現問題,例如socket突然壞掉或者收發有一方的主機突然宕機,此時,對於訊息傳送方來說,它認為訊息已經成功傳送了,對於接受方來說,它卻沒有收到訊息,那麼對於整個應用系統而言,就出現了訊息丟失的問題,就會出現訊息傳輸的不可靠的問題。 

我們通常所說tcp是可靠性傳輸,主要是指收發雙方在作業系統層面的可靠,即在通訊雙方都正常工作的情況下,能保證傳送方的socket快取中的內容按序、可靠的傳輸到接收方的socket快取中,另外,作業系統和應用程式之間的資料傳遞也是可靠的系統呼叫:系統呼叫成功返回,就意味著待傳送的資料已經成功交付給了作業系統,主要問題在於上述兩種操作之間的非同步性上。 

如何保證訊息傳輸的可靠性?

首先,我們得看到保證訊息的絕對可靠是非常困難的,通常付出的代價會非常大而獲得的成效卻並不成比例。大多數情況下,我們會選擇一些妥協措施,用盡量小的投入產生比較大的可靠性效益。 

這種思想體現在推送系統的設計中,就是對外提供最大努力的推送功能,而非絕對可靠的推送。如果應用層對可靠性要求高於推送的能力,那麼它應該自己來保證其可靠性,例如在應用層增加確認機制等措施,而不是依賴於推送系統。 

**** 

(1)直推模式

在該模式下,訊息被封裝在通知裡,直接由長連線服務(例如mosquitto)通過tcp連線直接傳送到「推送客戶端sdk」中,「推送客戶端sdk」從通知中解析出訊息內容,然後交給使用推送系統的應用接收客戶端。因此,無論是開源還是自研,所採用的長連線服務必須能夠將交付給它的訊息按序傳送到客戶端。以mosquito為例,其內部是單執行緒工作模式,保證了所有的併發請求到它那裡都變成序列執行,另外,它內部為每個連線都分配乙個結構體,用於儲存發往該連線的全部相關資料,例如連線的id、socket描述符等等,在該結構體成員中有乙個通知(訊息)佇列鍊錶,所有要傳送給該連線對應客戶端的通知(訊息)都被按照先後順序放入到該通知(訊息)佇列鍊錶中,在傳送通知(訊息)時也是按照先後順序將通知(訊息)佇列中的通知寫入到socket快取中,這樣就能保證所有交付到長連線服務的通知都能夠按序到達接收端,如下圖所示: 

在上圖中,有4個通知傳送方r1、r2、r3、r4,它們併發工作,各自先後傳送了兩條通知到長連線服務,長連線服務內部順序處理這些接收到的通知,並將通知放到對應接收方的通知接收佇列中,然後依次將這些通知從tcp連線中傳送給推送客戶端。 

mosquitto這種實現方式中,有三處實現細節保證了通知傳輸的順序性: 

(1) 單執行緒工作模式,無論傳送到它那裡的請求併發量有多大,它對請求的處理方式都是乙個個的順序進行; 

(2) 內部採用佇列方式快取待傳送的通知,保證通知按照佇列方式依次被插入和取走; 

(3) 訊息傳送時,按照在佇列中的快取順序,依次寫入到socket快取中,socket快取保證了網路傳輸過程中的順序性。 

mosquitto就像對通知進行了排序,無論外部以怎樣的併發量到達它那裡,它都會把所有的請求進行排序,然後將這些通知順序下發到客戶端。 

(1)拉模式

拉模式是指訊息是通過客戶端主動拉取的方式送達客戶端,在該模式下,所有待推送的訊息都會被推送的服務端快取,在推送後台中,有專門的快取集群(本文的快取集群中有相關描述)用於儲存每個接收端的訊息。在快取集群中,為每個使用者分配乙個訊息佇列,將要傳送給某個客戶端的訊息都會先存入到其訊息佇列中,這裡相當於對併發的傳送訊息的請求進行序列化處理,為訊息進行排序;客戶端在接到新訊息到來的通知時或者客戶端初次上線時按序從該訊息佇列中取出訊息(詳細拉取過程可見本文對拉模式的詳細介紹),如下圖所示: 

在拉模式下,是通過實際快取節點的序列化儲存和順序拉取訊息等措施保證訊息送達的順序性。 

無論到達推送後台的「傳送訊息」請求的併發量多複雜(包括量大而且無序),快取集群首先會將訊息路由到正確的快取節點(這一步並未進行任何序列化操作),實際的快取節點將轉交過來的併發請求進行序列化。快取節點以redis為例(詳見本文關於如何構建快取集群的介紹),它內部也是單執行緒方式處理請求,因此到達實際快取節點的併發請求,都將被其順序處理,也即將所有的併發而來的訊息進行排序。快取節點的序列化操作保障訊息在服務快取的順序性,從而為整個系統的訊息有序性提供了前提條件。

關於Marching Cube的一些疑惑思考

最近學習mc表面繪製,對於開始對於cg方面的一些知識不是很明白,搞的一頭霧水,於是就零零碎碎參考一些網上的程式來學習。主要參考的有3d med 的手冊,網上的一些程式,如 以及跟蹤vtk源 還有這個 但是在閱讀的時候產生的兩個疑惑,表示怎麼算出來的,看了書上的不是很明白,另乙個問題是得到的三角麵片怎...

關於Android的一些設計

關於android的一些設計 2012年01月13日 讓我們一起面對吧。android多型號的裝置以及形狀的諸多因素,讓其設計感覺像是一場艱苦的戰鬥。其神秘的文件使得設計和生產在一開始就顯得很難。在網上找有關android設計的網路資源,你會發現很少有幫助的東西。如果這一切讓你感到沮喪 而且如果這是...

關於Android的一些設計

關於android的一些設計 2012年01月13日 讓我們一起面對吧。android多型號的裝置以及形狀的諸多因素,讓其設計感覺像是一場艱苦的戰鬥。其神秘的文件使得設計和生產在一開始就顯得很難。在網上找有關android設計的網路資源,你會發現很少有幫助的東西。如果這一切讓你感到沮喪 而且如果這是...