作者: H.E. | 您可以轉(zhuǎn)載, 但必須以超鏈接形式標明文章原始出處和作者信息及版權(quán)聲明
網(wǎng)址: http://www.javabloger.com/article/mq-kestrel-redis-for-java.html
豆瓣讀書 向你推薦有關 JMS、 NoSQL、 分布式、 存儲、 性能、 類別的圖書。

Kestrel是twitter的開發(fā)團隊用scala語言寫的開源消息中間件,可以將消息持久存儲到磁盤上,也可以將消息存儲于內(nèi)存中,但是不論保存磁盤還是內(nèi)存中都可以設置消息存儲的超期時間長短。
原先Kestrel是由Ruby寫的Starling項目,但是后來 twitter的開發(fā)人員嘗試用scala重新實現(xiàn),并且可以支持Memcached的部分協(xié)議,例如:GET、SET、FLUSH_ALL、 STATS。對于Kestrel 服務器端而言如果有N個接收端連接在Kestrel服務器上,那么每個接收端會平均或者隨機的收到不同的消息,并且發(fā)送端發(fā)過了消息接收端就算不接收,等 到接收端再上去接收的時候還能收到消息,因為Kestrel支持消息持久化。
Kestrel 不存在主從 和 集群的概念,只存在分布式的說法,這有點類似memcached 通過客戶端組成一個環(huán)狀,對于Kestrel 服務器端而言,如果服務器端收到消息了,但是里面有消息沒有收下來就掛了,再重啟的時候接收端還能收到之前的消息。

Redis是一個key/value存儲系統(tǒng),大多數(shù)開發(fā)者把他當做類似Memcached的緩存系統(tǒng)使用,
Redis和MemCached區(qū)別的是:
    1.會周期性的把更新的數(shù)據(jù)寫入磁盤或者把修改操作寫入追加的記錄文件
    2.對于我來說更重要的是Redis還實現(xiàn)了master-slave(主從)數(shù)據(jù)同步的功能。
    3.Redis不僅支持傳統(tǒng)的存儲的value類型,還可以支持list(鏈表)、set(集合)和zset(有序集合),
    4.Redis還支持多種方式的排序,并且可以按照數(shù)據(jù)存儲的范圍來訪問,有點像查詢(select),
    5.支持消息發(fā)布和訂閱功能。

Redis也有我們對他不滿意之處,例如:
        1.對于同一個隊列或者通道來說,有N個接收端連接在Redis服務器上,每個接收端會收到相同的消息。
        2.發(fā)過了 你不接收 等接收端再上去消息就沒有了,默認的配置是寫在內(nèi)存中,重啟后不保存。
這2點上跟 Kestrel 是相反的,但通過一些奇巧淫技將Redis消息視為緩存進行持久化,保證redis服務器重啟后數(shù)據(jù)還在,并且通過Redis客戶端動態(tài)設置機器的主從備 份,只要有兩臺或以上的Redis做集群可以對數(shù)據(jù)永遠有保證,除非服務器集群環(huán)境全掛,這樣的話Redis還算是對數(shù)據(jù)提供了可靠性。
但動態(tài)指定主從關系的話此時又會面臨一致性的話題,在客戶端指定Master的時候,為避免相互競爭或者重復指定,我們通過Jgroups工具作為Redis for Java客戶端之間的通訊工具,在通訊過程中采用了Paxos算法,由客戶端先選出一個Leader,再由這個選出來的Leader進行動態(tài)指定Redis集群中的master機器,這樣可以提高一致性,避免競爭和沖突。

回到一開始說的Kestrel, 經(jīng)過一番測試比較使用 xmemcached、spymemcached 等客戶端對Kestrel進行收發(fā)操作,發(fā)現(xiàn)效果都比較爛,很遺憾沒有找到讓人滿意的Kestrel for Java的客戶端,所以我們一致認為使用Kestrel 這貨將來的杯具估計產(chǎn)生在客戶端(java),也許Twitter使用的是ruby,是不是對ruby反而支持的很好?對Java支持的不夠給 力?Kestrel 接收端 需要不斷的循環(huán)服務器 才能及時的收到消息, 這樣節(jié)點數(shù)一多的話網(wǎng)絡開銷會不會很大?
在測試的過程中,我們使用xmemcached作為客戶端,貌似不太靠譜,開10個線程,循環(huán)1000次,表示鴨梨很大,出現(xiàn)以下異常信息,producer和consumer都有,而且同時冒出來。
發(fā)送端:
Exception in thread "main" java.util.concurrent.TimeoutException: Timed out(1000) waiting for operation
接收端:
Exception in thread "main" java.util.concurrent.TimeoutException: Timed out waiting for operation
隨后google之,作者提供了解答:http://code.google.com/p/xmemcached/issues/detail?id=108
作者給出的解釋是:“這主要是因為xmc的操作都是異步的,同步等待有個超時時間,默認的1秒在高并發(fā)或者存取大數(shù)據(jù)的時候通常是不夠的。”暈啊,表示不能接受這樣的解釋。

