ZeroMQ:14---基礎篇之(高水位標記)

  • 所有的套接字類型都可以使用標識。如果你在使用PUB和SUB套接字,其中SUB套接字爲自己聲明瞭標識,那麼,當SUB斷開連接時,PUB會保留要發送給SUB的消息。
  • 這種機制有好有壞。好的地方在於發佈者會暫存這些消息,當訂閱者重連後進行發送;不好的地方在於這樣很容易讓發佈者因內存溢出而崩潰。
  • 如果你在使用持久化的SUB套接字(即爲SUB設置了套接字標識),那麼你必須設法避免消息在發佈者隊列中堆砌並溢出,應該使用閾值(HWM)來保護髮布者套接字。發佈者的閾值會分別影響所有的訂閱者。
  • 我們可以運行一個示例來證明這一點,用第一章中的wuclient和wuserver具體,在wuclient中進行套接字連接前加入這一行:
zmq_setsockopt (subscriber, ZMQ_IDENTITY, "Hello", 5);
  • 編譯並運行這兩段程序,一切看起來都很平常。但是觀察一下發布者的內存佔用情況,可以看到當訂閱者逐個退出後,發佈者的內存佔用會逐漸上升。若此時你重啓訂閱者,會發現發佈者的內存佔用不再增長了,一旦訂閱者停止,就又會增長。很快地,它就會耗盡系統資源
  • 我們先來看看如何設置閾值,然後再看如何設置得正確。下面的發佈者和訂閱者使用了上文提到的“節點協調”機制。發佈者會每隔一秒發送一條消息,這時你可以中斷訂閱者,重新啓動它,看看會發生什麼。
  • 以下是發佈者的代碼:
//
//  發佈者 - 連接持久化的訂閱者
//
#include "zhelpers.h"
 
int main (void) 
{
    void *context = zmq_init (1);
 
    //  訂閱者會發送已就緒的消息
    void *sync = zmq_socket (context, ZMQ_PULL);
    zmq_bind (sync, "tcp://*:5564");
 
    //  使用該套接字發佈消息
    void *publisher = zmq_socket (context, ZMQ_PUB);
    zmq_bind (publisher, "tcp://*:5565");
 
    //  等待同步消息
    char *string = s_recv (sync);
    free (string);
 
    //  廣播10條消息,一秒一條
    int update_nbr;
    for (update_nbr = 0; update_nbr < 10; update_nbr++) {
        char string [20];
        sprintf (string, "Update %d", update_nbr);
        s_send (publisher, string);
        sleep (1);
    }
    s_send (publisher, "END");
 
    zmq_close (sync);
    zmq_close (publisher);
    zmq_term (context);
    return 0;
}
  • 下面是訂閱者的代碼:
//
//  持久化的訂閱者
//
#include "zhelpers.h"
 
int main (void)
{
    void *context = zmq_init (1);
 
    //  連接SUB套接字
    void *subscriber = zmq_socket (context, ZMQ_SUB);
    zmq_setsockopt (subscriber, ZMQ_IDENTITY, "Hello", 5);
    zmq_setsockopt (subscriber, ZMQ_SUBSCRIBE, "", 0);
    zmq_connect (subscriber, "tcp://localhost:5565");
 
    //  發送同步消息
    void *sync = zmq_socket (context, ZMQ_PUSH);
    zmq_connect (sync, "tcp://localhost:5564");
    s_send (sync, "");
 
    //  獲取更新,並按指令退出
    while (1) {
        char *string = s_recv (subscriber);
        printf ("%s\n", string);
        if (strcmp (string, "END") == 0) {
            free (string);
            break;
        }
        free (string);
    }
    zmq_close (sync);
    zmq_close (subscriber);
    zmq_term (context);
    return 0;
}
  • 運行以上代碼,在不同的窗口中先後打開發布者和訂閱者。當訂閱者獲取了一至兩條消息後按Ctrl-C中止,然後重新啓動,看看執行結果:
$ durasub
Update 0
Update 1
Update 2
^C
$ durasub
Update 3
Update 4
Update 5
Update 6
Update 7
^C
$ durasub
Update 8
Update 9
END
  • 可以看到訂閱者的唯一區別是爲套接字設置了標識,發佈者就會將消息緩存起來,待重建連接後發送。設置套接字標識可以讓瞬時套接字轉變爲持久套接字。實踐中,你需要小心地給套接字起名字,可以從配置文件中獲取,或者生成一個UUID並保存起來。
  • 當我們爲PUB套接字設置了閾值,發佈者就會緩存指定數量的消息,轉而丟棄溢出的消息。讓我們將閾值設置爲2,看看會發生什麼:
