1 引言
網上很多文章會把集群和主從復制混為一談,其實這兩者是存在本質差異的,各自解決的問題不同。Redis在單機/單節點/單實例存在的風險:單點故障、容量有限、並發壓力問題。Redis主從復制配合Sentinel故障監控和轉移主要解決的是單點故障和並發壓力,並沒有解決服務器內存有限問題。
注意:內存不是單純的砸錢的問題,當服務器內存過大后對持久化和主從切換都是比較費時間的。所以,通常Redis服務器內存不會設置的太大。通過Redis集群擴容收容來解決內存有限問題。
本文主要針對Redis服務器容量有限問題進行集群模型推導,並通過主流代理和官方Redis Cluster實現Redis集群。
2 集群模型推導
2.1 AKF服務拆分
X抽拆分
當我們業務的訪問上升后,單台Redis承載不了訪問壓力時,通常會進行橫向擴容即如下圖所示,通過橫向的全量、鏡像部署多個副本節點分擔讀請求,實現讀寫分離/主備,從而提高Redis的訪問性能。
Y軸拆分
通過X軸橫向拆分解決了並發讀的問題,如果某些功能被頻繁訪問,涉及到的數據頻繁讀寫,這是可以將這部分獨立出來,按不同的業務將數據拆分。如下圖
Z軸拆分
在上面的AFK原則X-Y拆分之后,對服務器做了主從主備復制,然后做了業務拆分,不同的Redis負責不同的業務請求,這時候隨着業務的發展,可能會存在某些業務訪問量要明顯高於其他業務,例如對於Y軸上一個Redis,它負責某一樣業務,但是這個業務的數據訪問巨大,那就只好對數據請求進行AFK的Z軸拆分。先分析下數據請求的情況,然后根據訪問來源,分為北京的、上海的這樣不同的Redis雖然是負責不同的數據,但是負責的業務是一樣的。AFK拆分圖示:
AFK總結
X軸拆分:水平復制,就是單體系統多運行幾個實例,做集群加負載均衡的模式,主主、主備、主從。
Y軸拆分:基於不同的業務拆分。
Z軸拆分:基於數據拆分。
沒有最好的架構,只有更適合的架構。不要為了技術而技術!
2.2 集群數據存取
2.2.1 數據分類存取
數據分類存儲即將數據按邏輯業務拆分存儲到不同的Redis服務器中,適用於業務之間數據交集不多。具體圖示如下:
分類儲存前提是數據可以進行拆分,如果數據不能拆分這種模式就不適用了。
2.2.2 Sharding分片存取
當數據不能按業務拆分,可以使用分片存儲。如果將某條數據存儲到Redis后,查詢的時候希望可以直接去這台數據庫查詢,通常可以把數據的固定值通過一定的算法進行計算后再存儲,當取的時候同樣通過該值計算后再去查指定的Redis,常用的方式如下:
- Modula(Hash+取模)
缺點:取模的數須固定,即服務器台數需固定,否則會存在數據查不到的情況。比如,原有2台服務器,用戶A的id通過Hash計算后與2取模,結算結果存在node1中。當服務器擴展到3台后,用戶A的id通過Hash計算后需要與3取模,計算結果可能就不在node1上了。所以,這種模式會影響分布式下的擴展性。
2. Random(隨機存儲)
即隨機將數據存儲到某台Redis中,但是缺點也很明顯,數據隨機存儲后,取數據就不方便了。但是,這種模式有一些特定的場景是可以使用的,比如消息隊列。
3. Kemata(一致性哈希)
一致性Hash算法也是使用取模的方法,只是,剛才描述的取模法是對服務器的數量進行取模,而一致性Hash算法是對2^ 32-1取模。
第一步:將服務器的固定值(IP或主機名,建議主機名)Hash計算后對2^ 32-1取模,得到0到2^ 32的值,對應到Hash環上,如下圖:
第二步:將需要存儲的數據,按第一步相同的Hash計算,同樣對2^ 32-1取模,得到0到2^ 32的值同樣對應到虛擬Hash環上。將數據從所在位置順時針找第一台遇到的服務器節點,這個節點就是該key存儲的服務器。
例如我們有k1、k2兩個key,經過哈希計算后,在環空間上的位置如下:k1存儲在node1,k2存儲在node2。
一致性hash算法主要應用於分布式存儲系統中,可以有效地解決分布式存儲結構下普通余數Hash算法帶來的伸縮性差的問題,可以保證在動態增加和刪除節點的情況下盡量有多的請求命中原來的機器節點。
-
優點:對於節點的增減都只需重定位環空間中的一小部分數據,具有較好的容錯性和可擴展性。
-
缺點:一致性Hash算法在服務節點太少時,容易因為節點分部不均勻而造成數據傾斜問題(被緩存的對象大部分集中緩存在某一台服務器上)。為了解決數據傾斜問題,一致性Hash算法引入了虛擬節點機制,即對每一個服務節點計算多個哈希,每個計算結果位置都放置一個此服務節點,稱為虛擬節點。多個虛擬節點可以減少數據的傾斜。
Redis 集群沒有使用一致性hash, 而是在第一種Hash+取模的基礎上引入了哈希槽slots的概念。接下來我們分析下Redis的實現。
- Redis集群的分片存取
- Redis Cluster 特性之一是引入了槽的概念。一個redis集群包含 16384 個哈希槽。
- 集群時,會將16384個哈希槽分別分配給每個Master節點,每個Master節點占16384個哈希槽中的一部分。
- 執行GET/SET/DEL時,都會根據key進行操作,Redis通過CRC16算法對key進行計算得到該key所屬Redis節點。
- 根據key去指定Redis節點操作數據。
2.3 集群訪問
當客戶端連接Redis集群時,如果直接連server端造成的壓力很大,如下圖,4個人客戶端連接2台Redis,每台Redis都需要和所有客戶端連接。
因此,我們首先考慮的是同代理減少服務端連接,如下圖
通過代理確實可以解決服務端連接問題,但是proxy服務的單點問題就暴露了,為了解決單點問題,我們把架構調整如下:
如上圖,通過LVS+KeepAlived實現proxy的高用,從而解決了這一系列問題(具體架構詳解可以參考另一篇文章《LVS+KeepAlived高可用部署架構》)。接下來我們講解下Redis集群的具體搭建,proxy和Redis Cluster。
3 Redis集群搭建
無論是為了解決redis的高可用問題、還是為了可擴展性、或者是為了維護方便,用一款redis代理都是上佳的選擇。在github上有眾多開源的redis代理,本章中主要針對Twemproxy、Predixyr進行搭建Redis集群。
3.1 Twemproxy
Twemproxy是Twitter維護的(緩存)代理系統,代理Memcached的ASCII協議和Redis協議。它是單線程程序,使用c語言編寫,運行起來非常快。它是采用Apache2.0 license的開源軟件。 Twemproxy支持自動分區,如果其代理的其中一個Redis節點不可用時,會自動將該節點排除(這將改變原來的keys-instances的映射關系,所以你應該僅在把Redis當緩存時使用Twemproxy)。
Twemproxy本身不存在單點問題,因為你可以啟動多個Twemproxy實例,然后讓你的客戶端去連接任意一個Twemproxy實例。
Twemproxy是Redis客戶端和服務器端的一個中間層,由它來處理分區功能應該不算復雜,並且應該算比較可靠的。
3.1.1 Twemproxy安裝
官網地址:GItHub
# 下載安裝包
wget https://github.com/twitter/twemproxy/releases/download/0.5.0/twemproxy-0.5.0.tar.gz
tar xf twemproxy-0.5.0.tar.gz
# twemproxy運行需要automake和 libtool
yum install automake libtool -y
cd twemproxy
autoreconf -fvi
./configure
make
# 復制配置文件及加入系統命令
cp scripts/nutcracker.init /etc/init.d/twemproxy
chmod +x /etc/init.d/twemproxy
mkdir /etc/nutcracker
cp conf/* /etc/nutcracker/
cp src/nutcracker /usr/bin/
cd /etc/nutcracker/
# 修改配置文件
cp nutcracker.yml nutcracker.yml.bak
修改后配置,按需修改,這里使用的是本機2台Redis測試
alpha:
listen: 127.0.0.1:22121
hash: fnv1a_64
distribution: ketama
auto_eject_hosts: true
redis: true
server_retry_timeout: 2000
server_failure_limit: 1
servers:
- 127.0.0.1:6379:1
- 127.0.0.1:6380:1
3.1.2 手動啟動2個Redis實例
創建2個Redis目錄 6379 6380 分別進入2個目錄執行啟動命令
啟動Redis
# 進入6379文件夾
redis-server --port 6379
# 進入6380文件夾
redis-server --port 6380
查看
啟動twemproxy
server twemproxy start
3.1.3 驗證
連接twemproxy代理的Redis,注意此處端口使用twemproxy端口號
redis-cli -p 22121
通過在22121端口中set數據,最終會按設置的規則分布在6379和63802台Redis服務器。
22121代理:
6379
6380
到此已實現通過twemproxy代理2台Redis服務器進行數據分布儲存,后續用戶存取只用連接代理服務器即可,從而解決單台Redis內存有限問題。
使用twemproxy代理Redis進行數據分治后會存在的問題:代理層不支持查詢全部數據,不支持事務。
3.2 Predixy
Predixy是一款redis代理的開源程序,支持一套master-slave事務,支持redis單機,多機,集群的代理,對於客戶端是感應不到的。 要求是c++11編譯。
常用Redis代理對比
接下來演示Predixy代理搭建。
3.2.1 安裝
官方下載地址:GItHub
wget https://github.com/joyieldInc/predixy/releases/download/1.0.5/predixy-1.0.5-bin-amd64-linux.tar.gz
tar xf predixy-1.0.5-bin-amd64-linux.tar.gz
3.2.2 配置
修改predixy.conf
vi predixy-1.0.5/conf/predixy.conf
打開綁定端口
打開sentinel配置
修改sentinel配置
vi predixy-1.0.5/conf/sentinel.conf
內容如下
SentinelServerPool {
Databases 16
Hash crc16
HashTag "{}"
Distribution modula
MasterReadPriority 60
StaticSlaveReadPriority 50
DynamicSlaveReadPriority 50
RefreshInterval 1
ServerTimeout 1
ServerFailureLimit 10
ServerRetryTimeout 1
KeepAlive 120
# 哨兵IP就端口
Sentinels {
+ 127.0.0.1:8001
+ 127.0.0.1:8002
+ 127.0.0.1:8003
}
# 分組
Group beijin {
}
Group shanghai {
}
}
3.2.3 啟動sentinel集群
集群拓撲圖
說明:通過sentinel集群端口為:8001、8002、8003 監控2套Redis主從beijin和shanghai。
Redis主從一:beijin Master端口為6001,Slave 6002;
Redis主從一: shanghai Master端口為7001,Slave端口7002;
sentinel配置
beijin和shanghai為Redis主從分組,對應predixy.conf
中的Grpoup
8001:
port 8001
sentinel monitor beijin 127.0.0.1 6001 2
sentinel monitor shanghai 127.0.0.1 7001 2
8002:
port 8002
sentinel monitor beijin 127.0.0.1 6001 2
sentinel monitor shanghai 127.0.0.1 7001 2
8003:
port 8003
sentinel monitor beijin 127.0.0.1 6001 2
sentinel monitor shanghai 127.0.0.1 7001 2
配置文件列表
啟動3台sentinel
redis-server 8001.conf --sentinel
redis-server 8002.conf --sentinel
redis-server 8003.conf --sentinel
查看啟動狀態
3.2.4 啟動2套Redis主從
mkdir 6001
mkdir 6002
mkdir 7001
mkdir 7002
分別進入4個文件夾啟動對應Redis
# 6001文件夾
redis-server --port 6001
# 6002文件夾
redis-server --port 6002 --replicaof 127.0.0.1 6001
# 7001文件夾
redis-server --port 7001
# 7002文件夾
redis-server --port 7002 --replicaof 127.0.0.1 7001
查看啟動進程
啟動predixy
cd predixy-1.0.5/bin/
./predixy ../conf/predixy.conf
啟動日志
3.2.5 驗證
- 驗證數據存取
客戶端通過7617端口連接predixy代理,進而操作集群。
redis-cli -p 7617
通過代理服務器set k1和k2 數據,分別在2套Redis集群中查看數據分布
代理服務器
Redis主從一:beijin
Redis主從二:shanghai
- 驗證通過代理服務器將數據存儲到指定的Redis集群
代理服務器指定將k3和k4存儲到代號beijin的Redis主從,將k5和k6存儲到代號shanghai主從中
beijin主從
shanghai主從
- 驗證集群事務
發現當存在2套Redis組集群時predixy依舊不支持事務,predixy只支持單組集群下的事務。
停止predixy修改predixy.conf
vi predixy-1.0.5/conf/sentinel.conf
修改后配置
重新啟動predixy並驗證
通過測試發現單組集群predixy是可以支持事務的,接下來演示Redis Cluster集群模式
3.3 Redis Cluster集群搭建
3.3.1 使用官方提供的腳本搭建Redis Cluster集群
- 修改配置
# 進入Redis源碼目錄
cd redis-6.2.6/utils/create-cluster
vi create-cluster
修改nodes節點數量和replicas副本數量,我這使用3組 每組1主1從測試,修改后的結果
2. 啟動集群
# 啟動集群
./create-cluster start
# 分哈希槽
./create-cluster create
如下截圖,已經將16384個slots分給3台master,並且反饋了每台master對應分得的slots編號及數量
3.3.2 手動創建集群並分配哈希槽
目前是Redis cluster 自動幫我們設置的集群信息以及分配的哈希槽,也可以手動創建和設置
手動創建集群並分配哈希槽
# 停止集群
./create-cluster stop
# 清理已分配的哈希槽信息
./create-cluster clean
# 創建集群 --cluster-replicas 1 即 1主1從
redis-cli --cluster create 127.0.0.1:30001 127.0.0.1:30002 127.0.0.1:30003 127.0.0.1:30004 127.0.0.1:30005 127.0.0.1:30006 --cluster-replicas 1
手動設置的集群哈希槽分配情況
節點 | 哈希槽 | 數量 |
---|---|---|
30001 | 0-5460 | 5461 |
30002 | 5461-10922 | 5462 |
30003 | 10923-16383 | 5461 |
假設我們需要將30001節點的槽移動1000個到30003
# 連接Redis集群 隨便集群中的哪個節點都可以
[root@q110 create-cluster]# redis-cli --cluster reshard 127.0.0.1:30001
>>> Performing Cluster Check (using node 127.0.0.1:30001)
M: 8c7f5c199ebe53e12a639ab2e8920f99f9259b78 127.0.0.1:30001
slots:[0-5460] (5461 slots) master
1 additional replica(s)
S: 2f340a56e79d2a437a54a41460c73e6f05349431 127.0.0.1:30005
slots: (0 slots) slave
replicates 8c7f5c199ebe53e12a639ab2e8920f99f9259b78
M: a708bb61eac6f71c3e55557fe818fa9a96045a02 127.0.0.1:30002
slots:[5461-10922] (5462 slots) master
1 additional replica(s)
S: a2dd0cd2673facab097910d99bb12ca78c4e7e31 127.0.0.1:30006
slots: (0 slots) slave
replicates a708bb61eac6f71c3e55557fe818fa9a96045a02
S: 0868622a962e035646be41403fcabbab6d3af022 127.0.0.1:30004
slots: (0 slots) slave
replicates 8c16df7dbaf6d1c70eb63f369d048744c03ae3fc
M: 8c16df7dbaf6d1c70eb63f369d048744c03ae3fc 127.0.0.1:30003
slots:[10923-16383] (5461 slots) master
1 additional replica(s)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
# 需要移動的槽數量1000
How many slots do you want to move (from 1 to 16384)? 1000
# 分配給誰節點ID?
What is the receiving node ID? 8c16df7dbaf6d1c70eb63f369d048744c03ae3fc
Please enter all the source node IDs.
Type 'all' to use all the nodes as source nodes for the hash slots.
Type 'done' once you entered all the source nodes IDs.
# 從哪個節點取出
Source node #1: 8c7f5c199ebe53e12a639ab2e8920f99f9259b78
# 結束
Source node #2: done
查看節點信息
redis-cli --cluster info 127.0.0.1:30001
redis-cli --cluster check 127.0.0.1:30001
截圖中可以看出30001節點0-999共1000個槽已經移動到了30003
3.3.3 測試集群
測試數據存取
如下圖,我們使用Redis-cli -p 30001連接后set k1 xxx會報錯,提示set k1 需要到30003節點操作。通過redis-cli -c -p 30001連接Redis Cluster集群后 set k1 xxx會自動跳轉到30003節點。
上面的截圖具體原因是Redis Cluster moved重定向問題:
客戶端向Redis Cluster的任意節點發送命令,接收命令的節點會根據CRC16規則進行hash運算與16384取余,計算自己的槽和對應節點;如果保存數據的槽被分配給當前節點,則去槽中執行命令,並把命令執行結果返回給客戶端;如果保存數據的槽不在當前節點的管理范圍內,則向客戶端返回moved重定向異常;客戶端接收到節點返回的結果,如果是moved異常,則從moved異常中獲取目標節點的信息;客戶端向目標節點發送命令,獲取命令執行結果。
測試事務
127.0.0.1:30003> set k2 kkd
-> Redirected to slot [449] located at 127.0.0.1:30001
OK
127.0.0.1:30001> WATCH k1
-> Redirected to slot [12706] located at 127.0.0.1:30003
OK
127.0.0.1:30003> MULTI
OK
127.0.0.1:30003(TX)> set k2 asd
-> Redirected to slot [449] located at 127.0.0.1:30001
OK
127.0.0.1:30001> exec
(error) ERR EXEC without MULTI
報錯了,原因是事務在30003節點開啟,事務中我們set k2 跳轉到了30001 然后提交事務在30001提交。這種情況Redis cluster 自身是不支持的,可以通過認為處理,類似Predixy代理的處理方式,在key前面加上固定的字符,比如下面的測試
127.0.0.1:30002> set {beijin}k2 jjjjd
OK
127.0.0.1:30002> set {beijin}k1 jjjjd
OK
127.0.0.1:30002> MULTI
OK
127.0.0.1:30002(TX)> set {beijin}k2 lajdjddda
QUEUED
127.0.0.1:30002(TX)> exec
1) OK
127.0.0.1:30002>
至此Redis集群模式已經結束,但是實際工作中很多時候都是通過docker創建和管理集群,下一篇講解Docker搭建Redis集群及擴容收容