基於Redis實現分佈式消息隊列(3)

1、Redis是什麼鬼?

Redis是一個簡單的,高效的,分佈式的,基於內存的緩存工具。 
假設好服務器後,通過網絡連接(類似數據庫),提供Key-Value式緩存服務。

簡單,是redis突出的特色。 
簡單可以保證核心功能的穩定和優異。

2、性能

性能方面:Redis是足夠高效的。 
和Memecached對比,在數據量較小大情況下,Redis性能更優秀。 
數據量大到一定程度的時候,Memecached性能稍好。

簡單結論:但總體上講Redis性能已經足夠好。

// Ref: Redis性能測試 http://www.cnblogs.com/lulu/archive/2013/06/10/3130878.html 
原則:Value大小不要超過1390Byte。

經實驗得知: 
List操作和字符串操作性能相當,略差,幾乎可以忽略。 
使用Jedis自帶pool,“每次從pool中取用完放回“ 和 “重用單個連接“ 相比,平均用時是3倍。這部分需要繼續研究底層機制,採用更合理的實驗方法進一步獲得數據。 
使用Jedis自帶pool,性能上是滿足當前訪問量需要的,等有時間了再進一步深入。

3、數據類型

Redis支持5種數據類型:字符串、Map、List、Set、Sorted Set。 
List特別適合用於實現隊列。提供的操作包括: 
從左側(或右側)放入一個元素,從右側(或左側)取出一個元素,讀取某個範圍的元素,刪除某個範圍的元素。

Sorted Set中元素是唯一的,可以通過名字找。 
Map可以高效地通過key找。 
假如我們需要實現finishTash(taskId),需要通過名字在隊列中找元素,上面兩個可能會用到。

4、原子操作

實現分佈式隊列首要問題是:不能出現併發問題。

Redis是底層是單線程的,命令執行是原子操作,支持事務,契合了我們的需求。

Redis直接提供的命令都是原子操作,包括lpush、rpop、blpush、brpop等。

Redis支持事務。通過類似 begin…[cancel]…commit的語法,提供begin…commit之間的命令爲原子操作的功能,之間命令對數據的改變對其他操作是不可見的。類似關係型數據庫中的存儲過程,同時提供了最高級別的事務隔離級別。

Redis支持腳本,每個腳本的執行是原子性的。

做了一下併發測試: 
寫了個小程序,隨機對List做push或pop操作,push的比pop的稍多。 
記錄每次處理的詳細信息到數據庫。 
最後把List中數據都pop出來,詳細記錄每次pop詳細信息。 
統計push和pop是否相等,統計針對每條數據是否都有push和pop。 
500併發,沒有出現併發問題。

5、集羣

實現分佈式隊列另一個重要問題是:不能出現單點故障。

Redis支持Master-Slave數據複製,從服務器設置 slave-of master-ip:port 即可。 
集羣功能可以由客戶端提供。 
客戶端使用哨兵,可自動切換主服務器。

由於隊列操作都是寫操作,從服務器主要目的是備份數據,保證數據安全。

如果想基於 sharding 做多master集羣,可以結合 zookeeper 自己做。

Redis 3.0支持集羣了,還沒細看,應該是個好消息,等大家都用起來,沒什麼問題的話,可以考慮試試看。

如果 master 宕掉,怎麼辦? 
“哨兵”會選出一個新的master來。產生過程中,消息隊列暫停服務。 
最極端的情況,所有Redis都停了,當消息隊列發現Redis停止響應時,對業務系統的請求應拋出異常,停止隊列服務。 
這樣會影響業務,業務系統下訂單、審批等操作會失敗。如果可以接受,這是一種方案。 
Redis整個集羣宕掉,這種情況很少發生,如果真發生了,業務系統停止服務也是可以理解的。

