使用Docker 手動&redis-trib.rb方式創建 Redis-Cluster 實驗
假設我們在一臺主從機器上配置了200G內存,但是業務需求是需要500G的時候,主從結構+哨兵可以實現高可用故障切換+冗余備份,但是並不能解決數據容量的問題,用哨兵,redis每個實例也是全量存儲,每個redis存儲的內容都是完整的數據,浪費內存且有木桶效應
為了最大化利用內存,可以采用cluster群集,就是分布式存儲。即每臺redis存儲不同的內容
Redis 分布式方案一般有兩種
1. 客戶端分區方案
優點是分區邏輯可控,缺點是需要自己處理數據路由、高可用、故障轉移等問題,比如在redis2.8之前通常的做法是獲取某個key的hashcode,然後取余分布到不同節點,不過這種做法無法很好的支持動態伸縮性需求,一旦節點的增或者刪操作,都會導致key無法在redis中命中
2. 代理方案
優點是簡化客戶端分布式邏輯和升級維護便利,缺點是加重架構部署復雜度和性能損耗,比如twemproxy、Codis
3. 官方為我們提供了專有的集群方案:Redis Cluster
它非常優雅地解決了 Redis 集群方面的問題,部署方便簡單,因此理解應用好 Redis Cluster 將極大地解放我們使用分布式 Redis 的工作量。
Redis Cluster
簡介
Redis Cluster 是 Redis 的分布式解決方案,在3.0版本正式推出,有效地解決了 Redis 分布式方面的需求。當遇到單機內存、並發、流量等瓶頸時,可以采用 Cluster 架構方案達到負載均衡的目的
在這個圖中,每一個藍色的圈都代表著一個redis的服務器節點。它們任何兩個節點之間都是相互連通的。客戶端可以與任何一個節點相連接,然後就可以訪問集群中的任何一個節點,對其進行存取和其他操作
Redis 集群提供了以下兩個好處:
- 將數據自動切分到多個節點的能力
-
當集群中的一部分節點失效或者無法進行通訊時, 仍然可以繼續處理命令請求的能力,擁有自動故障轉移的能力
redis cluster vs. replication + sentinal如何選擇
如果你的數據量很少,主要是承載高並發高性能的場景,比如你的緩存一般就幾個G,單機足夠了
Replication:一個mater,多個slave,要幾個slave跟你的要求的讀吞吐量有關系,結合sentinal集群,去保證redis主從架構的高可用性,就可以了
數據分布是如何進行的
什麽是數據分布?數據分布有兩種方式,順序分區和哈希分區
分布式數據庫首先要解決把整個數據集按照分區規則映射到多個節點的問題,即把數據集劃分到多個節點上,每個節點負責整體數據的一個子集
順序分布
順序分布就是把一整塊數據分散到很多機器中,如下圖所示
順序分布一般都是平均分配的
哈希分區
如下圖所示,1~100這整塊數字,通過 hash 的函數,取余產生的數。這樣可以保證這串數字充分的打散,也保證了均勻的分配到各臺機器上
哈希分布和順序分布只是場景上的適用。哈希分布不能順序訪問,比如你想訪問1~100,哈希分布只能遍歷全部數據,同時哈希分布因為做了 hash 後導致與業務數據無關了
數據傾斜與數據遷移跟節點伸縮
順序分布是會導致數據傾斜的,主要是訪問的傾斜。每次點擊會重點訪問某臺機器,這就導致最後數據都到這臺機器上了,這就是順序分布最大的缺點
但哈希分布其實是有個問題的,當我們要擴容機器的時候,專業上稱之為“節點伸縮”,這個時候,因為是哈希算法,會導致數據遷移
哈希分區方式
因為redis-cluster使用的就是哈希分區規則所以分析下幾種分區形式
1、節點取余分區
使用特定的數據(包括redis的鍵或用戶ID),再根據節點數量N,使用公式:hash(key)%N計算出一個0~(N-1)值,用來決定數據映射到哪一個節點上。即哈希值對節點總數取余。余數x,表示這條數據存放在第(x+1)個節點上。
缺點:當節點數量N變化時(擴容或者收縮),數據和節點之間的映射關系需要重新計算,這樣的話,按照新的規則映射,要麽之前存儲的數據找不到,要麽之前數據被重新映射到新的節點(導致以前存儲的數據發生數據遷移)
實踐:常用於數據庫的分庫分表規則,一般采用預分區的方式,提前根據數據量規劃好分區數,比如劃分為512或1024張表,保證可支撐未來一段時間的數據量,再根據負載情況將表遷移到其他數據庫中
2、一致性哈希
一致性哈希分區(Distributed Hash Table)實現思路是為系統中每個節點分配一個 token,範圍一般在0~232,這些 token 構成一個哈希環。數據讀寫執行節點查找操作時,先根據 key 計算 hash 值,然後順時針找到第一個大於等於該哈希值的 token 節點
上圖就是一個一致性哈希的原理解析。
假設我們有 n1~n4 這四臺機器,我們對每一臺機器分配一個唯一 token,每次有數據(圖中×××代表數據),一致性哈希算法規定每次都順時針漂移數據,也就是圖中×××的數 據都指向 n3。
這個時候我們需要增加一個節點 n5,在 n2 和 n3 之間,數據還是會發生漂移(會偏移到大於等於的節點),但是這個時候你是否註意到,其實只有 n2~n3 這部分的數據被漂移,其他的數據都是不會變的,這種方式相比節點取余最大的好處在於加入和刪除節點只影響哈希環中相鄰的節點,對其他節點無影響
缺點:每個節點的負載不相同,因為每個節點的hash是根據key計算出來的,換句話說就是假設key足夠多,被hash算法打散得非常均勻,但是節點過少,導致每個節點處理的key個數不太一樣,甚至相差很大,這就會導致某些節點壓力很大
實踐:加減節點會造成哈希環中部分數據無法命中,需要手動處理或者忽略這部分數據,因此一致性哈希常用於緩存場景。
3.虛擬槽分區
虛擬槽分區巧妙地使用了哈希空間,使用分散度良好的哈希函數把所有數據映射到一個固定範圍的整數集合中,整數定義為槽(slot)。這個範圍一般遠遠大於節點數,比如 Redis Cluster 槽範圍是0~16383。槽是集群內數據管理和遷移的基本單位。采用大範圍槽的主要目的是為了方便數據拆分和集群擴展。每個節點會負責一定數量的槽,下圖所示。
當前集群有5個節點,每個節點平均大約負責3276個槽。由於采用高質量的哈希算法,每個槽所映射的數據通常比較均勻,將數據平均劃分到5個節點進行數據分區。Redis Cluster 就是采用虛擬槽分區,下面就介紹 Redis 數據分區方法。
每當 key 訪問過來,Redis Cluster 會計算哈希值是否在這個區間裏。它們彼此都知道對應的槽在哪臺機器上,這樣就能做到平均分配了
搭建集群
手動
介紹完 Redis 集群分區規則之後,下面我們開始搭建 Redis 集群。搭建集群工作需要以下三個步驟:
- 準備節點
- 節點握手
- 分配槽
節點規劃
容器名稱 | 容器 IP 地址 | 映射端口號 | 服務運行模式 |
---|---|---|---|
redis-master10 | 172.50.0.10 | 6400->6400<br/>16400->16400 | master |
redis-master11 | 172.50.0.11 | 6401->6401<br/>16401->16401 | master |
redis-master12 | 172.50.0.12 | 6402->6402 | master |
redis-slave10 | 172.30.0.10 | 6403->6403 | slave |
redis-slave11 | 172.30.0.11 | 6404->6404 | slave |
redis-slave12 | 172.30.0.12 | 6405->6405 | slave |
1. 準備節點
Redis 集群一般由多個節點組成,節點數量至少為6個才能保證組成完整高可用的集群。每個節點需要開啟配置 cluster-enabled yes,讓 Redis 運行在集群模式下,上面的配置都相應的給到redis的配置文件當中並啟動。
其他配置和單機模式一致即可,配置文件命名規則 redis-{port}.conf,準備好配置後啟動所有節點,第一次啟動時如果沒有集群配置文件,它會自動創建一份,文件名稱采用 cluster-config-file 參數項控制,建議采用 node-{port}.conf 格式定義,也就是說會有兩份配置文件
當集群內節點信息發生變化,如添加節點、節點下線、故障轉移等。節點會自動保存集群狀態到配置文件中。需要註意的是,Redis 自動維護集群配置文件,不要手動修改,防止節點重啟時產生集群信息錯亂
配置文件信息如下:
文件內容記錄了集群初始狀態,這裏最重要的是節點 ID,它是一個40位16進制字符串,用於唯一標識集群內一個節點,節點 ID 在集群初始化時只創建一次,節點重啟時會加載集群配置文件進行重用,結合做相應的集群操作,而 Redis 的運行 ID 每次重啟都會變化
構建鏡像用到的 Dockerfile 信息
FROM centos:latest
MAINTAINER peter "[email protected]"
RUN groupadd -r redis && useradd -r -g redis redis
RUN yum -y update && yum -y install epel-release && yum -y install redis && yum -y install wget && yum -y install net-tools && yum -y install ruby && yum -y install rubygems
RUN wget https://rubygems.org/downloads/redis-3.2.1.gem && gem install -l ./redis-3.2.1.gem && rm -f redis-3.2.1.gem
COPY ./config/redis-trib.rb /usr/bin
COPY ./config/redis.sh /usr/bin
RUN mkdir -p /config && chmod 775 /usr/bin/redis.sh && chmod 775 /usr/bin/redis-trib.rb
創建單獨的網絡
主節點網卡
docker network create -d bridge --subnet 172.50.0.0/16 redis-cluster_redis-master
從節點網卡
docker network create -d bridge --subnet 172.30.0.0/16 redis-cluster_redis-salve
依次執行新建容器命令:
docker run -itd --name redis-master10 -v /home/work/redis-cluster/config/:/config --workdir /config --net redis-cluster_redis-master -e PORT=6400 -p 6400:6400 -p 16400:16400 --ip 172.50.0.10 redis-cluster
docker run -itd --name redis-master11 -v /home/work/redis-cluster/config/:/config --workdir /config --net redis-cluster_redis-master -e PORT=6401 -p 6401:6401 -p 16401:16401 --ip 172.50.0.11 redis-cluster
...
docker run -itd --name redis-slave11 -v /home/work/redis-cluster/config/:/config --workdir /config --net redis-cluster_redis-slave -e PORT=6404 -p 6404:6404 -p 16404:16404 --ip 172.30.0.11 redis-cluster
docker run -itd --name redis-slave12 -v /home/work/redis-cluster/config/:/config --workdir /config --net redis-cluster_redis-slave -e PORT=6405 -p 6405:6405 -p 16405:16405 --ip 172.30.0.12 redis-cluster
由於使用的命令行方式創建的,entrypoint和CMD命令沒辦法得到執行,所以需要進入到每臺機器裏去開啟 Redis 服務.
[[email protected] config]# docker exec -it redis-master11 bash
[[email protected] config]# ./redis.sh &
2.節點握手
節點握手是指一批運行在集群模式下的節點通過 Gossip 協議彼此通信,達到感知對方的過程。節點握手是集群彼此通信的第一步,由客戶端發起命令:cluster meet{ip}{port}
關於Gossip可以看看文章的介紹
通過命令 cluster meet 127.0.0.1 6380讓節點6379和6380節點進行握手通信。cluster meet 命令是一個異步命令,執行之後立刻返回。內部發起與目標節點進行握手通信
1)節點6379本地創建6380節點信息對象,並發送 meet 消息
2)節點6380接受到 meet 消息後,保存6379節點信息並回復 pong 消息
3)之後節點6379和6380彼此定期通過 ping/pong 消息進行正常的節點通信
[[email protected] config]# redis-cli -p 6400
127.0.0.1:6400> cluster meet 47.101.139.0 6401
OK
127.0.0.1:6400> cluster meet 47.101.139.0 6402
OK
127.0.0.1:6400> cluster meet 47.101.139.0 6403
OK
127.0.0.1:6400> cluster meet 47.101.139.0 6404
OK
127.0.0.1:6400> cluster meet 47.101.139.0 6405
OK
127.0.0.1:6400>
通過cluster nodes 命令確認6個節點都彼此感知並組成集群
註意:
1、每個Redis Cluster節點會占用兩個TCP端口,一個監聽客戶端的請求,默認是6379,另外一個在前一個端口加上10000,比如16379,來監聽數據的請求,節點和節點之間會監聽第二個端口,用一套二進制協議來通信。
節點之間會通過套協議來進行失敗檢測,配置更新,failover認證等等。
為了保證節點之間正常的訪問,需要註意防火墻的配置。
2、節點建立握手之後集群還不能正常工作,這時集群處於下線狀態,所有的數據讀寫都被禁止
3.設置從節點
作為一個完整的集群,需要主從節點,保證當它出現故障時可以自動進行故障轉移。集群模式下,Reids 節點角色分為主節點和從節點。
首次啟動的節點和被分配槽的節點都是主節點,從節點負責復制主節點槽信息和相關的數據。
使用 cluster replicate {nodeId} 命令讓一個節點成為從節點。其中命令執行必須在對應的從節點上執行,將當前節點設置為 node_id 指定的節點的從節點
[[email protected] config]# redis-cli -h 47.101.139.0 -p 6403 cluster replicate 1a30f25427cf9ca0a8362a50e6a313b933fe7df2
[[email protected] config]# redis-cli -h 47.101.139.0 -p 6404 cluster replicate b99ed66e154468f0d2fc21ff4d7113bf0424262b
[[email protected] config]# redis-cli -h 47.101.139.0 -p 6405 cluster replicate ba4547686e34ab121a8226291eb5ab019c53c5f8
結果:
127.0.0.1:6401> cluster nodes
056b42451d574aa383464a4cb1b50b1511d6ebe5 47.101.139.0:6404 slave b99ed66e154468f0d2fc21ff4d7113bf0424262b 0 1555686394258 4 connected
ba4547686e34ab121a8226291eb5ab019c53c5f8 47.101.139.0:6402 master - 0 1555686390247 0 connected
2a2676234dcd3b6991dd4491e61feebbdb689c69 47.101.139.0:6403 slave 1a30f25427cf9ca0a8362a50e6a313b933fe7df2 0 1555686394759 3 connected
1a30f25427cf9ca0a8362a50e6a313b933fe7df2 47.101.139.0:6400 master - 0 1555686395260 1 connected
0dc1306db8bde9868846dacc631c80f6abe85ff3 47.101.139.0:6405 slave ba4547686e34ab121a8226291eb5ab019c53c5f8 0 1555686393255 5 connected
b99ed66e154468f0d2fc21ff4d7113bf0424262b 172.50.0.11:6401 myself,master - 0 0 2 connected
4. 分配槽
Redis 集群把所有的數據映射到16384個槽中。每個 key 會映射為一個固定的槽,只有當節點分配了槽,才能響應和這些槽關聯的鍵命令。通過 cluster addslots 命令為節點分配槽
利用 bash 特性批量設置槽(slots),命令如下:
[[email protected] config]# redis-cli -h 47.101.139.0 -p 6400 cluster addslots {0..5461}
[[email protected] config]# redis-cli -h 47.101.139.0 -p 6401 cluster addslots {5462..10922}
[[email protected] config]# redis-cli -h 47.101.139.0 -p 6402 cluster addslots {10923..16383}
主節點的槽信息:
127.0.0.1:6401> cluster nodes
056b42451d574aa383464a4cb1b50b1511d6ebe5 47.101.139.0:6404 slave b99ed66e154468f0d2fc21ff4d7113bf0424262b 0 1555687118050 4 connected
ba4547686e34ab121a8226291eb5ab019c53c5f8 47.101.139.0:6402 master - 0 1555687118550 0 connected 10923-16383
2a2676234dcd3b6991dd4491e61feebbdb689c69 47.101.139.0:6403 slave 1a30f25427cf9ca0a8362a50e6a313b933fe7df2 0 1555687115546 3 connected
1a30f25427cf9ca0a8362a50e6a313b933fe7df2 47.101.139.0:6400 master - 0 1555687117549 1 connected 0-5461
0dc1306db8bde9868846dacc631c80f6abe85ff3 47.101.139.0:6405 slave ba4547686e34ab121a8226291eb5ab019c53c5f8 0 1555687116547 5 connected
b99ed66e154468f0d2fc21ff4d7113bf0424262b 172.50.0.11:6401 myself,master - 0 0 2 connected 5462-10922
我們依照 Redis 協議手動建立一個集群。它由6個節點構成,3個主節點負責處理槽和相關數據,3個從節點負責故障轉移。手動搭建集群便於理解集群建立的流程和細節,但是我們從中發現集群搭建需要很多步驟,當集群節點眾多時,必然會加大搭建集群的復雜度和運維成本。因此 Redis 官方提供了 redis-trib.rb 工具方便我們快速搭建集群
5. 操作集群
-c 集群模式
[[email protected] config]# redis-cli -c -h 47.101.139.0 -p 6400 set mayun alibaba-996
[[email protected] config]# redis-cli -c -h 47.101.139.0 -p 6400 get mayun
"alibaba-996"
如果沒有指定集群模式,那麽會出現如下錯誤
[[email protected] config]# redis-cli -h 47.101.139.0 -p 6400 set mayun alibaba-996
(error) MOVED 11165 47.101.139.0:6402
所有命令:
CLUSTER info:打印集群的信息
CLUSTER nodes:列出集群當前已知的所有節點(node)的相關信息
CLUSTER meet <ip> <port>:將ip和port所指定的節點添加到集群當中
CLUSTER addslots <slot> [slot ...]:將一個或多個槽(slot)指派(assign)給當前節點
CLUSTER delslots <slot> [slot ...]:移除一個或多個槽對當前節點的指派
CLUSTER slots:列出槽位、節點信息
CLUSTER slaves <node_id>:列出指定節點下面的從節點信息
CLUSTER replicate <node_id>:將當前節點設置為指定節點的從節點
CLUSTER saveconfig:手動執行命令保存保存集群的配置文件,集群默認在配置修改的時候會自動保存配置文件
CLUSTER keyslot <key>:列出key被放置在哪個槽上
CLUSTER flushslots:移除指派給當前節點的所有槽,讓當前節點變成一個沒有指派任何槽的節點
CLUSTER countkeysinslot <slot>:返回槽目前包含的鍵值對數量
CLUSTER getkeysinslot <slot> <count>:返回count個槽中的鍵
CLUSTER setslot <slot> node <node_id> 將槽指派給指定的節點,如果槽已經指派給另一個節點,那麽先讓另一個節點刪除該槽,然後再進行指派
CLUSTER setslot <slot> migrating <node_id> 將本節點的槽遷移到指定的節點中
CLUSTER setslot <slot> importing <node_id> 從 node_id 指定的節點中導入槽 slot 到本節點
CLUSTER setslot <slot> stable 取消對槽 slot 的導入(import)或者遷移(migrate)
CLUSTER failover:手動進行故障轉移
CLUSTER forget <node_id>:從集群中移除指定的節點,這樣就無法完成握手,過期時為60s,60s後兩節點又會繼續完成握手
CLUSTER reset [HARD|SOFT]:重置集群信息,soft是清空其他節點的信息,但不修改自己的id,hard還會修改自己的id,不傳該參數則使用soft方式
CLUSTER count-failure-reports <node_id>:列出某個節點的故障報告的長度
CLUSTER SET-CONFIG-EPOCH:設置節點epoch,只有在節點加入集群前才能設置
redis-trib.rb 搭建集群
redis-trib.rb 是采用 Ruby 實現的 Redis 集群管理工具。內部通過 Cluster 相關命令幫我們簡化集群創建、檢查、槽遷移和均衡等常見運維操作,使用之前需要安裝 Ruby 依賴環境。下面介紹搭建集群的詳細步驟
內部通過 Cluster 相關命令幫我們簡化集群創建、檢查、槽遷移和均衡等常見運維操作,使用之前需要安裝 Ruby 依賴環境,相關擴展這個在dockerfile文件當中已經寫了指令,查看下理解意思就可以了
啟動好6個節點之後,使用 redis-trib.rb create 命令完成節點握手和槽分配過程
redis-trib.rb create --replicas 1 47.101.139.0:6400 47.101.139.0:6401 47.101.139.0:6402 47.101.139.0:6403 47.101.139.0:6404 47.101.139.0:6405
--replicas 參數指定集群中每個主節點配備幾個從節點,這裏設置為1,redis-trib.rb 會盡可能保證主從節點不分配在同一機器下,因此會重新排序節點列表順序。節點列表順序用於確定主從角色,先主節點之後是從節點。創建過程中首先會給出主從節點角色分配的計劃,並且會生成報告
命令說明:
redis-trib.rb help
Usage: redis-trib <command> <options> <arguments ...>
#創建集群
create host1:port1 ... hostN:portN
--replicas <arg> #帶上該參數表示是否有從,arg表示從的數量
#檢查集群
check host:port
#查看集群信息
info host:port
#修復集群
fix host:port
--timeout <arg>
#在線遷移slot
reshard host:port #個是必傳參數,用來從一個節點獲取整個集群信息,相當於獲取集群信息的入口
--from <arg> #需要從哪些源節點上遷移slot,可從多個源節點完成遷移,以逗號隔開,傳遞的是節點的node id,還可以直接傳遞--from all,這樣源節點就是集群的所有節點,不傳遞該參數的話,則會在遷移過程中提示用戶輸入
--to <arg> #slot需要遷移的目的節點的node id,目的節點只能填寫一個,不傳遞該參數的話,則會在遷移過程中提示用戶輸入。
--slots <arg> #需要遷移的slot數量,不傳遞該參數的話,則會在遷移過程中提示用戶輸入。
--yes #設置該參數,可以在打印執行reshard計劃的時候,提示用戶輸入yes確認後再執行reshard
--timeout <arg> #設置migrate命令的超時時間。
--pipeline <arg> #定義cluster getkeysinslot命令一次取出的key數量,不傳的話使用默認值為10。
#平衡集群節點slot數量
rebalance host:port
--weight <arg>
--auto-weights
--use-empty-masters
--timeout <arg>
--simulate
--pipeline <arg>
--threshold <arg>
#將新節點加入集群
add-node new_host:new_port existing_host:existing_port
--slave
--master-id <arg>
#從集群中刪除節點
del-node host:port node_id
#設置集群節點間心跳連接的超時時間
set-timeout host:port milliseconds
#在集群全部節點上執行命令
call host:port command arg arg .. arg
#將外部redis數據導入集群
import host:port
--from <arg>
--copy
--replace
使用Docker 手動&redis-trib.rb方式創建 Redis-Cluster 實驗