1. 程式人生 > >基於Redis實現分散式訊息佇列(3)

基於Redis實現分散式訊息佇列(3)

1、Redis是什麼鬼?

Redis是一個簡單的,高效的,分散式的,基於記憶體的快取工具。
假設好伺服器後,通過網路連線(類似資料庫),提供Key-Value式快取服務。

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

2、效能

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

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

經實驗得知:
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. 將以上操作寫成指令碼,自動化執行,避免人為錯誤。