Redis详解(三)
一、Redis集群介紹
Clustering:
redis 3.0之后進(jìn)入生產(chǎn)環(huán)境
分布式數(shù)據(jù)庫(kù),通過分片機(jī)制來進(jìn)行數(shù)據(jù)分布,clustering 內(nèi)的每個(gè)節(jié)點(diǎn),僅有數(shù)據(jù)庫(kù)的一部分?jǐn)?shù)據(jù);
去中心化的集群:
redis集群中的每一個(gè)節(jié)點(diǎn),都可以作為集群的接入節(jié)點(diǎn)。//每一個(gè)node都有全局元數(shù)據(jù),client訪問哪一個(gè)node都可以獲取所有的node
client向任意node發(fā)起請(qǐng)求,該node會(huì)返回給client真正的key所在node,然后讓client去獲取。
每一個(gè)節(jié)點(diǎn)持有全局元數(shù)據(jù),但僅持有部分?jǐn)?shù)據(jù)。
redis分布式的常見解決方案:
Twemproxy(Twitter)
Codis(豌豆莢)
Redis Cluster(官方)
Cerberus(芒果TV)
1、Twemproxy特點(diǎn):
代理分片機(jī)制
優(yōu)點(diǎn):
非常穩(wěn)定,企業(yè)級(jí)方案
缺點(diǎn):
單點(diǎn)故障
需依賴第三方軟件,例如keepalived //進(jìn)行HA
無法平滑地橫向擴(kuò)展
沒有后臺(tái)界面
代理分片機(jī)制引入更多的來回次數(shù)并提高延遲
單核模式,無法重新利用多核,除非多實(shí)例
Twitter官方內(nèi)部不再私用Twemproxy
圖1:
2、Codis(豌豆莢)
代理分片機(jī)制
2014年開源
基于Go以及C語言研發(fā)
優(yōu)點(diǎn):
文檔充足,企業(yè)級(jí)方案
數(shù)據(jù)自動(dòng)平衡
高性能
簡(jiǎn)單的測(cè)試顯示較Twemproxy快一倍
善用多核CPU
簡(jiǎn)單:
沒有paxos類的協(xié)調(diào)機(jī)制
沒有主從復(fù)制
有后臺(tái)界面
缺點(diǎn):
代理分片機(jī)制引入更多的來回次數(shù)并提高延遲 //更多的ping(pong)操作
需要第三方軟件支持協(xié)調(diào)機(jī)制 //目前支持Zookeeper及Etcd
不支持主從復(fù)制,需要另外實(shí)現(xiàn)
Codis采用proxy方案,所以必然會(huì)帶來單機(jī)性能的損失,
靜測(cè)試,在不開pipeline的情況下,大概會(huì)損失40%左右的性能
3、Redis cluster(官方)
官方實(shí)現(xiàn)
需要Redis 3.0 或更高版本
優(yōu)點(diǎn):
無中心的p2p Gossip分散式模式
更少的來回次數(shù)并降低延遲
自動(dòng)于多個(gè)Redis節(jié)點(diǎn)進(jìn)行分片,自動(dòng)均衡
不需要第三方軟件支持協(xié)調(diào)機(jī)制
缺點(diǎn):
依賴于Redis 3.0或更高版本
需要時(shí)間驗(yàn)證其穩(wěn)定性
沒有后臺(tái)界面
需要智能客戶端 //萬一某個(gè)client下線了,client需要知道哪個(gè)node在線。
Redis客戶端必須支持Redis cluster架構(gòu)
較Codis有更多的維護(hù)升級(jí)版本
4、Cerberus(芒果TV)
優(yōu)點(diǎn):
數(shù)據(jù)自動(dòng)平衡
本身實(shí)現(xiàn)了Redis的smart client//server端直接實(shí)現(xiàn)了智能client
支持讀寫分離
缺點(diǎn):
依賴Redis 3.0或更高版本
代理分片機(jī)制引入更多的來回次數(shù)并增大延遲
需要時(shí)間驗(yàn)證其穩(wěn)定性
沒有后臺(tái)界面
codis-proxy : 是客戶端連接的Redis代理服務(wù),codis-proxy 本身實(shí)現(xiàn)了Redis協(xié)議,表現(xiàn)得和一個(gè)原生的Redis沒什么區(qū)別(就像Twemproxy),對(duì)于一個(gè)業(yè)務(wù)來說,可以部署多個(gè)codis-proxy,codis-proxy本身是沒狀態(tài)的。
codis-config :是Codis的管理工具,支持包括,添加/刪除Redis節(jié)點(diǎn),添加/刪除Proxy節(jié)點(diǎn),發(fā)起數(shù)據(jù)遷移等操作,codis-config本身還自帶了一個(gè)http server,會(huì)啟動(dòng)一個(gè)dashboard,用戶可以直接在瀏覽器上觀察Codis集群的狀態(tài)。
codis-server:是Codis項(xiàng)目維護(hù)的一個(gè)Redis分支,基于2.8.13開發(fā),加入了slot的支持和原子的數(shù)據(jù)遷移指令,Codis上層的codis-proxy和codis-config只能和這個(gè)版本的Redis交互才能正常運(yùn)行。
ZooKeeper :用來存放數(shù)據(jù)路由表和codis-proxy節(jié)點(diǎn)的元信息,codis-config發(fā)起的命令都會(huì)通過ZooKeeper同步到各個(gè)存活的codis-proxy
二、官方版安裝與實(shí)現(xiàn)
1、安裝與實(shí)現(xiàn)
redis-trib.rb的ruby腳本。redis-trib.rb是redis官方推出的管理redis集群的工具,集成在redis的源碼src目錄下(redis-xxx/src/)
redis-trib.rb是redis作者用ruby完成的。所以redis集群需要先安裝ruby環(huán)境。
[root@node112 redis]# redis-trib.rb --help
/usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require': cannot load such file -- redis (LoadError)from /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require'from /usr/local/bin/redis-trib.rb:25:in `<main>' 需要安裝ruby的client # gem install redis //要求ruby版本2.2之上,假如版本低更新ruby版本如下 # yum install autoconf automake bison libffi-devel libtool readline-devel sqlite-devel libyaml-devel # curl -sSL https://rvm.io/mpapis.asc | gpg --import - # curl -L get.rvm.io | bash -s stable # source /etc/profile.d/rvm.sh //加載RVM環(huán)境。 # rvm reload # rvm requirements run # rvm install 2.4.2 //安裝ruby2.4 設(shè)置默認(rèn)的ruby版本: # rvm list # rvm use 2.4.2 --default # ruby --version [root@node112 ~]# redis-trib.rb create --replicas 1 127.0.0.1:1201 127.0.0.1:1202 127.0.0.1:1203 127.0.0.1:1204 127.0.0.1:1205 127.0.0.1:1206 127.0.0.1:1207 127.0.0.1:1208 Can I set the above configuration? (type 'yes' to accept): yes >>> Nodes configuration updated >>> Assign a different config epoch to each node >>> Sending CLUSTER MEET messages to join the cluster Waiting for the cluster to join....... >>> Performing Cluster Check (using node 127.0.0.1:1201) M: d392057bb258248b09db27e57da53e6277bfbb87 127.0.0.1:1201slots:0-4095 (4096 slots) master1 additional replica(s) M: 8ee780051bba2d62514c3adddb6ad050128309c2 127.0.0.1:1204slots:12288-16383 (4096 slots) master1 additional replica(s) S: 84fc85c03e8cd1d20b794a36091ccfd5235d3403 127.0.0.1:1207slots: (0 slots) slavereplicates 32d3f692b64dd438d5ff9c4729be53c55d3768bb S: e92354f4f158e5c45c508cf494dfd99892437bb4 127.0.0.1:1208slots: (0 slots) slavereplicates 8ee780051bba2d62514c3adddb6ad050128309c2 S: a23562b6abf030b912f950c0e1fe2f576d4e1b8d 127.0.0.1:1205slots: (0 slots) slavereplicates 8251d8d4bde7dd099acb462094d2132b94457481 M: 32d3f692b64dd438d5ff9c4729be53c55d3768bb 127.0.0.1:1203slots:8192-12287 (4096 slots) master1 additional replica(s) S: b31e1d76d83c050c5870caaaf7a6807b3b5c16a3 127.0.0.1:1206slots: (0 slots) slavereplicates d392057bb258248b09db27e57da53e6277bfbb87 M: 8251d8d4bde7dd099acb462094d2132b94457481 127.0.0.1:1202slots:4096-8191 (4096 slots) master1 additional replica(s) [OK] All nodes agree about slots configuration. >>> Check for open slots... >>> Check slots coverage... [OK] All 16384 slots covered. [root@node112 ~]# [root@node112 ~]# netstat -tunlp |grep -i redis tcp 0 0 127.0.0.1:1201 0.0.0.0:* LISTEN 35965/redis-server tcp 0 0 127.0.0.1:1202 0.0.0.0:* LISTEN 35985/redis-server tcp 0 0 127.0.0.1:1203 0.0.0.0:* LISTEN 35990/redis-server tcp 0 0 127.0.0.1:1204 0.0.0.0:* LISTEN 35995/redis-server tcp 0 0 127.0.0.1:1205 0.0.0.0:* LISTEN 40784/redis-server tcp 0 0 127.0.0.1:1206 0.0.0.0:* LISTEN 40789/redis-server tcp 0 0 127.0.0.1:1207 0.0.0.0:* LISTEN 40794/redis-server tcp 0 0 127.0.0.1:1208 0.0.0.0:* LISTEN 40799/redis-server tcp 0 0 127.0.0.1:11201 0.0.0.0:* LISTEN 35965/redis-server tcp 0 0 127.0.0.1:11202 0.0.0.0:* LISTEN 35985/redis-server tcp 0 0 127.0.0.1:11203 0.0.0.0:* LISTEN 35990/redis-server tcp 0 0 127.0.0.1:11204 0.0.0.0:* LISTEN 35995/redis-server tcp 0 0 127.0.0.1:11205 0.0.0.0:* LISTEN 40784/redis-server tcp 0 0 127.0.0.1:11206 0.0.0.0:* LISTEN 40789/redis-server tcp 0 0 127.0.0.1:11207 0.0.0.0:* LISTEN 40794/redis-server tcp 0 0 127.0.0.1:11208 0.0.0.0:* LISTEN 40799/redis-serverRedis集群不僅需要開通redis客戶端連接的端口,而且需要開通集群總線端口,集群總線端口為redis客戶端連接的端口 + 10000
[root@node112 redis]# redis-cli -h 127.0.0.1 -p 1201 測(cè)試: 終端一:訂閱 [root@node112 ~]# redis-cli -h 127.0.0.1 -p 1206 127.0.0.1:1206> SUBSCRIBE mm 終端二:發(fā)布 [root@node112 redis]# redis-cli -h 127.0.0.1 -p 1201 127.0.0.1:1201> PUBLISH mm "test for messages" 在終端一上查看[root@node112 ~]# redis-cli -h 127.0.0.1 -p 1201 127.0.0.1:1201> set hello "hello worlld" OK [root@node112 ~]# redis-cli -h 127.0.0.1 -p 1205 -c //-c使用集群模式 127.0.0.1:1205> get hello -> Redirected to slot [866] located at 127.0.0.1:1201 "hello worlld"2、集群相關(guān)命令
redis-cluster把所有的物理節(jié)點(diǎn)映射到[0-16383]個(gè)slot(hash槽)上,由cluster負(fù)責(zé)維護(hù)node<->slot<->value
集群選舉容錯(cuò):節(jié)點(diǎn)fail選舉是過程是集群中所有master參與,半數(shù)以上master與被檢測(cè)node監(jiān)測(cè)超時(shí)(cluster-node-timeout),就認(rèn)為當(dāng)前master節(jié)點(diǎn)down
集群不可用:cluster:fail
1.集群任意master掛掉,且當(dāng)前master沒有slave。集群進(jìn)入fail狀態(tài),也可以理解成集群的slot映射[0-16838]不完整時(shí)進(jìn)入fail狀態(tài)。
2.集群半數(shù)以上master掛掉,收到error clusterdown The Cluster is down的錯(cuò)誤
集群優(yōu)缺點(diǎn):
優(yōu)點(diǎn):
在master節(jié)點(diǎn)下線后,slave節(jié)點(diǎn)會(huì)自動(dòng)提升為master,保證集群可用性
fail node復(fù)活后,自動(dòng)添加到集群中,變成slave節(jié)點(diǎn)。
缺點(diǎn):
redis的復(fù)制使用異步復(fù)制機(jī)制,auto-fail-over的過程中。集群可能會(huì)丟失write命令。然而redis幾乎是同時(shí)執(zhí)行(將命令恢復(fù)發(fā)送給客戶端,以及將命令復(fù)制到slave節(jié)點(diǎn))這兩個(gè)操作。所以實(shí)際操作中,丟失的可能性非常小
3、redis-trib.rb命令詳解
redis-trib.rb help //查看幫助信息
redis-trib.rb主要有兩個(gè)類:ClusterNode和RedisTrib。ClusterNode保存了每個(gè)節(jié)點(diǎn)的信息,RedisTrib則是redis-trib.rb各個(gè)功能的實(shí)現(xiàn)。
3.1、create:創(chuàng)建集群 //redis-trib.rb create --replicas 1 $host1:port1 $host2:port2 ...
1、首先為每個(gè)節(jié)點(diǎn)創(chuàng)建ClusterNode對(duì)象,包括連接每個(gè)節(jié)點(diǎn)。檢查每個(gè)節(jié)點(diǎn)是否為獨(dú)立且db為空的節(jié)點(diǎn)。執(zhí)行l(wèi)oad_info方法導(dǎo)入節(jié)點(diǎn)信息。
2、檢查傳入的master節(jié)點(diǎn)數(shù)量是否大于等于3個(gè)。只有大于3個(gè)節(jié)點(diǎn)才能組成集群。//master+slave大于6個(gè)
3、計(jì)算每個(gè)master需要分配的slot數(shù)量,以及給master分配slave。分配的算法大致如下:
先把節(jié)點(diǎn)按照host分類,這樣保證master節(jié)點(diǎn)能分配到更多的主機(jī)中。
不停遍歷遍歷host列表,從每個(gè)host列表中彈出一個(gè)節(jié)點(diǎn),放入interleaved數(shù)組。直到所有的節(jié)點(diǎn)都彈出為止。
master節(jié)點(diǎn)列表就是interleaved前面的master數(shù)量的節(jié)點(diǎn)列表。保存在masters數(shù)組。
計(jì)算每個(gè)master節(jié)點(diǎn)負(fù)責(zé)的slot數(shù)量,保存在slots_per_node對(duì)象,用slot總數(shù)除以master數(shù)量取整即可。
遍歷masters數(shù)組,每個(gè)master分配slots_per_node個(gè)slot,最后一個(gè)master,分配到16384個(gè)slot為止。
接下來為master分配slave,分配算法會(huì)盡量保證master和slave節(jié)點(diǎn)不在同一臺(tái)主機(jī)上。對(duì)于分配完指定slave數(shù)量的節(jié)點(diǎn),還有多余的節(jié)點(diǎn),也會(huì)為這些節(jié)點(diǎn)尋找master。分配算法會(huì)遍歷兩次masters數(shù)組。
第一次遍歷masters數(shù)組,在余下的節(jié)點(diǎn)列表找到replicas數(shù)量個(gè)slave。每個(gè)slave為第一個(gè)和master節(jié)點(diǎn)host不一樣的節(jié)點(diǎn),如果沒有不一樣的節(jié)點(diǎn),則直接取出余下列表的第一個(gè)節(jié)點(diǎn)。
第二次遍歷是在對(duì)于節(jié)點(diǎn)數(shù)除以replicas不為整數(shù),則會(huì)多余一部分節(jié)點(diǎn)。遍歷的方式跟第一次一樣,只是第一次會(huì)一次性給master分配replicas數(shù)量個(gè)slave,而第二次遍歷只分配一個(gè),直到余下的節(jié)點(diǎn)被全部分配出去。
4、打印出分配信息,并提示用戶輸入“yes”確認(rèn)是否按照打印出來的分配方式創(chuàng)建集群。
5、輸入“yes”后,會(huì)執(zhí)行flush_nodes_config操作,該操作執(zhí)行前面的分配結(jié)果,給master分配slot,讓slave復(fù)制master,對(duì)于還沒有握手(cluster meet)的節(jié)點(diǎn),slave復(fù)制操作無法完成,不過沒關(guān)系,flush_nodes_config操作出現(xiàn)異常會(huì)很快返回,后續(xù)握手后會(huì)再次執(zhí)行flush_nodes_config。
6、給每個(gè)節(jié)點(diǎn)分配epoch,遍歷節(jié)點(diǎn),每個(gè)節(jié)點(diǎn)分配的epoch比之前節(jié)點(diǎn)大1。
7、節(jié)點(diǎn)間開始相互握手,握手的方式為節(jié)點(diǎn)列表的其他節(jié)點(diǎn)跟第一個(gè)節(jié)點(diǎn)握手。
8、然后每隔1秒檢查一次各個(gè)節(jié)點(diǎn)是否已經(jīng)消息同步完成,使用ClusterNode的get_config_signature方法,檢查的算法為獲取每個(gè)節(jié)點(diǎn)cluster nodes信息,排序每個(gè)節(jié)點(diǎn),組裝成node_id1:slots|node_id2:slot2|...的字符串。如果每個(gè)節(jié)點(diǎn)獲得字符串都相同,即認(rèn)為握手成功。
9、此后會(huì)再執(zhí)行一次flush_nodes_config,這次主要是為了完成slave復(fù)制操作。
10、最后再執(zhí)行check_cluster,全面檢查一次集群狀態(tài)。包括和前面握手時(shí)檢查一樣的方式再檢查一遍。確認(rèn)沒有遷移的節(jié)點(diǎn)。確認(rèn)所有的slot都被分配出去了。
11、至此完成了整個(gè)創(chuàng)建流程,返回[OK] All 16384 slots covered.。
3.2、check檢查集群狀態(tài)
[root@node112 redis]# redis-trib.rb check 127.0.0.1:1201
檢查集群狀態(tài),只需要選擇一個(gè)集群中的一個(gè)節(jié)點(diǎn)即可。
檢查前會(huì)先執(zhí)行l(wèi)oad_cluster_info_from_node方法,把所有節(jié)點(diǎn)數(shù)據(jù)load進(jìn)來。load的方式為通過自己的cluster nodes發(fā)現(xiàn)其他節(jié)點(diǎn),然后連接每個(gè)節(jié)點(diǎn),并加入nodes數(shù)組。接著生成節(jié)點(diǎn)間的復(fù)制關(guān)系。
load完數(shù)據(jù)后,開始檢查數(shù)據(jù),檢查的方式也是調(diào)用創(chuàng)建時(shí)候使用的check_cluster。
3.3、info查看集群信息
info命令也是先執(zhí)行l(wèi)oad_cluster_info_from_node獲取完整的集群信息。然后顯示ClusterNode的info_string結(jié)果
[root@node112 redis]# redis-trib.rb info 127.0.0.1:1201 127.0.0.1:1204 (9a60d0fc...) -> 0 keys | 4096 slots | 1 slaves. 127.0.0.1:1203 (31f66735...) -> 1 keys | 4096 slots | 1 slaves. 127.0.0.1:1209 (a4dc7dd4...) -> 0 keys | 4096 slots | 2 slaves. 127.0.0.1:1202 (56b03fec...) -> 0 keys | 4096 slots | 1 slaves. [OK] 1 keys in 4 masters. 0.00 keys per slot on average.3.4、fix修復(fù)集群
目前fix命令能修復(fù)兩種異常,一種是集群有處于遷移中的slot的節(jié)點(diǎn),一種是slot未完全分配的異常。
fix_open_slot方法是修復(fù)集群有處于遷移中的slot的節(jié)點(diǎn)異常。
1、先檢查該slot是誰負(fù)責(zé)的,遷移的源節(jié)點(diǎn)如果沒完成遷移,owner還是該節(jié)點(diǎn)。沒有owner的slot無法完成修復(fù)功能。
2、遍歷每個(gè)節(jié)點(diǎn),獲取哪些節(jié)點(diǎn)標(biāo)記該slot為migrating狀態(tài),哪些節(jié)點(diǎn)標(biāo)記該slot為importing狀態(tài)。對(duì)于owner不是該節(jié)點(diǎn),但是通過cluster countkeysinslot獲取到該節(jié)點(diǎn)有數(shù)據(jù)的情況,也認(rèn)為該節(jié)點(diǎn)為importing狀態(tài)。
3、如果migrating和importing狀態(tài)的節(jié)點(diǎn)均只有1個(gè),這可能是遷移過程中redis-trib.rb被中斷所致,直接執(zhí)行move_slot繼續(xù)完成遷移任務(wù)即可。傳遞dots和fix為true。
4、如果migrating為空,importing狀態(tài)的節(jié)點(diǎn)大于0,那么這種情況執(zhí)行回滾流程,將importing狀態(tài)的節(jié)點(diǎn)數(shù)據(jù)通過move_slot方法導(dǎo)給slot的owner節(jié)點(diǎn),傳遞dots、fix和cold為true。接著對(duì)importing的節(jié)點(diǎn)執(zhí)行cluster stable命令恢復(fù)穩(wěn)定。
5、如果importing狀態(tài)的節(jié)點(diǎn)為空,有一個(gè)migrating狀態(tài)的節(jié)點(diǎn),而且該節(jié)點(diǎn)在當(dāng)前slot沒有數(shù)據(jù),那么可以直接把這個(gè)slot設(shè)為stable。
6、如果migrating和importing狀態(tài)不是上述情況,目前redis-trib.rb工具無法修復(fù),上述的三種情況也已經(jīng)覆蓋了通過redis-trib.rb工具遷移出現(xiàn)異常的各個(gè)方面,人為的異常情形太多,很難考慮完全。
fix_slots_coverage方法能修復(fù)slot未完全分配的異常。未分配的slot有三種狀態(tài)。
1、所有節(jié)點(diǎn)的該slot都沒有數(shù)據(jù)。該狀態(tài)redis-trib.rb工具直接采用隨機(jī)分配的方式,并沒有考慮節(jié)點(diǎn)的均衡。本人嘗試對(duì)沒有分配slot的集群通過fix修復(fù)集群,結(jié)果slot還是能比較平均的分配,但是沒有了連續(xù)性,打印的slot信息非常離散。
2、有一個(gè)節(jié)點(diǎn)的該slot有數(shù)據(jù)。該狀態(tài)下,直接把slot分配給該slot有數(shù)據(jù)的節(jié)點(diǎn)。
3、有多個(gè)節(jié)點(diǎn)的該slot有數(shù)據(jù)。此種情況目前還處于TODO狀態(tài),不過redis作者列出了修復(fù)的步驟,對(duì)這些節(jié)點(diǎn),除第一個(gè)節(jié)點(diǎn),執(zhí)行cluster migrating命令,然后把這些節(jié)點(diǎn)的數(shù)據(jù)遷移到第一個(gè)節(jié)點(diǎn)上。清除migrating狀態(tài),然后把slot分配給第一個(gè)節(jié)點(diǎn)。
3.5、reshard在線遷移slot
在線把集群的一些slot從集群原來slot負(fù)責(zé)節(jié)點(diǎn)遷移到新的節(jié)點(diǎn),利用reshard可以完成集群的在線橫向擴(kuò)容和縮容。
1、通過load_cluster_info_from_node方法裝載集群信息。
2、執(zhí)行check_cluster方法檢查集群是否健康。只有健康的集群才能進(jìn)行遷移。
3、獲取需要遷移的slot數(shù)量,用戶沒傳遞--slots參數(shù),則提示用戶手動(dòng)輸入。
4、獲取遷移的目的節(jié)點(diǎn),用戶沒傳遞--to參數(shù),則提示用戶手動(dòng)輸入。此處會(huì)檢查目的節(jié)點(diǎn)必須為master節(jié)點(diǎn)。
5、獲取遷移的源節(jié)點(diǎn),用戶沒傳遞--from參數(shù),則提示用戶手動(dòng)輸入。此處會(huì)檢查源節(jié)點(diǎn)必須為master節(jié)點(diǎn)。--from all的話,源節(jié)點(diǎn)就是除了目的節(jié)點(diǎn)外的全部master節(jié)點(diǎn)。這里為了保證集群slot分配的平均,建議傳遞--from all。
6、執(zhí)行compute_reshard_table方法,計(jì)算需要遷移的slot數(shù)量如何分配到源節(jié)點(diǎn)列表,采用的算法是按照節(jié)點(diǎn)負(fù)責(zé)slot數(shù)量由多到少排序,計(jì)算每個(gè)節(jié)點(diǎn)需要遷移的slot的方法為:遷移slot數(shù)量 * (該源節(jié)點(diǎn)負(fù)責(zé)的slot數(shù)量 / 源節(jié)點(diǎn)列表負(fù)責(zé)的slot總數(shù))。這樣算出的數(shù)量可能不為整數(shù),這里代碼用了下面的方式處理:
這樣的處理方式會(huì)帶來最終分配的slot與請(qǐng)求遷移的slot數(shù)量不一致,這個(gè)BUG已經(jīng)在github上提給作者,https://github.com/antirez/redis/issues/2990。
7、打印出reshard計(jì)劃,如果用戶沒傳--yes,就提示用戶確認(rèn)計(jì)劃。
8、根據(jù)reshard計(jì)劃,一個(gè)個(gè)slot的遷移到新節(jié)點(diǎn)上,遷移使用move_slot方法,該方法被很多命令使用,具體可以參見下面的遷移流程。move_slot方法傳遞dots為true和pipeline數(shù)量。
9、至此,就完成了全部的遷移任務(wù)。
redis-trib.rb reshard --from all --to 80b661ecca260c89e3d8ea9b98f77edaeef43dcd --slots 11 10.180.157.199:6379
3.6、rebalance平衡集群節(jié)點(diǎn)slot數(shù)量
ruby redis-trib.rb rebalance --threshold 1 --weight b31e3a2e=5 --weight 60b8e3a1=5 --use-empty-masters --simulate 10.180.157.199:6379
host:port:這個(gè)是必傳參數(shù),用來從一個(gè)節(jié)點(diǎn)獲取整個(gè)集群信息,相當(dāng)于獲取集群信息的入口。 --weight <arg>:節(jié)點(diǎn)的權(quán)重,格式為node_id=weight,如果需要為多個(gè)節(jié)點(diǎn)分配權(quán)重的話,需要添加多個(gè)--weight <arg>參數(shù),即--weight b31e3a2e=5 --weight 60b8e3a1=5,node_id可為節(jié)點(diǎn)名稱的前綴,只要保證前綴位數(shù)能唯一區(qū)分該節(jié)點(diǎn)即可。沒有傳遞–weight的節(jié)點(diǎn)的權(quán)重默認(rèn)為1。 --auto-weights:這個(gè)參數(shù)在rebalance流程中并未用到。 --threshold <arg>:只有節(jié)點(diǎn)需要遷移的slot閾值超過threshold,才會(huì)執(zhí)行rebalance操作。具體計(jì)算方法可以參考下面的rebalance命令流程的第四步。 --use-empty-masters:rebalance是否考慮沒有節(jié)點(diǎn)的master,默認(rèn)沒有分配slot節(jié)點(diǎn)的master是不參與rebalance的,設(shè)置--use-empty-masters可以讓沒有分配slot的節(jié)點(diǎn)參與rebalance。 --timeout <arg>:設(shè)置migrate命令的超時(shí)時(shí)間。 --simulate:設(shè)置該參數(shù),可以模擬rebalance操作,提示用戶會(huì)遷移哪些slots,而不會(huì)真正執(zhí)行遷移操作。 --pipeline <arg>:與reshar的pipeline參數(shù)一樣,定義cluster getkeysinslot命令一次取出的key數(shù)量,不傳的話使用默認(rèn)值為10。 rebalance命令流程如下:1、load_cluster_info_from_node方法先加載集群信息。 2、計(jì)算每個(gè)master的權(quán)重,根據(jù)參數(shù)--weight <arg>,為每個(gè)設(shè)置的節(jié)點(diǎn)分配權(quán)重,沒有設(shè)置的節(jié)點(diǎn),則權(quán)重默認(rèn)為1。 3、根據(jù)每個(gè)master的權(quán)重,以及總的權(quán)重,計(jì)算自己期望被分配多少個(gè)slot。計(jì)算的方式為:總slot數(shù)量 * (自己的權(quán)重 / 總權(quán)重)。 4、計(jì)算每個(gè)master期望分配的slot是否超過設(shè)置的閾值,即--threshold <arg>設(shè)置的閾值或者默認(rèn)的閾值。計(jì)算的方式為:先計(jì)算期望移動(dòng)節(jié)點(diǎn)的閾值,算法為:(100-(100.0*expected/n.slots.length)).abs,如果計(jì)算出的閾值沒有超出設(shè)置閾值,則不需要為該節(jié)點(diǎn)移動(dòng)slot。只要有一個(gè)master的移動(dòng)節(jié)點(diǎn)超過閾值,就會(huì)觸發(fā)rebalance操作。 5、如果觸發(fā)了rebalance操作。那么就開始執(zhí)行rebalance操作,先將每個(gè)節(jié)點(diǎn)當(dāng)前分配的slots數(shù)量減去期望分配的slot數(shù)量獲得balance值。將每個(gè)節(jié)點(diǎn)的balance從小到大進(jìn)行排序獲得sn數(shù)組。 6、用dst_idx和src_idx游標(biāo)分別從sn數(shù)組的頭部和尾部開始遍歷。目的是為了把尾部節(jié)點(diǎn)的slot分配給頭部節(jié)點(diǎn)。sn數(shù)組保存的balance列表排序后,負(fù)數(shù)在前面,正數(shù)在后面。負(fù)數(shù)表示需要有slot遷入,所以使用dst_idx游標(biāo),正數(shù)表示需要有slot遷出,所以使用src_idx游標(biāo)。理論上sn數(shù)組各節(jié)點(diǎn)的balance值加起來應(yīng)該為0,不過由于在計(jì)算期望分配的slot的時(shí)候只是使用直接取整的方式,所以可能出現(xiàn)balance值之和不為0的情況,balance值之和不為0即為節(jié)點(diǎn)不平衡的slot數(shù)量,由于slot總數(shù)有16384個(gè),不平衡數(shù)量相對(duì)于總數(shù),基數(shù)很小,所以對(duì)rebalance流程影響不大。7、獲取sn[dst_idx]和sn[src_idx]的balance值較小的那個(gè)值,該值即為需要從sn[src_idx]節(jié)點(diǎn)遷移到sn[dst_idx]節(jié)點(diǎn)的slot數(shù)量。 8、接著通過compute_reshard_table方法計(jì)算源節(jié)點(diǎn)的slot如何分配到源節(jié)點(diǎn)列表。這個(gè)方法在reshard流程中也有調(diào)用,具體步驟可以參考reshard流程的第六步。 9、如果是simulate模式,則只是打印出遷移列表。 10、如果沒有設(shè)置simulate,則執(zhí)行move_slot操作,遷移slot,傳入的參數(shù)為:quiet=>true,:dots=>false,:update=>true。 11、遷移完成后更新sn[dst_idx]和sn[src_idx]的balance值。如果balance值為0后,游標(biāo)向前進(jìn)1。 12、直到dst_idx到達(dá)src_idx游標(biāo),完成整個(gè)rebalance操作。3.7、add-node將新節(jié)點(diǎn)加入集群
--slave:設(shè)置該參數(shù),則新節(jié)點(diǎn)以slave的角色加入集群 --master-id:這個(gè)參數(shù)需要設(shè)置了--slave才能生效,--master-id用來指定新節(jié)點(diǎn)的master節(jié)點(diǎn)。如果不設(shè)置該參數(shù),則會(huì)隨機(jī)為節(jié)點(diǎn)選擇master節(jié)點(diǎn)。 ruby redis-trib.rb add-node --slave --master-id dcb792b3e85726f012e83061bf237072dfc45f99 10.180.157.202:6379 10.180.157.199:6379add-node流程如下:
1、通過load_cluster_info_from_node方法轉(zhuǎn)載集群信息,check_cluster方法檢查集群是否健康。
2、如果設(shè)置了--slave,則需要為該節(jié)點(diǎn)尋找master節(jié)點(diǎn)。設(shè)置了--master-id,則以該節(jié)點(diǎn)作為新節(jié)點(diǎn)的master,如果沒有設(shè)置--master-id,則調(diào)用get_master_with_least_replicas方法,尋找slave數(shù)量最少的master節(jié)點(diǎn)。如果slave數(shù)量一致,則選取load_cluster_info_from_node順序發(fā)現(xiàn)的第一個(gè)節(jié)點(diǎn)。load_cluster_info_from_node順序的第一個(gè)節(jié)點(diǎn)是add-node設(shè)置的existing_host:existing_port節(jié)點(diǎn),后面的順序根據(jù)在該節(jié)點(diǎn)執(zhí)行cluster nodes返回的結(jié)果返回的節(jié)點(diǎn)順序。
3、連接新的節(jié)點(diǎn)并與集群第一個(gè)節(jié)點(diǎn)握手。
4、如果沒設(shè)置–slave就直接返回ok,設(shè)置了–slave,則需要等待確認(rèn)新節(jié)點(diǎn)加入集群,然后執(zhí)行cluster replicate命令復(fù)制master節(jié)點(diǎn)。
5、至此,完成了全部的增加節(jié)點(diǎn)的流程。
3.8、del-node從集群中刪除節(jié)點(diǎn)
del-node可以把某個(gè)節(jié)點(diǎn)從集群中刪除。del-node只能刪除沒有分配slot的節(jié)點(diǎn)。刪除命令傳遞兩個(gè)參數(shù):
host:port:從該節(jié)點(diǎn)獲取集群信息。
node_id:需要?jiǎng)h除的節(jié)點(diǎn)id。
del-node執(zhí)行結(jié)果示例如下:
redis-trib.rb del-node 10.180.157.199:6379 d5f6d1d17426bd564a6e309f32d0f5b96962fe53
3.9、set-timeout設(shè)置集群節(jié)點(diǎn)間心跳連接的超時(shí)時(shí)間
set-timeout用來設(shè)置集群節(jié)點(diǎn)間心跳連接的超時(shí)時(shí)間,單位是毫秒,不得小于100毫秒,因?yàn)?00毫秒對(duì)于心跳時(shí)間來說太短了。該命令修改是節(jié)點(diǎn)配置參數(shù)cluster-node-timeout,默認(rèn)是15000毫秒。通過該命令,可以給每個(gè)節(jié)點(diǎn)設(shè)置超時(shí)時(shí)間,設(shè)置的方式使用config set命令動(dòng)態(tài)設(shè)置,然后執(zhí)行config rewrite命令將配置持久化保存到硬盤。
ruby redis-trib.rb set-timeout 10.180.157.199:6379 30000
3.10、call在集群全部節(jié)點(diǎn)上執(zhí)行命令
call命令可以用來在集群的全部節(jié)點(diǎn)執(zhí)行相同的命令。call命令也是需要通過集群的一個(gè)節(jié)點(diǎn)地址,連上整個(gè)集群,然后在集群的每個(gè)節(jié)點(diǎn)執(zhí)行該命令。
redis-trib.rb call 10.180.157.199:6379 get key
3.11、import將外部redis數(shù)據(jù)導(dǎo)入集群
import命令可以把外部的redis節(jié)點(diǎn)數(shù)據(jù)導(dǎo)入集群。導(dǎo)入的流程如下:
1、通過load_cluster_info_from_node方法轉(zhuǎn)載集群信息,check_cluster方法檢查集群是否健康。
2、連接外部redis節(jié)點(diǎn),如果外部節(jié)點(diǎn)開啟了cluster_enabled,則提示錯(cuò)誤。
3、通過scan命令遍歷外部節(jié)點(diǎn),一次獲取1000條數(shù)據(jù)。
4、遍歷這些key,計(jì)算出key對(duì)應(yīng)的slot。
5、執(zhí)行migrate命令,源節(jié)點(diǎn)是外部節(jié)點(diǎn),目的節(jié)點(diǎn)是集群slot對(duì)應(yīng)的節(jié)點(diǎn),如果設(shè)置了--copy參數(shù),則傳遞copy參數(shù),如果設(shè)置了--replace,則傳遞replace參數(shù)。
6、不停執(zhí)行scan命令,直到遍歷完全部的key。
7、至此完成整個(gè)遷移流程
這中間如果出現(xiàn)異常,程序就會(huì)停止。沒使用--copy模式,則可以重新執(zhí)行import命令,使用--copy的話,最好清空新的集群再導(dǎo)入一次。
import命令更適合離線的把外部redis數(shù)據(jù)導(dǎo)入,在線導(dǎo)入的話最好使用更專業(yè)的導(dǎo)入工具,以slave的方式連接redis節(jié)點(diǎn)去同步節(jié)點(diǎn)數(shù)據(jù)應(yīng)該是更好的方式。
./redis-trib.rb import --from 10.0.10.1:6379 10.10.10.1:7000 //把 10.0.10.1:6379(redis 2.8)上的數(shù)據(jù)導(dǎo)入到 10.10.10.1:7000這個(gè)節(jié)點(diǎn)所在的集群四、redis分布式鎖
Redis為單進(jìn)程單線程模式、采用隊(duì)列模式將并發(fā)訪問變成串行訪問。且多client對(duì)redis的鏈接并不存在競(jìng)爭(zhēng)關(guān)系。其次Redis提供SETNX,GETSET方便實(shí)現(xiàn)分布式鎖
條件:
系統(tǒng)是一個(gè)分布式系統(tǒng)(關(guān)鍵是分布式,單機(jī)的可以使用ReentrantLock或者synchronized代碼塊來實(shí)現(xiàn))
共享資源(各個(gè)系統(tǒng)訪問同一個(gè)資源,資源的載體可能是傳統(tǒng)關(guān)系型數(shù)據(jù)庫(kù)或者NoSQL)
同步訪問(即有很多個(gè)進(jìn)程同事訪問同一個(gè)共享資源。沒有同步訪問,誰管你資源競(jìng)爭(zhēng)不競(jìng)爭(zhēng))
分布式鎖多種方式實(shí)現(xiàn),比如zookeeper、redis...。不管哪種方式,基本原理是不變的:用一個(gè)狀態(tài)值表示鎖,對(duì)鎖的占用和釋放通過狀態(tài)值來標(biāo)識(shí)。
1、setNX實(shí)現(xiàn)分布式鎖
Redis為單進(jìn)程單線程模式,采用隊(duì)列模式將并發(fā)訪問變成串行訪問,且多客戶端對(duì)Redis的連接并不存在競(jìng)爭(zhēng)關(guān)系。redis的SETNX命令可以方便的實(shí)現(xiàn)分布式鎖。
SETNX key value //若給定的 key 已經(jīng)存在,則 SETNX 不做任何動(dòng)作。
設(shè)置成功,返回 1 。 //exists key1 測(cè)試key1是否存在,為0表示不存在,1表示存在
設(shè)置失敗,返回 0 。
SETNX lock.foo <current Unix time + lock timeout + 1>
返回1:則該客戶端獲得鎖,把lock.foo的鍵值設(shè)置為時(shí)間值表示該鍵已被鎖定,該客戶端最后可以通過DEL lock.foo來釋放該鎖。
返回0:表明該鎖已被其他客戶端取得,這時(shí)我們可以先返回或進(jìn)行重試等對(duì)方完成或等待鎖超時(shí)。
GETSET key value
將給定 key 的值設(shè)為 value,并返回 key 的舊值(old value)。
當(dāng) key 存在但不是字符串類型時(shí),返回一個(gè)錯(cuò)誤 //當(dāng) key 沒有舊值時(shí),也即是,key 不存在時(shí),返回 nil 。
127.0.0.1:1209> GETSET job "chanpinjingli" //返回舊指
"yunweigongchengshi"
127.0.0.1:1209> get job //已經(jīng)修改
"chanpinjingli"
2、解決死鎖
刪除鎖的操作應(yīng)該是鎖擁有這執(zhí)行的,只需要等它超時(shí)即可
當(dāng)多個(gè)客戶端檢測(cè)到鎖超時(shí)后都會(huì)嘗試去釋放它,這里就可能出現(xiàn)一個(gè)競(jìng)態(tài)條件,讓我們模擬一下這個(gè)場(chǎng)景:
C0操作超時(shí)了,但它還持有著鎖,C1和C2讀取lock.foo檢查時(shí)間戳,先后發(fā)現(xiàn)超時(shí)了。
C1 發(fā)送DEL lock.foo
C1 發(fā)送SETNX lock.foo 并且成功了。
C2 發(fā)送DEL lock.foo
C2 發(fā)送SETNX lock.foo 并且成功了。
這樣一來,C1,C2都拿到了鎖!問題大了!
解決方法:
C3發(fā)送SETNX lock.foo 想要獲得鎖,由于C0還持有鎖,所以Redis返回給C3一個(gè)0
C3發(fā)送GET lock.foo 以檢查鎖是否超時(shí)了,如果沒超時(shí),則等待或重試。反之,如果已超時(shí),C3通過下面的操作來嘗試獲得鎖:
GETSET lock.foo <current Unix time + lock timeout + 1>
通過GETSET,C3拿到的時(shí)間戳如果仍然是超時(shí)的,那就說明,C3如愿以償拿到鎖了。
如果在C3之前,有個(gè)叫C4的客戶端比C3快一步執(zhí)行了上面的操作,那么C3拿到的時(shí)間戳是個(gè)未超時(shí)的值,這時(shí),C3沒有如期獲得鎖,需要再次等待或重試。留意一下,盡管C3沒拿到鎖,但它改寫了C4設(shè)置的鎖的超時(shí)值,不過這一點(diǎn)非常微小的誤差帶來的影響可以忽略不計(jì)。
為了讓分布式鎖的算法更穩(wěn)鍵些,持有鎖的客戶端在解鎖之前應(yīng)該再檢查一次自己的鎖是否已經(jīng)超時(shí),再去做DEL操作,因?yàn)榭赡芸蛻舳艘驗(yàn)槟硞€(gè)耗時(shí)的操作而掛起,操作完的時(shí)候鎖因?yàn)槌瑫r(shí)已經(jīng)被別人獲得,這時(shí)就不必解鎖了
五、其他
1、Redis漏洞問題,參考
最近針對(duì)Redis的弱口令進(jìn)行***的案例較多。處理方法大同小異,對(duì)比參考:
http://blog.jobbole.com/94518/ https://blog.csdn.net/kevin_pso/article/details/54844980 https://blog.51cto.com/simeon/21151842、redis集群相關(guān)配置指令
#cat conf/sentinel.conf sentinel monitor redis_cluster 127.0.0.1 16000 1 sentinel failover-timeout redis_cluster 15000 sentinel config-epoch redis_cluster 0 daemonize yes dir "/tmp/mongo/redis" logfile "/tmp/redis/redis-sentinel.log" port 16006解釋:
sentinel monitor mymaster 127.0.0.1 6379 2 //監(jiān)控的master的名字為mymaster。當(dāng)集群中有2個(gè)sentinel認(rèn)為master死了時(shí),才能真正認(rèn)為該master已經(jīng)不可用了 sentinel down-after-milliseconds mymaster 60000 //如果master在“一定時(shí)間范圍”內(nèi)不回應(yīng)PONG 或者是回復(fù)了一個(gè)錯(cuò)誤消息,那么這個(gè)sentinel會(huì)主觀地(單方面地)認(rèn)為這個(gè)master已經(jīng)不可用了(subjectively down, 也簡(jiǎn)稱為SDOWN)。客觀down:ODOWN sentinel failover-timeout mymaster 180000 // sentinel parallel-syncs mymaster 1 //在發(fā)生failover主備切換時(shí),這個(gè)選項(xiàng)指定了最多可以有多少個(gè)slave同時(shí)對(duì)新的master進(jìn)行同步,這個(gè)數(shù)字越小,完成failover所需的時(shí)間就越長(zhǎng),但是如果這個(gè)數(shù)字越大,就意味著越多的slave因?yàn)閞eplication而不可用。可以通過將這個(gè)值設(shè)為 1 來保證每次只有一個(gè)slave處于不能處理命令請(qǐng)求的狀態(tài)。//這些指令都可以通過SENTINEL SET command動(dòng)態(tài)修改。
參考博客:
https://www.oschina.net/question/tag/twemproxy //twemproxy開源中國(guó)社區(qū)
https://www.cnblogs.com/lihaoyang/p/6906444.html //gem安裝redis官方集群版
https://redis.io/commands/cluster-nodes //官方redis集群相關(guān)命令
https://blog.csdn.net/huwei2003/article/details/50973967 //redis-trib.rb詳解
https://piaosanlang.gitbooks.io/redis/content/redisfen-bu-shi-suo-shi-xian.html
https://www.cnblogs.com/haoxinyue/p/redis.html //twitter版redis集群
https://blog.csdn.net/shmiluwei/article/details/51958359 //Codis安裝和配置
轉(zhuǎn)載于:https://blog.51cto.com/hmtk520/2115789
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的Redis详解(三)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: iBatis——执行原理
- 下一篇: linux cmake编译源码,linu