Redis集羣-官方推薦方案RedisCluster
前情提要
- 理解RedisCluster的原理和容錯機制
- 能夠配置RedisCluster並使用
redis使用中遇到的瓶頸
我們日常工作中使用Redis,經常會遇到一些問題:
1、高可用問題,如何保證redis的持續高可用性。
2、容量問題,單實例redis內存無法無限擴充,達到32G後就進入了64位世界,性能下降。
3、併發性能問題,redis號稱單實例10萬併發,但也是有盡頭的。
RedisCluster的原理和容錯機制
redis的集羣策略
redis3.0以後推出的redis cluster 集羣方案,redis cluster集羣保證了高可用、高性能、高可擴展性。
主要有下面三種集羣策略,分別爲:
- 推特:twemproxy : 代理式
- 豌豆莢:codis :代理式
- 官方:redis cluster : 非代理
我們主要來學習官方推出的 redis cluster,這也是生產項目中用的最多的。
redis-cluster的優勢
1、官方推薦,毋庸置疑。
2、去中心化,集羣最大可增加1000個節點,性能隨節點增加而線性擴展。
3、管理方便,後續可自行增加或摘除節點,移動分槽等等。
4、簡單,易上手。
redis-cluster名詞介紹
1、master 主節點、
2、slave 從節點
3、slot 槽,一共有16384數據分槽,分佈在集羣的所有主節點中。
redis cluster 架構圖
架構細節:
-
圖中描述的是六個redis實例構成的集羣,6379端口爲客戶端通訊端口,16379端口爲集羣總線端口
-
集羣內部劃分爲16384個數據分槽,分佈在三個主redis中。
-
從redis中沒有分槽,不會參與集羣投票,也不會幫忙加快讀取數據,僅僅作爲主機的備份。
-
三個主節點中平均分佈着16384數據分槽的三分之一,每個節點中不會存有有重複數據,僅僅有自己的從機幫忙冗餘。
-
所有的redis主節點彼此互聯(PING-PONG機制),內部使用二進制協議優化傳輸速度和帶寬。
-
客戶端與redis節點直連,不需要中間proxy層.客戶端不需要連接集羣所有節點,連接集羣中任何一個可用節點即可。
-
節點的fail是通過集羣中超過半數的節點檢測失效時才生效。
操作原理演示:
Redis 集羣中內置了 16384個哈希槽,當需要在 Redis 集羣中放置一個 key-value 時,redis 先對 key使用 crc16 算法算出一個結果,然後把結果對 16384 求餘數,這樣每個 key 都會對應一個編號在 0-16383 之間的哈希槽,redis 會根據節點數量大致均等的將哈希槽映射到不同的節點。
redis cluster 投票:容錯
-
節點失效判斷: 所有master參與投票,如果半數以上master節點與其中一個master節點通信超過(cluster-node-timeout),認爲該master節點掛掉.
-
掛掉主節點的從節點自動升級爲主節點,redis集羣操作.
集羣失效判斷: 什麼時候整個集羣不可用(cluster_state:fail)?
- 如果集羣任意master掛掉,且當前master沒有slave,則集羣進入fail狀態。也可以理解成集羣的[0-16383]slot映射不完全時進入fail狀態。
- 如果集羣超過半數以上master掛掉,無論是否有slave,集羣進入fail狀態。 (投票無效)
集羣部署
redis 集羣最少需要三臺服務器,這裏我們採用3主3從來配置 redis cluster。端口可以自定定義,我設爲 6310、6320、6330、6340、6350、6360
- 第一步,複製安裝redis bin目錄的 redis.conf 爲 6份。eg:redis1.conf …
- 第二步,修改這6份配置文件,如下所示,記着6份配置文件都需要修改哈;
#redis.conf默認配置
daemonize yes
pidfile /var/run/redis/redis.pid #多實例情況下需修改,例如redis_6380.pid
port 6379 #多實例情況下需要修改,例如6380
tcp-backlog 511
bind 0.0.0.0
timeout 0
tcp-keepalive 0
loglevel notice
logfile “” #多實例情況下需要修改,例如6380.log
databases 16
save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb #多實例情況下需要修改,例如dump.6380.rdb
slave-serve-stale-data yes
slave-read-only yes
repl-diskless-sync no
repl-diskless-sync-delay 5
repl-disable-tcp-nodelay no
slave-priority 100
appendonly yes
appendfilename "appendonly.aof" #多實例情況下需要修改,例如 appendonly_6380.aof
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-load-truncated yes
lua-time-limit 5000
slowlog-log-slower-than 10000
slowlog-max-len 128
latency-monitor-threshold 0
notify-keyspace-events ""
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-entries 512
list-max-ziplist-value 64
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
hll-sparse-max-bytes 3000
activerehashing yes
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
hz 10
#################自定義配置
#系統配置
#vim /etc/sysctl.conf
#vm.overcommit_memory = 1
aof-rewrite-incremental-fsync yes
maxmemory 4096mb
maxmemory-policy allkeys-lru
dir /opt/redis/data #多實例情況下需要修改,例如/data/6380
#集羣配置
cluster-enabled yes #打開集羣配置
cluster-config-file /opt/redis/6380/nodes.conf #多實例情況下需要修改,例如/6380/
cluster-node-timeout 5000
#從ping主間隔默認10秒
#複製超時時間
#repl-timeout 60
#遠距離主從
#config set client-output-buffer-limit "slave 536870912 536870912 0"
#config set repl-backlog-size 209715200
- 第三步,啓動6個實例,
./redis-server redis.conf
注意,redis.conf應爲6個不同的修改過的多實例配置文件; - 第四步,創建redis集羣;
[root@localhost redis-cluster]# ./redis-cli --cluster create 192.168.137.6:6310 192.168.137.6:6320 192.168.137.6:6330 192.168.137.6:6340 192.168.137.6:6350 192.168.137.6:6360 --cluster-replicas 1
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 192.168.137.6:6350 to 192.168.137.6:6310
Adding replica 192.168.137.6:6360 to 192.168.137.6:6320
Adding replica 192.168.137.6:6340 to 192.168.137.6:6330
>>> Trying to optimize slaves allocation for anti-affinity
[WARNING] Some slaves are in the same host as their master
M: 6de62fad996e29fec56265d6259e0221e07d83f2 192.168.137.6:6310
slots:[0-5460] (5461 slots) master
M: 6b3a0a5826bf5350953a6f1a75954f5d9276b821 192.168.137.6:6320
slots:[5461-10922] (5462 slots) master
M: 305613cdddafbb2fbbe02bda877a9337b88bc8b7 192.168.137.6:6330
slots:[10923-16383] (5461 slots) master
S: 35d42e9e33baaef1f295cf36f88fd49766fa549b 192.168.137.6:6340
replicates 6de62fad996e29fec56265d6259e0221e07d83f2
S: 66864b86e16a139640b52aeb1f8a95297a236edd 192.168.137.6:6350
replicates 6b3a0a5826bf5350953a6f1a75954f5d9276b821
S: a85a97502509a7a5d53266bb9720cb176ec5f957 192.168.137.6:6360
replicates 305613cdddafbb2fbbe02bda877a9337b88bc8b7
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 192.168.137.6:6310)
M: 6de62fad996e29fec56265d6259e0221e07d83f2 192.168.137.6:6310
slots:[0-5460] (5461 slots) master
1 additional replica(s)
M: 305613cdddafbb2fbbe02bda877a9337b88bc8b7 192.168.137.6:6330
slots:[10923-16383] (5461 slots) master
1 additional replica(s)
M: 6b3a0a5826bf5350953a6f1a75954f5d9276b821 192.168.137.6:6320
slots:[5461-10922] (5462 slots) master
1 additional replica(s)
S: a85a97502509a7a5d53266bb9720cb176ec5f957 192.168.137.6:6360
slots: (0 slots) slave
replicates 305613cdddafbb2fbbe02bda877a9337b88bc8b7
S: 35d42e9e33baaef1f295cf36f88fd49766fa549b 192.168.137.6:6340
slots: (0 slots) slave
replicates 6de62fad996e29fec56265d6259e0221e07d83f2
S: 66864b86e16a139640b52aeb1f8a95297a236edd 192.168.137.6:6350
slots: (0 slots) slave
replicates 6b3a0a5826bf5350953a6f1a75954f5d9276b821
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
可以看到,16384個槽已經平均分配給了3臺master實例。
命令行客戶端連接集羣
./redis-cli -p 6310 -c
注意:-c 表示是以redis集羣方式進行連接
127.0.0.1:6310> set key1 11
-> Redirected to slot [9189] located at 192.168.137.10:6320
OK
192.168.137.10:6320>
- 查看集羣狀態
127.0.0.1:6310> cluster info
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:6
cluster_my_epoch:1
cluster_stats_messages_ping_sent:777
cluster_stats_messages_pong_sent:772
cluster_stats_messages_sent:1549
cluster_stats_messages_ping_received:767
cluster_stats_messages_pong_received:777
cluster_stats_messages_meet_received:5
cluster_stats_messages_received:1549
- 查看集羣中的節點
127.0.0.1:6310> cluster nodes
7bd6149e1350b1494e73d3e0d3623c8a5d0f6578 192.168.137.10:6340@16340 slave 8ab958bf84a6d9d30fe79ff379c9afade2587416 0 1582208322552 4 connected
550dd0cd4939a1a14cbabc7c7cf4c2cb6ce48d47 192.168.137.10:6350@16350 slave ce1707cae8882f129bf00f8b4282be9b8b875adc 0 1582208323555 5 connected
85709be5115d23f9c449a382de119cf189851fd1 192.168.137.10:6330@16330 master - 0 1582208322000 3 connected 10923-16383
ce1707cae8882f129bf00f8b4282be9b8b875adc 192.168.137.10:6320@16320 master - 0 1582208323555 2 connected 5461-10922
8ab958bf84a6d9d30fe79ff379c9afade2587416 192.168.137.10:6310@16310 myself,master - 0 1582208322000 1 connected 0-5460
e31d92d97d96bcbe43c0a1f8c33ab66540db974a 192.168.137.10:6360@16360 slave 85709be5115d23f9c449a382de119cf189851fd1 0 1582208322953 6 connected
維護節點
添加主節點
- 先創建6370節點
- 添加新創建的節點到集羣
[root@localhost redis-cluster]# ./redis-cli --cluster add-node 192.168.137.10:6370 192.168.137.10:6320
>>> Adding node 192.168.137.10:6370 to cluster 192.168.137.10:6320
>>> Performing Cluster Check (using node 192.168.137.10:6320)
M: ce1707cae8882f129bf00f8b4282be9b8b875adc 192.168.137.10:6320
slots:[5461-10922] (5462 slots) master
1 additional replica(s)
S: e31d92d97d96bcbe43c0a1f8c33ab66540db974a 192.168.137.10:6360
slots: (0 slots) slave
replicates 85709be5115d23f9c449a382de119cf189851fd1
M: 85709be5115d23f9c449a382de119cf189851fd1 192.168.137.10:6330
slots:[10923-16383] (5461 slots) master
1 additional replica(s)
M: 8ab958bf84a6d9d30fe79ff379c9afade2587416 192.168.137.10:6310
slots:[0-5460] (5461 slots) master
1 additional replica(s)
S: 550dd0cd4939a1a14cbabc7c7cf4c2cb6ce48d47 192.168.137.10:6350
slots: (0 slots) slave
replicates ce1707cae8882f129bf00f8b4282be9b8b875adc
S: 7bd6149e1350b1494e73d3e0d3623c8a5d0f6578 192.168.137.10:6340
slots: (0 slots) slave
replicates 8ab958bf84a6d9d30fe79ff379c9afade2587416
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
>>> Send CLUSTER MEET to node 192.168.137.10:6370 to make it join the cluster.
[OK] New node added correctly.
新節點必須是空的,不能包含任何數據。請把之前aof和dump文件刪掉,並且若有nodes.conf也需要刪除。add-node 將一個節點添加到集羣裏面, 第一個是新節點ip:port, 第二個是任意一個已存在節點ip:port,node:新節點沒有包含任何數據, 因爲它沒有包含任何slot。
- 查看集羣節點發現新增的6370節點
127.0.0.1:6310> cluster nodes
e305402550696f152b76d6d77fca3b8ede162033 192.168.137.10:6370@16370 master - 0 1582209128696 0 connected
7bd6149e1350b1494e73d3e0d3623c8a5d0f6578 192.168.137.10:6340@16340 slave 8ab958bf84a6d9d30fe79ff379c9afade2587416 0 1582209129198 4 connected
550dd0cd4939a1a14cbabc7c7cf4c2cb6ce48d47 192.168.137.10:6350@16350 slave ce1707cae8882f129bf00f8b4282be9b8b875adc 0 1582209130601 5 connected
85709be5115d23f9c449a382de119cf189851fd1 192.168.137.10:6330@16330 master - 0 1582209129598 3 connected 10923-16383
ce1707cae8882f129bf00f8b4282be9b8b875adc 192.168.137.10:6320@16320 master - 0 1582209130201 2 connected 5461-10922
8ab958bf84a6d9d30fe79ff379c9afade2587416 192.168.137.10:6310@16310 myself,master - 0 1582209129000 1 connected 0-5460
e31d92d97d96bcbe43c0a1f8c33ab66540db974a 192.168.137.10:6360@16360 slave 85709be5115d23f9c449a382de119cf189851fd1 0 1582209129699 6 connected
Hash槽重新分配
添加完主節點需要對主節點進行hash槽分配,這樣該主節纔可以存儲數據。
通過 cluster nodes
查看集羣中槽的佔用情況:
給剛添加的6370結點分配槽
- 第一步:連接上集羣(連接集羣中任意一個可用結點都行),執行下面的命令
./redis-cli -p 6320 --cluster reshard 192.168.137.10:6310
- 第二步:輸入要分配的槽數量
輸入:3000,表示要給目標節點分配3000個槽
- 第三步:輸入接收槽的結點id
輸入:e305402550696f152b76d6d77fca3b8ede162033
PS:這裏準備給6370分配槽,通過cluster nodes查看7007結點id爲:e305402550696f152b76d6d77fca3b8ede162033
- 第四步:輸入源結點id
輸入all。
ps: 這裏輸入源節點id,即從此節點取出槽,分配後此節點中將不再有這些槽。輸入all 從所有源節點中拿,輸入done取消分配。
- 第五步:輸入yes開始移動槽到目標結點id
輸入:yes
添加從節點
添加 6380 從結點,將 6380 作爲 6370 的從結點
命令:
./redis-cli --cluster add-node 新節點的ip : 端口 舊節點ip : 端口 --cluster-slave --
cluster-master-id 主節點id
例如:
./redis-cli --cluster add-node 192.168.137.10:6380 192.168.137.10:6370 --cluster-slave --cluster-master-id e305402550696f152b76d6d77fca3b8ede162033
e305402550696f152b76d6d77fca3b8ede162033 是6370節點的id,可以通過 cluster nodes
命令查看.
上圖所示,即已經配置從節點成功了。
注意:如果原來該結點在集羣中的配置信息已經生成到 cluster-config-file 指定的配置文件中(如果cluster-config-file 沒有指定則默認爲nodes.conf),這時可能會報錯:
[ERR] Node XXXXXX is not empty. Either the node already knows other nodes (check
with CLUSTER NODES) or contains some key in database 0
解決方法是刪除生成的配置文件nodes.conf,刪除後再執行 ./redis-cli add-node
指令.
查看集羣中的結點,剛添加的 6380 爲 6370 的從節點:
刪除節點
命令:
./redis-cli --cluster del-node 192.168.137.10:6380 89a4504a36c37bc2f898a977aee1182d40101d49
執行完上面的刪除節點命令後,我們可以再次查看集羣節點狀況:
可以發現6380這臺從節點實例已經被刪除掉了。
看到這裏,認真思考的同學可能會想上面我們是直接刪除了從節點實例,如果要刪除主節點呢,那麼動手去嘗試下吧!
如你所願,確實出錯啦,因爲 6370 是一臺主節點,而且已經被分配了 slot (槽),即會有數據分配到這臺機器上,所以是不允許刪除有分配槽的節點的。 如果你實在想要下點集羣中的實例,那麼需要將該節點佔用的 hash 槽分配出去,參考 上面我們學習到的 Hash槽重新分配的 命令。
Jedis連接集羣
如果你跟着我從上面已經完成了redis集羣的搭建,那麼接下來是不是就應該想下我們應用中應該如何使用啦?
由於我是搞Java的,這裏就寫下如果使用Jedis連接RedisCluster的代碼,當然主流語言都是有連接Redis的工具包,就不多做贅述了。
- 創建一個Maven項目,這個步驟就不列了,如果有不知道的小夥伴,可以文末關注我,加我好友聯繫交流哈。
- 添加Jedis客戶端jar包,測試如下代碼;
# pom文件添加依賴
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.1.0</version>
</dependency>
# 測試代碼
@Test
public void testJedisCluster() throws Exception {
//創建一連接,JedisCluster對象,在系統中是單例存在
Set<HostAndPort> nodes = new HashSet<>();
nodes.add(new HostAndPort("192.168.137.10", 6310));
nodes.add(new HostAndPort("192.168.137.10", 6320));
nodes.add(new HostAndPort("192.168.137.10", 6330));
nodes.add(new HostAndPort("192.168.137.10", 6340));
nodes.add(new HostAndPort("192.168.137.10", 6450));
nodes.add(new HostAndPort("192.168.137.10", 6360));
JedisCluster cluster = new JedisCluster(nodes);
//執行JedisCluster對象中的方法,方法和redis一一對應。
cluster.set("cluster-test", "my jedis cluster test");
String result = cluster.get("cluster-test");
System.out.println(result);
//程序結束時需要關閉JedisCluster對象
cluster.close();
}
注意:執行上面代碼有可能會報網絡不可達,只要關閉redis服務器的防火牆就好了;
systemctl stop firewalld.service # 我的系統是centos7
使用Spring
- 添加xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 連接池配置 -->
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<!-- 最大連接數 -->
<property name="maxTotal" value="30"/>
<!-- 最大空閒連接數 -->
<property name="maxIdle" value="10"/>
<!-- 每次釋放連接的最大數目 -->
<property name="numTestsPerEvictionRun" value="1024"/>
<!-- 釋放連接的掃描間隔(毫秒) -->
<property name="timeBetweenEvictionRunsMillis" value="30000"/>
<!-- 連接最小空閒時間 -->
<property name="minEvictableIdleTimeMillis" value="1800000"/>
<!-- 連接空閒多久後釋放, 當空閒時間>該值 且 空閒連接>最大空閒連接數 時直接釋放 -->
<property name="softMinEvictableIdleTimeMillis" value="10000"/>
<!-- 獲取連接時的最大等待毫秒數,小於零:阻塞不確定的時間,默認-1 -->
<property name="maxWaitMillis" value="1500"/>
<!-- 在獲取連接的時候檢查有效性, 默認false -->
<property name="testOnBorrow" value="true"/>
<!-- 在空閒時檢查有效性, 默認false -->
<property name="testWhileIdle" value="true"/>
<!-- 連接耗盡時是否阻塞, false報異常,ture阻塞直到超時, 默認true -->
<property name="blockWhenExhausted" value="false"/>
</bean>
<!-- redis集羣 -->
<bean id="jedisCluster" class="redis.clients.jedis.JedisCluster">
<constructor-arg index="0">
<set>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg index="0" value="192.168.137.10"></constructor-arg>
<constructor-arg index="1" value="6310"></constructor-arg>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg index="0" value="192.168.137.10"></constructor-arg>
<constructor-arg index="1" value="6320"></constructor-arg>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg index="0" value="192.168.137.10"></constructor-arg>
<constructor-arg index="1" value="6330"></constructor-arg>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg index="0" value="192.168.137.10"></constructor-arg>
<constructor-arg index="1" value="6340"></constructor-arg>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg index="0" value="192.168.137.10"></constructor-arg>
<constructor-arg index="1" value="6350"></constructor-arg>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg index="0" value="192.168.137.10"></constructor-arg>
<constructor-arg index="1" value="6360"></constructor-arg>
</bean>
</set>
</constructor-arg>
<constructor-arg index="1" ref="jedisPoolConfig"></constructor-arg>
</bean>
</beans>
- 添加 spring 的jar包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
- 測試代碼
public class RedisClusterSpring {
private ApplicationContext applicationContext;
@Before
public void init() {
applicationContext = new ClassPathXmlApplicationContext("classpath:application.xml");
}
@Test
public void testJedisCluster() {
JedisCluster jedisCluster = (JedisCluster) applicationContext.getBean("jedisCluster");
jedisCluster.set("name","羅小黑");
String value = jedisCluster.get("name");
System.out.println(value);
}
}
好了,今天主要學了Redis官方推出的集羣搭建方式 Redis-Cluster,你學會了? 如果對Redis主從模式、哨兵模式不太瞭解的同學建議閱讀筆者的這兩篇文章,學習Redis這幾個知識點是必會的,面試中也經常問Redis主從、哨兵、集羣模式的區別和共同點之類的。
Redis進階你不得不瞭解的知識點-主從複製原理
Redis哨兵機制-你不得不瞭解的知識點
如果在操作過程中有遇到問題歡迎掃碼關注加我好友,一起溝通學習,有問必答!