本文首發自[慕課網](imooc.com) ,想了解更多IT乾貨內容,程序員圈內熱聞,歡迎關注"慕課網"及“慕課網公眾號”!
作者:一凡|慕課網講師
Redis 是一種開源(BSD 許可)、數據結構存儲在內存中的系統,用作數據庫、緩存和消息隊列。Redis 提供了諸如字符串、散列、列表、集合、帶範圍查詢的排序集合、位圖、超級日誌、地理空間索引和流等數據結構。Redis 內置複製、Lua 腳本、LRU 驅逐、事務和不同級別的磁盤持久化,並通過 Redis Sentinel 和 Redis Cluster 自動分區提供高可用性。
1 集羣的優勢
下面是redis集羣的幾個明顯優勢。
1.1 伸縮性,數據規模不斷增大的時候,容易擴容
單實例模式:只能垂直擴展,增大機器內存的容量;
集羣模式:支持垂直擴展,也支持水平擴展,有更好的靈活性,也可以支持更大的容量;
1. 2 高可用,服務故障的情況,影響範圍小
單實例模式:故障轉移前100%不可用(slave轉換為master之前);
集羣模式:故障轉移前部分不可用(集羣規模越大,故障影響越小);
1. 3 高性能,查詢和寫入的性能
單實例模式:查詢可以分散在多個slave,寫入卻只有一個master;
集羣模式:查詢有多個master和多個slave,寫入也有多個master;
2 數據分片,一致性hash
實現redis集羣的核心點,是針對數據的分片,這裏的一致性hash算法就非常關鍵。
2.1 普通的hash 算法
node=hash(key)%number
數量變化和node順序變化,導致node選擇的差異性巨大,造成巨大的緩存失效。
2.2 一致性hash
hash(node) 形成虛擬節點環,hash(key)落在虛擬節點環,找到對應的node。
由於hash(node)的穩定性,與node順序無關。node變更隻影響一小部分數據。
2.3 redis cluster的hash slot算法
關係: cluster > node > slot > key
Redis Cluster在設計中沒有使用一致性哈希(Consistency Hashing),而是使用數據分片引入哈希槽(hash slot)來實現。
一個 Redis Cluster包含16384(0~16383)個哈希槽,存儲在Redis Cluster中的所有鍵都會被映射到這些slot中。
集羣中的每個鍵都屬於這16384個哈希槽中的一個,集羣使用公式slot=CRC16(key)/16384來計算key屬於哪個槽,其中CRC16(key)語句用於計算key的CRC16 校驗和。
按照槽來進行分片,通過為每個節點指派不同數量的槽,可以控制不同節點負責的數據量和請求數。
3 集羣元數據的一致性
3.1 對比:集中式存儲元數據
依賴外部的集中式存儲服務,比如:zookeeper, etcd等,會增加運維負擔和系統複雜度。
集中式的好處在於,元數據的讀取和更新,時效性非常好,一旦元數據出現了變更,就立即更新到集中式的存儲中,其它節點讀取的時候就可以感知到;不好在於,所有的元數據的更新壓力全部集中在一個地方,可能會導致元數據的存儲有壓力。
3.2 gossip 協議來廣播自己的狀態以及自己對整個集羣認知的改變
ping / pong 消息來確認節點的存活和同步全部的集羣元數據。
集羣元數據,包括:master/slave node列表和狀態, slot與node關係。
每個master node每秒會執行 10 次 ping,每次會選擇 5 個最久沒有通信的其它節點。
定時檢查全部節點,如果發現某個節點通信延時達到了 cluster_node_timeout / 2,那麼立即發送 ping,避免數據交換延時過長。
3.3 增加新節點
命令 CLUSTER MEET <ip> <port>
向一個節點 node 發送 CLUSTER MEET 命令,可以讓 node 節點與 ip 和 port 所指定的節點進行握手(handshake),當握手成功時,node 節點就會將 ip 和 port 所指定的節點添加到 node 節點當前所在的集羣中。
按照gossip協議,廣播meet消息,全部節點接收新節點。
重新hash(node),分配和轉移相應的slot給到新節點。
4 客户端如何調用
4.1 hash slot 信息
當客户端連接任何一個實例,實例就將哈希槽與實例的映射關係響應給客户端,客户端就會將哈希槽與實例映射信息緩存在本地。
4.2 請求數據
當客户端請求時,會計算出鍵所對應的哈希槽,在通過本地緩存的哈希槽實例映射信息定位到數據所在實例上,再將請求發送給對應的實例。
4.3 重定向機制
客户端將請求發送到實例上,這個實例沒有相應的數據,該 Redis 實例會告訴客户端更新本地的哈希槽與映射信息的緩存,將請求發送到其他的實例上。
5 新的節點加入集羣(擴容)
同3.3 增加新節點的前序步驟是一樣的。
這裏詳細瞭解下重新擴容時slot遷移和數據。
5.1 每個slot的遷移過程如下所示:
- 對目標節點發送cluster setslot {slot_id} importing {sourceNodeId}命令,目標節點的狀態被標記為"importing",準備導入這個slot的數據。
- 對源節點發送cluster setslot {slot_id} migrating {targetNodeID}命令,源節點的狀態被標記為"migrating",準備遷出slot的數據。
- 源節點執行cluster getkeysinslot {slot_id} {count}命令,獲取這個slot的所有的key列表(分批獲取,count指定一次獲取的個數),然後針對每個key進行遷移。
- 在源節點執行migrate {targetIp} {targetPort} "" 0 {timeout} keys {keys}命令,把一批批key遷移到目標節點(redis-3.0.6之前一次只能遷移一個key),具體來説,源節點對遷移的key執行dump指令得到序列化內容,然後通過客户端向目標節點發送攜帶着序列化內容的restore指令,目標節點進行反序列化後將接收到的內容存入自己的內存中,目標節點給客户端返回"OK",然後源節點刪除這個key,這樣,一個key的遷移過程就結束了。
- 所有的key都遷移完成後,一個slot的遷移就結束了。
- 遷移所有的slot(應該被遷移的那些),所有的slot遷移完成後,新的集羣的slot就重新分配完成了,向集羣內所有master發送cluster setslot {slot_id} node {targetNodeId}命令,通知他們哪些槽被遷移到了哪些master上,讓它們更新自己的信息。
5.2 slot遷移的其他説明
- 遷移過程是同步的,在目標節點執行restore指令到原節點刪除key之間,原節點的主線程處於阻塞狀態,直到key被刪除成功。
- 如果遷移過程突然出現網路故障,整個slot遷移只進行了一半,這時兩個節點仍然會被標記為中間過濾狀態,即"migrating"和"importing",下次遷移工具連接上之後,會繼續進行遷移。
- 在遷移過程中,如果每個key的內容都很小,那麼遷移過程很快,不會影響到客户端的正常訪問。
- 如果key的內容很大,由於遷移一個key的遷移過程是阻塞的,就會同時導致原節點和目標節點的卡頓,影響集羣的穩定性,所以,集羣環境下,業務邏輯要儘可能的避免大key的產生 。
5.3 slot編號
無需要求每個master的slot編號是連續的,只要每個master管理的slot的數量均衡就可以。
5.4 減少節點(縮容)
縮容的過程與擴容類似,只是slot和數據從下線的節點內轉移到其他的節點上。
6 集羣中機器出現故障
6.1 故障檢測,節點失效
如果一個節點認為另外一個節點宕機,那麼就是 pfail,主觀宕機;
如果超過半數的節點都認為另外一個節點宕機了,那麼就是 fail,客觀宕機;
6.2 故障轉移,節點選舉
每個slave節點,都根據自己對 master 複製數據的 offset,來設置一個選舉時間,offset 越大(複製數據越多)的從節點,選舉時間越靠前,優先進行選舉。
所有的 master node 開始 slave 選舉投票,給要進行選舉的 slave 進行投票,如果大部分 master node(N/2 + 1)都投票給了某個從節點,那麼選舉通過,那個從節點可以切換成 master。
從節點執行主備切換,從節點切換為主節點。
7 主從同步以及高可用
redis的主從同步在cluster版本之前就存在了,既可以提供更高的查詢效率(多slave可以查詢),又可以增加服務的可用性(master掛機後可以啓用slave成為master)。
7.1 使用主從架構時
將只有一個 Master 和多個從屬用於複製。
所有寫入都轉到主節點,這會在主節點上產生更多負載。
如果Master宕機,整個架構容易出現SPOF(單點故障)。
當您的用户羣增長時,MS 架構無助於擴展。
所以我們需要一個進程來在發生故障或關閉的情況下監控 Master,那就是 Sentinel。
7.2 Redis哨兵
故障轉移處理
8 主從同步與數據一致性
8.1 主從同步的實現過程
主從同步分為 2 個步驟:同步和命令傳播。
數據同步有sync和psync。
sync全量同步,性能比較差;psync增量同步,速度和實時性好很多。
8.2 全量同步 sync
從服務器對主服務的同步操作,需要通過 sync 命令來實現,以下是 sync 命令的執行步驟:
- 從服務器向主服務器發送 sync 命令
- 收到 sync 命令後,主服務器執行 bgsave 命令,用來生成 rdb 文件,並在一個緩衝區中記錄從現在開始執行的寫命令。
- bgsave 執行完成後,將生成的 rdb 文件發送給從服務器,用來給從服務器更新數據
- 主服務器再將緩衝區記錄的寫命令發送給從服務器,從服務器執行完這些寫命令後,此時的數據庫狀態便和主服務器一致了。
8.3 部分重同步 psync
部分重同步功能由以下 3 部分組成:
- 主從服務器的複製偏移量
- 主服務器的複製積壓緩衝區
- 服務器的運行 id(run id)
8.4 心跳檢測
當完成了同步之後,主從服務器就會進入命令傳播階段,此時從服務器會以每秒 1 次的頻率,向主服務器發送命令:REPLCONF ACK <replication_offset> 其中 replication_offset 是從服務器當前的複製偏移量
發送這個命令主要有三個作用:
- 檢測主從服務器的網絡狀態
- 輔助實現 min-slaves 選項
- 檢測命令丟失(若丟失,主服務器會將丟失的寫命令重新發給從服務器)
8.5 主從同步總結
發送 SLAVEOF 命令可以進行主從同步,比如:SLAVEOF 127.0.0.1 6379
主從同步有同步和命令傳播 2 個步驟
- 同步:將從服務器的數據庫狀態更新成主服務器當前的數據庫狀態(一個消耗資源的操作)
- 命令傳播:當主服務器數據庫狀態被修改後,導致主從服務器數據庫狀態不一致,此時需要讓主從數據同步到一致的過程
主從同步分初次複製和斷線後重複製兩種情況
- 從 2.8 版本開始,在出現斷線後重複製情況時,主服務器會根據複製偏移量、複製積壓緩衝區和 run id,來確定執行完整重同步還是部分重同步
- 2.8 版本使用 psync 命令來代替 sync 命令去執行同步操作。目的是為了解決同步(sync 命令)的低效操作
問題1:集羣的規模能否無限大,比如:1w台機器?
答案是否定的,redis 官方給的 Redis Cluster 的規模上限是 1000 個實例。
限制的原因,關鍵在於實例間的通信開銷,集羣中的每個節點都保存所有哈希槽與節點對應關係信息(Slot 映射到節點的表),以及自身的狀態信息。
參照3.2gossip協議廣播方式,節點越多,廣播風暴對於網絡以及服務器壓力也就越大。
雖然可以設置廣播消息同步的超時時間,但是節點增多、超時時間變長之後,數據一致性的消息同步延時也會更大,出現元數據不一致的可能性也會增加。
問題2:從庫的使用,以及如何權衡?
從庫的作用,一是提高可用性,當主庫宕機之後,可以立即啓用從庫作為主庫提供服務;一是提高伸縮性,提高了數據查詢併發能力,從庫提供查詢服務就增加了服務資源,更多的節點來支持查詢。
由於主從同步存在數據一致性問題,所以在使用從庫的過程中,相應的也就會遇到一些問題。
比如:因為從庫數據同步慢了,這時候主庫宕機了,數據不完整的從庫作為主庫,就會出現數據丟失的情況。從庫用來查詢也有類似問題,實時寫入的新數據,同步到從庫可能會有延時,在數據沒有同步到從庫的時候查詢從庫,也會出現查詢無數據的情況。
所以在使用從庫的情況下,需要考慮到上面的問題。
面對宕機的時候,數據丟失的問題,內存型數據庫都會存在的風險,使用redis都需要面對這個風險,否則就要犧牲性能高正數據一致性,redis數據先持久化再提供服務,這樣性能就會下降非常明顯了,沒法滿足內存性數據庫的優勢了。
啓用從庫查詢,可以針對一些數據更新的實時性較低,對於髒數據不那麼敏感的業務,或者查詢量實在太大而可以忽略部分數據延時的影響。
問題3:redis集羣化之後,代理的必要性?
代理的性能和穩定性同樣是問題所在。
產品的運維難度以及持續的維護,還是官方的redis cluster更加可靠。
有條件的團隊,針對redis cluster的不足,還會有更深入的優化,比如咱們自己研發的tendis。
問題4:單key的百萬qps限頻問題?(待解)
單key的頻繁更新,由於單個key有且只能落地到一個master節點的一個slot上面,無法通過增加節點增加slave的方法擴容,性能瓶頸就會受限於機器的CPU/內存的讀寫能力了。
假設單機最高10w的寫如速度,那麼,要實現接口的100w的qps限頻功能,要怎麼實現呢?
請輸出你的解答。。。
附-腦洞:三體人來到地球,要消滅近半的地球人(待解)
三體人不像滅霸,直接用手套簡單粗暴的一個響指就毀滅一半生靈,而是給每個人一個選擇,選擇對的人就有機會生存下來。
這個選擇的方法也特別簡單,但是需要開發一套系統來支持。
那麼這套系統的開發工作就落在了地球人最聰明的程序員你的頭上。
開發好了可以讓自己以及家人獲得豁免權而生存下來,開發的不好,直接咔嚓。
這個選擇的描述如下
全球有一個三體人的燈球,默認是熄滅狀態。
每個人會有一個三體人的開關,上面會顯示當前燈球的狀態(熄滅或者亮起),有兩個操作按鈕,分別是控制燈球的熄滅和亮起。每個人只有一次選擇機會,兩個按鈕只能選擇一個按鈕,按一次。如果一個按鈕都不選擇,不按的話,無論燈球最終狀態如何,都是要被消滅。小孩子或者老人、殘疾人,可以由監護人來輔助其完成選擇。
系統實現的前提條件和需求
前提條件説明
1 全球80億人口,必須同時在1分鐘時間內完成選擇(三體人的開關,全球實時狀態同步,無時間偏差,無時延),規定時間範圍之外無法操作;
2 三體人提供1000台128核256G內存1T磁盤的服務器,三體人的開關與服務器的網絡是直連的,沒有時延,沒有網絡開銷;
3 全球80億人,每個人都有一個唯一的從0開始自增的數字ID,與三體人的開關也是一一對應的;
4 每個人的ID%1000指向特定服務器,請求系統提供的接口 /vote
系統需求
功能,開發這個投票接口(所有請求只會在這1分鐘內請求)
/vote?uid=1111&click=1&t=1234143134134134134
參數
uid 長整型,每個人的唯一ID
click 枚舉值,按鈕選擇,0 熄滅,1 亮起
t 長整型,操作時間,單位納秒
處理
1 在同一個時刻(同一納秒),如果有多個人操作,選擇次數多的生效,如果2個選擇次數相同,狀態不變。如:熄滅2次,亮起3次,這個時刻的狀態是亮起。
結果數據
1 最終燈球的狀態,是熄滅,還是亮起;
2 選擇正確的人(ID集合);
3 選擇錯誤的人(ID集合);
4 沒有做出選擇的人(ID集合);
最終執行
調用三體人在服務器上安裝的系統程序 ,完成地球人消滅計劃。
kill uid
調用三體人的系統程序無延時,等同於內存讀取的效率。
要求在1分鐘時間內,把選擇錯誤的人和沒有做出選擇的人消滅掉。
模擬測試
1 三體人在1分鐘內導入測試用例,完成80億人的選擇。
2 1分鐘正確執行完成 kill 調用。
如果無法實現上述工作,失敗。
請輸出你的解答。。。
歡迎關注「慕課網」官方帳號,我們會一直堅持提供IT圈優質內容,分享乾貨知識,大家一起共同成長吧!
本文原創發佈於慕課網 ,轉載請註明出處,謝謝合作