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. 將以上操作寫成腳本,自動化執行,避免人爲錯誤。