隨后目光開始關注Redis,Redis采用長連接,事件監(jiān)聽,不需要輪詢,接收端等待消息,只接收字符串類型的消息,對消息的數(shù)據(jù)沒有保證,不接收就沒 有了服務器端不保存,也不支持Queue的消息模式,就像JMS中的Topic消息那樣一個發(fā)送端對應N個接收端每個接收端收到同樣的消息。
但我們將publish/subscribe和rpush/rpop 4個協(xié)議混合使用就可以完全滿足我們的要求,客戶端不輪訓可以保證及時性和消息持久化的問題,而且還是Queue的模式。思路上通了后,做了壓力測試,收 發(fā)100w個消息開1000個線程,8秒以內(nèi)搞定收發(fā),表示毫無壓力,體積小,部署簡單比JMS方便,而且無單點,支持可散列。

在壓力測試的過程中發(fā)現(xiàn)使用Redis Java客戶端需要注意的兩個問題:1.接收端莫名其妙的異常退出,這是由于沒有設置連接空閑的超時導致的,就和MySQL的8小時問題一樣,在 Redis客戶端設置一下config.setMaxIdle(num) 就好了。2.發(fā)送端由于發(fā)送大量請求會崩潰,出現(xiàn)超時錯誤,例如:JedisConnectionException: java.net.SocketTimeoutException: Read timed out,后來使用JedisPool和JedisPoolConfig實例,進行池化,一切都ok了,否則難以支持大數(shù)據(jù)量的高并發(fā)。

在系統(tǒng)中采用MQ是為了進行解耦合,用在2個環(huán)節(jié)上:
    1.系統(tǒng)中有大量數(shù)據(jù)并發(fā)寫入,并且可以容忍一定的延時,那么就可以先存儲緩存中,然后再將數(shù)據(jù)批量寫入,降低DAL和數(shù)據(jù)庫層每次通訊的開銷。
    2.我們自己開發(fā)的分布式并行計算的定時器稱為“任務工廠”(支持失敗轉(zhuǎn)發(fā)),任務工廠從數(shù)據(jù)庫或者緩存中拿用戶的信息,將用戶信息和業(yè)務邏輯框架作為消 息,發(fā)送給各個不同的處理業(yè)務邏輯的服務器進行處理,那樣1000w個用戶的需要定時批量操作的話,任務工廠集群越大處理的速度就越快,而且如果有一個任 務工廠的節(jié)點壞掉了或者又添加了一臺新節(jié)點,輪詢完畢后 重新計算一次后,每臺機器又將平攤被分載的計算。

這次在實施的項目中,我們還專門設計了一個“失敗隊列”,他的作用是 處理具體業(yè)務邏輯的服務器 收到消息后將處理失敗的請求放入“失敗隊列”中,往往消息在發(fā)送過來后進行處理,但是由于種種原因處理失敗,但是下次還會再進來處理,這樣的情況如果量大 而且每次都這樣的話對系統(tǒng)顯然很不利,所以從原有的隊列中移除這個用戶請求,轉(zhuǎn)移到“失敗隊列”中由 專門處理“失敗隊列”的任務工廠會去定時處理,如果這個賬號輪訓處理5次再失敗,移除失敗隊列,存入數(shù)據(jù)庫,進行管理員人工干預。

如果你真正的理解了MQ的工作原理,可以采用一些網(wǎng)絡通信工具例如:netty、mina、grizzly  ,加上一些基于Key/Value的內(nèi)存存儲產(chǎn)品,例如memcached、redis ,自己可以是建造一個符合自己要求高效的輕量級MQ系統(tǒng),如果說你可以失去一部分性能而換取可靠性的系統(tǒng)而言JMS依舊是首選產(chǎn)品。

感謝作者 莊曉丹 的回復,內(nèi)容如下:
———————————————————————————————————————-
1、需要輪詢,kestrel就是一個簡單的push/pull模型,消費者只能通過pull輪詢來獲取消息。這個輪詢的代價我認為很低的,取決于隊列名稱的大小,一般也就十幾個字節(jié)一次請求。
2、超時的原因很多,通常來說跟你傳輸?shù)南⒋笮?、網(wǎng)絡狀況、kestrel的服務器狀況都有關系,數(shù)據(jù)越大,在網(wǎng) 絡傳輸上耗費的時間越多,相應的越容易出現(xiàn)超時的情況。kestrel是scala寫的,啟動的時候要注意下jvm參數(shù),如堆大小、gc算法之類,減少 gc暫停帶來的影響。服務器如果gc暫停,或者磁盤做刷入,都有可能導致響應超時,簡單的做法就是將操作的超時時間加大,請注意,這個超時加大,不代表每 個操作都要用這么多時間(可以統(tǒng)計平均看看),而是代表可以容許的最大超時時間。

kestrel整體來講,只能作為一個輕量級的mq方案,用在一些相對簡單的場景。———————————————————————————————————————-

 
–end–