uint64_t hwm = 2;
zmq_setsockopt (publisher, ZMQ_HWM, &hwm, sizeof (hwm));
  • 運行程序,中斷訂閱者後等待一段時間再重啓,可以看到結果如下:
$ durasub
Update 0
Update 1
^C
$ durasub
Update 2
Update 3
Update 7
Update 8
Update 9
END
  • 看仔細了,發佈者只爲我們保存了兩條消息(2和3)。閾值使得ZMQ丟棄溢出隊列的消息。
  • 簡而言之,如果你要使用持久化的訂閱者,就必須在發佈者端設置閾值,否則可能造成服務器因內存溢出而崩潰。但是,還有另一種方法。ZMQ提供了名爲交換區(swap)的機制,它是一個磁盤文件,用於存放從隊列中溢出的消息。啓動它很簡單:
// 指定交換區大小,單位:字節。
uint64_t swap = 25000000;
zmq_setsockopt (publisher, ZMQ_SWAP, &swap, sizeof (swap));
  • 我們可以將上面的方法綜合起來,編寫一個既能接受持久化套接字,又不至於內存溢出的發佈者:
//
//  發佈者 - 連接持久化訂閱者
//
#include "zhelpers.h"
 
int main (void) 
{
    void *context = zmq_init (1);
 
    //  訂閱者會告知我們它已就緒
    void *sync = zmq_socket (context, ZMQ_PULL);
    zmq_bind (sync, "tcp://*:5564");
 
    //  使用該套接字發送消息
    void *publisher = zmq_socket (context, ZMQ_PUB);
 
    //  避免慢持久化訂閱者消息溢出的問題
    uint64_t hwm = 1;
    zmq_setsockopt (publisher, ZMQ_HWM, &hwm, sizeof (hwm));
 
    //  設置交換區大小,供所有訂閱者使用
    uint64_t swap = 25000000;
    zmq_setsockopt (publisher, ZMQ_SWAP, &swap, sizeof (swap));
    zmq_bind (publisher, "tcp://*:5565");
 
    //  等待同步消息
    char *string = s_recv (sync);
    free (string);
 
    //  發佈10條消息,一秒一條
    int update_nbr;
    for (update_nbr = 0; update_nbr < 10; update_nbr++) {
        char string [20];
        sprintf (string, "Update %d", update_nbr);
        s_send (publisher, string);
        sleep (1);
    }
    s_send (publisher, "END");
 
    zmq_close (sync);
    zmq_close (publisher);
    zmq_term (context);
    return 0;
}
  • 若在現實環境中將閾值設置爲1,致使所有待發送的消息都保存到磁盤上,會大大降低處理速度。這裏有一些典型的方法用以處理不同的訂閱者:
    • 必須爲PUB套接字設置閾值,具體數字可以通過最大訂閱者數、可供隊列使用的最大內存區域、以及消息的平均大小來衡量。舉例來說,你預計會有5000個訂閱者,有1G的內存可供使用,消息大小在200個字節左右,那麼,一個合理的閾值是1,000,000,000 / 200 / 5,000 = 1,000。
    • 如果你不希望慢速或崩潰的訂閱者丟失消息,可以設置一個交換區,在高峯期的時候存放這些消息。交換區的大小可以根據訂閱者數、高峯消息比率、消息平均大小、暫存時間等來衡量。比如,你預計有5000個訂閱者,消息大小爲200個字節左右,每秒會有10萬條消息。這樣,你每秒就需要100MB的磁盤空間來存放消息。加總起來,你會需要6GB的磁盤空間,而且必須足夠的快(這超出了本指南的講解範圍)。
  • 關於持久化訂閱者:
    • 數據可能會丟失,這要看消息發佈的頻率、網絡緩存大小、通信協議等。持久化的訂閱者比起瞬時套接字要可靠一些,但也並不是完美的。
    • 交換區文件是無法恢復的,所以當發佈者或代理消亡時,交換區中的數據仍然會丟失。
  • 關於閾值:
    • 這個選項會同時影響套接字的發送和接收隊列。當然,PUB、PUSH不會有接收隊列,SUB、PULL、REQ、REP不會有發送隊列。而像DEALER、ROUTER、PAIR套接字時,他們既有發送隊列,又有接收隊列。
    • 當套接字達到閾值時,ZMQ會發生阻塞,或直接丟棄消息。
    • 使用inproc協議時,發送者和接受者共享同一個隊列緩存,所以說,真正的閾值是兩個套接字閾值之和。如果一方套接字沒有設置閾值,那麼它就不會有緩存方面的限制。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章