Redis集群-官方推荐方案RedisCluster

Redis集群-官方推荐方案RedisCluster

前情提要

  • 理解RedisCluster的原理和容错机制
  • 能够配置RedisCluster并使用

redis使用中遇到的瓶颈

我们日常工作中使用Redis,经常会遇到一些问题:

1、高可用问题,如何保证redis的持续高可用性。  
2、容量问题,单实例redis内存无法无限扩充,达到32G后就进入了64位世界,性能下降。  
3、并发性能问题,redis号称单实例10万并发,但也是有尽头的。

RedisCluster的原理和容错机制

redis的集群策略

redis3.0以后推出的redis cluster 集群方案,redis cluster集群保证了高可用、高性能、高可扩展性。

主要有下面三种集群策略,分别为:

  1. 推特:twemproxy : 代理式
  2. 豌豆荚:codis :代理式
  3. 官方:redis cluster : 非代理

我们主要来学习官方推出的 redis cluster,这也是生产项目中用的最多的。

redis-cluster的优势

1、官方推荐,毋庸置疑。  
2、去中心化,集群最大可增加1000个节点,性能随节点增加而线性扩展。  
3、管理方便,后续可自行增加或摘除节点,移动分槽等等。  
4、简单,易上手。

redis-cluster名词介绍

1、master  主节点、  
2、slave   从节点  
3、slot    槽,一共有16384数据分槽,分布在集群的所有主节点中。

redis cluster 架构图

架构细节:

  1. 图中描述的是六个redis实例构成的集群,6379端口为客户端通讯端口,16379端口为集群总线端口

  2. 集群内部划分为16384个数据分槽,分布在三个主redis中。

  3. 从redis中没有分槽,不会参与集群投票,也不会帮忙加快读取数据,仅仅作为主机的备份。

  4. 三个主节点中平均分布着16384数据分槽的三分之一,每个节点中不会存有有重复数据,仅仅有自己的从机帮忙冗余。

  5. 所有的redis主节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽。

  6. 客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。

  7. 节点的fail是通过集群中超过半数的节点检测失效时才生效。

操作原理演示:

Redis 集群中内置了 16384个哈希槽,当需要在 Redis 集群中放置一个 key-value 时,redis 先对 key使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点。

redis cluster 投票:容错

  1. 节点失效判断: 所有master参与投票,如果半数以上master节点与其中一个master节点通信超过(cluster-node-timeout),认为该master节点挂掉.

  2. 挂掉主节点的从节点自动升级为主节点,redis集群操作.

集群失效判断: 什么时候整个集群不可用(cluster_state:fail)?

  1. 如果集群任意master挂掉,且当前master没有slave,则集群进入fail状态。也可以理解成集群的[0-16383]slot映射不完全时进入fail状态。
  2. 如果集群超过半数以上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的工具包,就不多做赘述了。

  1. 创建一个Maven项目,这个步骤就不列了,如果有不知道的小伙伴,可以文末关注我,加我好友联系交流哈。
  2. 添加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哨兵机制-你不得不了解的知识点

如果在操作过程中有遇到问题欢迎扫码关注加我好友,一起沟通学习,有问必答!

猜你喜欢

转载自blog.csdn.net/taurus_7c/article/details/104450281