如果想要在Redis整個集羣宕掉的情況下,消息隊列仍繼續提供服務。 
方法是這樣的: 
啓用備用存儲機制,可以是zookeeper、可以是關係型數據庫、可以是另外可用的Memecached等。 
本地內存存儲是不可取的,首先,同步多個客戶端虛擬機內存數據太複雜,相當於自己實現了一個Redis,其次,保證內存數據存儲安全太複雜。 
備用存儲機制相當於實現了另外一個版本的消息隊列,邏輯一致,底層存儲不同。這個實現可以性能低一些,保證最基本的原則即可。 
想要保證不出現併發問題,由於消息隊列程序同時運行在多個虛擬機中,對象鎖、方法鎖無效。需要有一個獨立於虛擬機的鎖機制,zookeeper是個好選擇。 
將關係型數據庫設置爲最高級別的事務隔離級別,太傻了。除了zk有其他好辦法嗎?

Redis集羣整個宕掉的同時Zookeeper也全軍覆沒怎麼辦? 
這個問題是沒有盡頭的,提供了第二備用存儲、第三備用存儲、第四備用存儲、…,理論上也會同時宕掉,那時候怎麼辦? 
有錢任性的土豪可以繼續,預算有限的情況,能做到哪步就做到哪步。

6、持久化

分佈式隊列的應用場景和緩存的應用場景是不一樣的。

如果有沒來得及持久化的數據怎麼辦? 
從業務系統的角度,已經成功發送給消息隊列了。 
消息隊列也以爲Redis妥妥地收好了。 
可Redis還沒寫到日記裏,更沒有及時通知小夥伴,掛了。可能是斷電了,可能是進程被kill了。

後果會怎樣? 
已經執行過的任務會再次執行一遍。 
已經放到隊列中的任務,消失了。 
標記爲已經完成的任務,狀態變爲“進行中”了,然後又被執行了一遍。 
後果不可接受。

分佈式隊列不允許丟數據。 
從業務角度,哪怕丟1條數據也是無法接受的。 
從運維角度,Redis丟數據後,如果可以及時發現並補救,也是可以接受的。

架構角度,隊列保存在Redis中,業務數據(包括任務狀態)保存在關係型數據庫中。 
任務狀態是從業務角度確定的,消息隊列不應該干涉。如果業務狀態沒有統一的規範和定義,從業務數據比對任務隊列是否全面正確,就只能交給業務開發方來做。 
從分工上來看,任務隊列的目的是管理任務執行的狀態,業務系統把這個職責交給了任務隊列,業務系統自身的任務狀態維護未必準確。 
結論:任務隊列不能推卸責任,不能丟數據是核心功能,不能打折扣。

採用 Master-Slave 數據複製模式,配置bgsave,追加存儲到aof。

在從服務器上配置bgsave,不影響master性能。

隊列操作都是寫操作,master任務繁重,能讓slave分擔的持久化工作,就不要master做。

rdb和aof兩種方法都用上,多重保險。 
appendfsync設爲always。// 單節點測性能,連續100000次算平均時間,和per second比對,性能損失不大。 
性能會有些許損失,但任務執行爲異步操作,無需用戶同步等待,爲了保證數據安全,這樣是值得的。

當運維需要重啓Master服務器的時候,採取這樣的順序: 
1. 通過 cli shutdown 停止master服務器, master交代完後事後,關掉自己。這時候“哨兵”會找一個新的master出來。 
萬萬不可以直接kill或者直接打開防火牆中斷master和slave之間的連接。 
master 對外防火牆,停止對外服務,Master 自動切換到其他服務器上, 原 Master 繼續持久化 aof,發送到原來各從服務器。 
2. 在原 master 上進行運維操作。 
3. 啓動原 master,這時候它已經是從服務器了。耐心等待它從新 master 獲取最新數據。觀察 redis 日誌輸出,確認數據安全。 
4. 對新的 master 重複1-3的操作。 
5. 將以上操作寫成腳本,自動化執行,避免人爲錯誤。

發佈了20 篇原創文章 · 獲贊 13 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章