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

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

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

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

回到一開始說的Kestrel, 經過一番測試比較使用 xmemcached、spymemcached 等客戶端對Kestrel進行收發操作,發現效果都比較爛,很遺憾沒有找到讓人滿意的Kestrel for Java的客戶端,所以我們一致認為使用Kestrel 這貨將來的杯具估計產生在客戶端(java),也許Twitter使用的是ruby,是不是對ruby反而支持的很好?對Java支持的不夠給 力?Kestrel 接收端 需要不斷的循環服務器 才能及時的收到消息, 這樣節點數一多的話網絡開銷會不會很大?
在測試的過程中,我們使用xmemcached作為客戶端,貌似不太靠譜,開10個線程,循環1000次,表示鴨梨很大,出現以下異常信息,producer和consumer都有,而且同時冒出來。
發送端:
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秒在高并發或者存取大數據的時候通常是不夠的。”暈啊,表示不能接受這樣的解釋。

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

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

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

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

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

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

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

 
–end–