http://www.codedump.info/?p=196
zeromq和libevent,ACE這樣定位的項目有什么區別沒有?
1) libevent封裝了對網絡I/O,信號,定時器等的處理,可以基於它之上做網絡層的開發.
2) ACE封裝了不同平台下的系統調用,也提供好幾種網絡編程的模型.
然而,zeromq不是libevent,也不是ACE,因為它的主要特性是:面向消息進行通信.所以,它提供的是比libevent,ACE處在網絡通信中更高一層的組件.使用它,程序員不再需要上面提到的libevent,ACE之類的庫需要關心的東西,程序員如果要使用zeromq,只需要做如下的事情:
1) 告知所使用的patten,比如request-reply,pub-sub,push-pull等(下面會詳細解釋這個pattern).
2) 告知是用於機器之間,還是進程之間,線程之間的通信.
然后,將所需要發送的數據封裝到zeromq自帶的msg結構體中發送出去,使用者自己關心如何序列化/反序列化這些數據,然后如何處理這些數據就是使用者的事情了.
這樣看上來,使用者要關注的事情”高”了一層,大部分的精力都可以放在業務邏輯之上了.簡而言之,它讓使用者的精力放在了通信模式和業務邏輯上,而不是更下面一層的網絡層上.
下面解釋一下zeromq常用的幾種網絡pattern:
1) request-reply
就是一般的C/S架構中,client與server之間一問一答的通信模式,比如最經典的echo服務.需要注意的是,client發送一個request,server必須有一個回應.
這個pattern並不是什么亮點,下面亮點來了.
2) publish-subscribe
server端作為publish端,而任何連接到服務端的client都會成為subscribe端.也就是說,server端會把當前的所有需要publish出去的消息全部發送到當前連接上去的client上.
3) push-pull
server端作為push端,而client端作為pull端.如果有多個client端同時連接到這個server,則服務器會在內部做一個負載均衡,采用平均分配的算法,將所有的消息均衡發布到client端上.
看上去,很稀松平常?接下來亮點真的來了.
2),3)兩種模式中,無論是server端還是client端在啟動時都是不知道client實際數量的,這就意味着,一個使用zeromq搭建的服務,可以進行”熱更新”.
考慮如下一種場景.一個server端做為一組服務器集群最上層的一個proxy,起到負載均衡的作用,將請求按照它下面對應服務器集群依次派發到不同的client端進行處理.某個時刻可能處理的機器只有2台,而隨着負載越來越大,可能需要3台機器了,這個時候如果使用zeromq的push-pull搭建的proxy端,則可以不用對之前搭建的server,client端進行停機,只需要新啟動一個client連接上去,proxy層就會自動根據當前的機器分配平均派發任務了.cool.
實際上,這些模式並不是什么新東西,只不過zeromq為使用者做了一個封裝,而不是像libevent,ACE等還局限在網絡層部分的封裝,它關注的是通信層,通信模式等.
個人感覺,zeromq部分解決了erlang所要解決的問題:在多台機器中通信,派發任務等,是分布式通信的利器,但是局限於語言的限制,它沒有辦法做的跟erlang一樣的完善(erlang已經可以算的上一個簡易微型的OS了),但是許多的時候,似乎只使用這一部分功能也就足夠了.
zeromq的代碼量,截至到我目前閱讀的2.0.10-stable版本,也只有不到2W行代碼.提供出去的API也極為簡單,但是內部的實現比較”繞”,zeromq是我閱讀過的項目中少數的非常需要依賴調試工具跟進代碼才能看懂代碼流程的項目,同時代碼中類的繼承層次也比較多,閱讀起來並不像它提供的API那樣簡單直白.后續會對其中的一些難點做一些分析.
https://github.com/anjuke/zguide-cn/blob/master/chapter1.md
在使用SUB套接字時,必須使用zmq_setsockopt()方法來設置訂閱的內容。如果你不設置訂閱內容,那將什么消息都收不到,新手很容易犯這個錯誤。訂閱信息可以是任何字符串,可以設置多次。只要消息滿足其中一條訂閱信息,SUB套接字就會收到。訂閱者可以選擇不接收某類消息,也是通過zmq_setsockopt()方法實現的。
PUB-SUB套接字組合是異步的。客戶端在一個循環體中使用zmq_recv()接收消息,如果向SUB套接字發送消息則會報錯;類似地,服務端可以不斷地使用zmq_send()發送消息,但不能在PUB套接字上使用zmq_recv()。
關於PUB-SUB套接字,還有一點需要注意:你無法得知SUB是何時開始接收消息的。就算你先打開了SUB套接字,后打開PUB發送消息,這時SUB還是會丟失一些消息的,因為建立連接是需要一些時間的。很少,但並不是零。
我們知道在建立TCP連接時需要進行三次握手,會耗費幾毫秒的時間,而當節點數增加時這個數字也會上升。在這么短的時間里,ZMQ就可以發送很多很多消息了。舉例來說,如果建立連接需要耗時5毫秒,而ZMQ只需要1毫秒就可以發送完這1000條消息。
關於發布-訂閱模式的幾點說明:
- 訂閱者可以連接多個發布者,輪流接收消息;
- 如果發布者沒有訂閱者與之相連,那它發送的消息將直接被丟棄;
- 如果你使用TCP協議,那當訂閱者處理速度過慢時,消息會在發布者處堆積。以后我們會討論如何使用閾值(HWM)來保護發布者。
- 在目前版本的ZMQ中,消息的過濾是在訂閱者處進行的。也就是說,發布者會向訂閱者發送所有的消息,訂閱者會將未訂閱的消息丟棄。
正確地使用上下文
ZMQ應用程序的一開始總是會先創建一個上下文,並用它來創建套接字。在C語言中,創建上下文的函數是zmq_init()。一個進程中只應該創建一個上下文。從技術的角度來說,上下文是一個容器,包含了該進程下所有的套接字,並為inproc協議提供實現,用以高速連接進程內不同的線程。如果一個進程中創建了兩個上下文,那就相當於啟動了兩個ZMQ實例。如果這正是你需要的,那沒有問題,但一般情況下:
在一個進程中使用zmq_init()函數創建一個上下文,並在結束時使用zmq_term()函數關閉它
如果你使用了fork()系統調用,那每個進程需要自己的上下文對象。如果在調用fork()之前調用了zmq_init()函數,那每個子進程都會有自己的上下文對象。通常情況下,你會需要在子進程中做些有趣的事,而讓父進程來管理它們。
ZMQ對象包括:消息、套接字、上下文。
- 不要在多個線程中使用同一個套接字。不要去想為什么,反正別這么干就是了。
- 關閉所有的套接字,並在主程序中關閉上下文對象。
- 如果仍有處於阻塞狀態的recv或poll調用,應該在主程序中捕捉這些錯誤,並在相應的線程中關閉套接字。不要重復關閉上下文,zmq_term()函數會等待所有的套接字安全地關閉后才結束。
記住一點,在ZMQ中所有的套接字都是由ZMQ管理的,只有消息是由程序員管理的。
ZMQ連接和傳統的TCP連接是有區別的,主要有:
- 使用多種協議,inproc(進程內)、ipc(進程間)、tcp、pgm(廣播)、epgm;
- 當客戶端使用zmq_connect()時連接就已經建立了,並不要求該端點已有某個服務使用zmq_bind()進行了綁定;
- 連接是異步的,並由一組消息隊列做緩沖;
- 連接會表現出某種消息模式,這是由創建連接的套接字類型決定的;
- 一個套接字可以有多個輸入和輸出連接;
- ZMQ沒有提供類似zmq_accept()的函數,因為當套接字綁定至端點時它就自動開始接受連接了;
- 應用程序無法直接和這些連接打交道,因為它們是被封裝在ZMQ底層的。
服務端節點可以僅使用一個套接字就能綁定至多個端點。也就是說,它能夠使用不同的協議來建立連接:
zmq_bind (socket, "tcp://*:5555"); zmq_bind (socket, "tcp://*:9999"); zmq_bind (socket, "ipc://myserver.ipc");
當然,你不能多次綁定至同一端點,這樣是會報錯的。客戶端節點也可以使用一個套接字同時建立多個連接。
我們在設計架構時,應該遵循“服務端是穩定的,客戶端是靈活的“原則,這樣就不太會出錯。
https://github.com/anjuke/zguide-cn/blob/master/chapter2.md
TCP套接字和ZMQ套接字之間在傳輸數據方面的區別:
- ZMQ套接字傳輸的是消息,而不是字節(TCP)或幀(UDP)。消息指的是一段指定長度的二進制數據塊。
- ZMQ套接字在后台進行I/O操作,也就是說無論是接收還是發送消息,它都會先傳送到一個本地的緩沖隊列,這個內存隊列的大小是可以配置的。
- ZMQ套接字可以和多個套接字進行連接(如果套接字類型允許的話)。TCP協議只能進行點對點的連接,而ZMQ則可以進行一對多(類似於無線廣播)、多對多(類似於郵局)、多對一(類似於信箱),當然也包括一對一的情況。
- ZMQ套接字可以發送消息給多個端點(扇出模型),或從多個端點中接收消息(扇入模型)
調用zmq_send()方法時其實並沒有真正將消息發送給套接字連接。消息會在一個內存隊列中保存下來,並由后台的I/O線程異步地進行發送。如果不出意外情況,這一行為是非阻塞的。所以說,即便zmq_send()有返回值,並不能代表消息已經發送。
當你在用zmq_msg_init_data()初始化消息后,你不能重用或是釋放這條消息,否則ZMQ的I/O線程會認為它在傳輸垃圾數據。這對初學者來講是一個常犯的錯誤,下文我們會講述如何正確地處理消息。
ZMQ提供了一組單播傳輸協議(inporc, ipc, tcp),和兩個廣播協議(epgm, pgm)。
進程內協議,即inproc,可以在同一個進程的不同線程之間進行消息傳輸,它比ipc或tcp要快得多。這種協議有一個要求,必須先綁定到端點,才能建立連接,也許未來也會修復。通常的做法是先啟動服務端線程,綁定至端點,后啟動客戶端線程,連接至端點。
向套接字寫入一個消息時可能會將消息發送給很多節點,相應的,套接字又會從所有已建立的連接中接收消息。zmq_recv()方法使用了公平隊列的算法來決定接收哪個連接的消息。
I/O線程
我們提過ZMQ是通過后台的I/O線程進行消息傳輸的。一個I/O線程已經足以處理多個套接字的數據傳輸要求,當然,那些極端的應用程序除外。這也就是我們在創建上下文時傳入的1所代表的意思:
void *context = zmq_init (1);
ZMQ應用程序和傳統應用程序的區別之一就是你不需要為每個套接字都創建一個連接。單個ZMQ套接字可以處理所有的發送和接收任務。如,當你需要向一千個訂閱者發布消息時,使用一個套接字就可以了;當你需要向二十個服務進程分發任務時,使用一個套接字就可以了;當你需要從一千個網頁應用程序中獲取數據時,也是使用一個套接字就可以了。
ZMQ會為你做些什么:
它會將消息快速高效地發送給其他節點,這里的節點可以是線程、進程、或是其他計算機;
ZMQ為應用程序提供了一套簡單的套接字API,不用考慮實際使用的協議類型(進程內、進程間、TPC、或廣播);
當節點調動時,ZMQ會自動進行連接或重連;
無論是發送消息還是接收消息,ZMQ都會先將消息放入隊列中,並保證進程不會因為內存溢出而崩潰,適時地將消息寫入磁盤;
ZMQ會處理套接字異常;
所有的I/O操作都在后台進行;
ZMQ不會產生死鎖。
ZMQ的核心消息模式有:
-
請求-應答模式 將一組服務端和一組客戶端相連,用於遠程過程調用或任務分發。
-
發布-訂閱模式 將一組發布者和一組訂閱者相連,用於數據分發。
-
管道模式 使用扇入或扇出的形式組裝多個節點,可以產生多個步驟或循環,用於構建並行處理架構。
我們在第一章中已經講述了這些模式,不過還有一種模式是為那些仍然認為ZMQ是類似TCP那樣點對點連接的人們准備的:
- 排他對接模式 將兩個套接字一對一地連接起來,這種模式應用場景很少,我們會在本章最末尾看到一個示例。
ZMQ的傳輸單位是消息,即一個二進制塊。你可以使用任意的序列化工具,如谷歌的Protocal Buffers、XDR、JSON等,將內容轉化成ZMQ消息。