Redis集群架构搭建和原理

Redis集群架构教程

Redis常见的架构有主从、哨兵、高可用集群,接下来的文章分四章分别介绍linux安装redis、主从架构搭建、哨兵模式搭建、集群架构搭建

第一章 Redis的安装

我的cenos版本为7,redis版本为5.0.9 Redis下载地址

1.第一步下载一个redis安装包到本地

2.下 usr/local 目录下创建一个redis目录

[root@localhost local]# mkdir redis

3.通过远程连接工具把安装包放在redis目录中

Redis集群架构搭建和原理_第1张图片

4.解压

[root@localhost redis]# tar -zxvf redis-5.0.9.tar.gz

5.安装redis运行环境

[root@localhost redis]# yum install gcc-c++

6.编译

执行一次make命令

[root@localhost redis-5.0.9]# make

7.安装

进入到src目录下

image-20220502134013638

#执行make install命令进行安装
[root@localhost src]# make install

8.配置

可以在redis目录下创建两个文件夹bin和etc etc存放我们的配置文件 bin目录存放我们的启动的.sh文件等

[root@localhost redis]# mkdir bin data

9.移动相关文件

[root@localhost redis-5.0.9]# mv redis.conf /usr/local/redis/etc/
[root@localhost src]# mv mkreleasehdr.sh redis-benchmark redis-check-aof redis-check-rdb redis-cli redis-sentinel  redis-server redis-trib.rb /usr/local/redis/bin/

10.修改配置文件

[root@localhost etc]# vim redis.conf

(1)bind 注释

image-20220502134656837

(2)关闭保护模式

Redis集群架构搭建和原理_第2张图片

(3)以后台进程挂起模式开启 no改为yes

Redis集群架构搭建和原理_第3张图片

(4)也可以为redis设置一个密码

Redis集群架构搭建和原理_第4张图片

11.启动redis

[root@localhost bin]# redis-server /usr/local/redis/etc/redis.conf

至此我们的redis就启动成功了

image-20220502135051734

通过命令查看redis进行

[root@localhost bin]# ps -ef|grep redis

image-20220502135138558

第二章 主从架构

2.1 环境搭建

1.复制一份主节点的配置文件

(6380我之前搭建过了,这边以6381为例) (为了启动方便我把之前的redis.conf放在了bin目录下)

[root@localhost bin]# cp redis.conf redis-6381.conf

2.修改redis-6181.conf

port 6381  #把端口改为6381
pidfile /var/run/redis_6381.pid #进程文件改为自己的
logfile "6381.log"  #日志文件改为自己的
# bind 192.168.1.100 10.0.0.1
# bind 127.0.0.1  这里最好的bind注释掉  否则只能指定的主机访问

dir /usr/local/redis/data/6381 #配置从节点文件的存放位置 需要先把6381目录建好

replicaof 192.168.26.129 6379  #配置主机节点  ip加端口
masterauth qianyue #如果主节点配置了密码 需要添加主节点的密码
replica-read-only yes #配置只读属性

3.创建6381目录存放6381节点的数据文件

[root@localhost data]# mkdir 6381

4.启动主节点

这里只是为了方便启动把配置文件放在了bin目录下,生产可以新建etc目录,把节点的配置放在这个目录下方便管理

[root@localhost bin]# redis-server redis.conf
29240:C 01 May 2022 09:13:44.964 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
29240:C 01 May 2022 09:13:44.964 # Redis version=5.0.9, bits=64, commit=00000000, modified=0, pid=29240, just started
29240:C 01 May 2022 09:13:44.964 # Configuration loaded

5.启动从节点6380、6381

[root@localhost bin]# redis-server redis-6380.conf
[root@localhost bin]# redis-server redis-6381.conf

6.登录6381客户端

[root@localhost bin]# redis-cli -p 6381
127.0.0.1:6381> ping
(error) NOAUTH Authentication required.
127.0.0.1:6381> auth qianyue
OK
127.0.0.1:	6381> info  #看到role  slave 就说明配置已经成功
# Replication
role:slave
master_host:192.168.26.129
master_port:6379
master_link_status:up
master_last_io_seconds_ago:8
master_sync_in_progress:0
slave_repl_offset:70
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:0ec63e74a87f13b65d1868dc6e1fef68195aa540
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:70
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:70

# CPU
used_cpu_sys:0.076340
used_cpu_user:0.002936
used_cpu_sys_children:0.000974
used_cpu_user_children:0.000000

# Cluster
cluster_enabled:0
# Keyspace
db0:keys=15,expires=0,avg_ttl=0

2.2 测试

1.尝试在从节点写入数据

127.0.0.1:6381> set user:id 1000
(error) READONLY You can't write against a read only replica.
#不允许写

2.主节点写入从节点读取

主节点写入

127.0.0.1:6379> set user:id 1001
OK

从节点读入

127.0.0.1:6381> get user:id
"1001"

2.3 原理

如果你为master配置了一个slave,不管这个slave是否是第一次连接上Master,它都会发送一个PSYNC 命令给master请求复制数据。master收到PSYNC命令后,会在后台进行数据持久化通过bgsave生成最新的rdb快照文件,持久化期间,master会继续接收客户端的请求,它会把这些可能修改数据集的请求(命令)缓存在内存中.当持久化进行完毕以后,master会把这份rdb文件数据集发送给slave,slave会把接收到的数据进行持久化生成rdb,然后 再加载到内存中。然后,master再将之前缓存在内存中的命令发送给slave.当master与slave之间的连接由于某些原因而断开时,slave能够自动重连Master,如果master收到了多 个slave并发连接请求,它只会进行一次持久化,而不是一个连接一次,然后再把这一份持久化的数据发送 给多个并发连接的slave.原理图如下

Redis集群架构搭建和原理_第5张图片

但是这样做,如果有很多的从节点,主节点每次都需要发送新的数据给从节点,这样主节点既要保证客户端的连接,又要和从节点保持连接,无疑会增加主节点的压力。所以redis对此做出了数据部分复制的方案

数据部分复制

当master和slave断开重连后,一般都会对整份数据进行复制。但从redis2.8版本开始,redis改用可以支持部分数据复制的命令PSYNC去master同步数据,slave与master能够在网络连接断开重连后只进行部分数据复制(断点续传)。 master会在其内存中创建一个复制数据用的缓存队列,缓存最近一段时间的数据,master和它所有的 slave都维护了复制的数据下标offset和master的进程id,因此,当网络连接断开后,slave会请求master 继续进行未完成的复制,从所记录的数据下标开始。如果master进程id变化了,或者从节点数据下标 offset太旧.已经不在master的缓存队列里了.那么将会进行一次全量数据的复制.主从复制(部分复制,断点续传)流程图:

Redis集群架构搭建和原理_第6张图片

如果有很多从节点,为了缓解主从复制风暴(多个从节点同时复制主节点导致主节点压力过大),可以做如下架构,让部分从节点与从节点(与主节点同步)同步数据

Redis集群架构搭建和原理_第7张图片

第三章 哨兵模式

Redis是sentinel哨兵也是一个特殊的redis实例,但是不提供读写功能,只负责监控redis实例节点。哨兵架构下client端第一次从哨兵找出redis的主节点,后续就直接访问redis的主节点,不会每次都通过 sentinel代理访问redis的主节点,当redis的主节点发生变化,哨兵会第一时间感知到,并且将新的redis 主节点通知给client端(这里面redis的client端一般都实现了订阅功能,订阅sentinel发布的节点变动消息)

Redis集群架构搭建和原理_第8张图片

3.1 环境搭建

1.复制sentinel.conf(端口为26379) 为sentinel-26380.conf

[root@localhost redis] #cp sentinel.conf sentinel-26380.conf

2.修改sentinel-26380.conf文件内容

port 26380 #修改端口
daemonized yes  #后台进行开启
pidfile "/var/run/redis‐sentinel‐26380.pid" #进程文件改为自己的
logfile "26380.log" #日志文件
dir "/usr/local/redis/data" #节点存放数据的目录
# sentinel monitor     11 # quorum是一个数字,指明当有多少个sentinel认为一个master失效时(值一般为:sentinel总数/2 + 1),master才算真正失效
sentinel monitor mymaster 192.168.0.60 6379 2 # mymaster这个名字随便取,客户端访问时会用到 指定主节点的ip和端口
sentinel auth-pass mymaster qianyue #配置主节点的密码

3.启动哨兵实例(为了启动方便 我把sentinel的配置文件也放在了bin目录下)

[root@localhost bin]# redis-sentinel sentinel-26379.conf
[root@localhost bin]# redis-sentinel sentinel-26380.conf
[root@localhost bin]# redis-sentinel sentinel-26381.conf

4.查看效果

[root@localhost bin]# redis-cli -p 26379
127.0.0.1:26379> ping
PONG
127.0.0.1:26379> info
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=192.168.26.129:6379,slaves=2,sentinels=3 #显示哨兵集群节点信息

5.查看sentinel配置文件信息的改变

[root@localhost bin]# vim sentinel-26380.conf

sentinel known-replica mymaster 192.168.26.129 6380  #从节点信息
sentinel known-replica mymaster 192.168.26.129 6381  #从节点信息
sentinel known-sentinel mymaster 192.168.26.129 26380 dcc5e77338f9fde31857389ed08a928892f674d9  #其他哨兵节点信息
sentinel known-sentinel mymaster 192.168.26.129 26381 ec84f68640f6dde594269c3640b1e66ccec1b449  #其他哨兵节点信息

3.2 效果展示

测试sentinel的选举制度

(1)先查看redis集群服务运行情况

可以看到6379、6380、6381三个实例在运行,三个哨兵实例26379、26380、26381也在运行

[root@localhost bin]# ps -ef|grep redis
root      17255  66905  0 09:52 pts/5    00:00:00 grep --color=auto redis
root      29241      1  0 09:13 ?        00:00:02 redis-server *:6379
root      30129      1  0 09:14 ?        00:00:02 redis-server *:6380
root      30246      1  0 09:14 ?        00:00:02 redis-server *:6381
root      35956   2982  0 09:18 pts/1    00:00:00 redis-cli -p 6381
root      38707  36714  0 09:20 pts/3    00:00:00 redis-cli
root     119241      1  0 09:44 ?        00:00:00 redis-sentinel *:26379 [sentinel]
root     119506      1  0 09:44 ?        00:00:00 redis-sentinel *:26380 [sentinel]
root     119686      1  0 09:44 ?        00:00:00 redis-sentinel *:26381 [sentinel]

(2)把6379干掉

[root@localhost bin]# kill 29241
[root@localhost bin]# ps -ef|grep redis
root      23403  66905  0 09:53 pts/5    00:00:00 grep --color=auto redis
root      30129      1  0 09:14 ?        00:00:02 redis-server *:6380
root      30246      1  0 09:14 ?        00:00:02 redis-server *:6381
root      35956   2982  0 09:18 pts/1    00:00:00 redis-cli -p 6381
root      38707  36714  0 09:20 pts/3    00:00:00 redis-cli
root     119241      1  0 09:44 ?        00:00:00 redis-sentinel *:26379 [sentinel]
root     119506      1  0 09:44 ?        00:00:00 redis-sentinel *:26380 [sentinel]
root     119686      1  0 09:44 ?        00:00:00 redis-sentinel *:26381 [sentinel]

(3)查看sentinel.conf内容变化

可以看出6381、6380成为了字节点 6380成为了主节点

sentinel known-replica mymaster 192.168.26.129 6381
sentinel known-replica mymaster 192.168.26.129 6379
sentinel known-sentinel mymaster 192.168.26.129 26380 dcc5e77338f9fde31857389ed08a928892f674d9
sentinel known-sentinel mymaster 192.168.26.129 26381 ec84f68640f6dde594269c3640b1e66ccec1b449

(4)重启6379服务

[root@localhost bin]# redis-server redis.conf
61372:C 01 May 2022 10:03:29.132 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
61372:C 01 May 2022 10:03:29.132 # Redis version=5.0.9, bits=64, commit=00000000, modified=0, pid=61372, just started
61372:C 01 May 2022 10:03:29.132 # Configuration loaded
[root@localhost bin]# vim sentienl-26379.conf
sentinel known-replica mymaster 192.168.26.129 6381
sentinel known-replica mymaster 192.168.26.129 6379
sentinel known-sentinel mymaster 192.168.26.129 26380 dcc5e77338f9fde31857389ed08a928892f674d9
sentinel known-sentinel mymaster 192.168.26.129 26381 ec84f68640f6dde594269c3640b1e66ccec1b449

可以发现 就算6379服务重新启动,也成了从节点

3.3 Jedis操作哨兵集群

1.引入依赖

    <dependencies>
        <dependency>
            <groupId>redis.clientsgroupId>
            <artifactId>jedisartifactId>
            <version>2.9.0version>
        dependency>
    dependencies>

2.编写测试类

public class JedisSentinelTest {

    public static void main(String[] args) {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(20);
        jedisPoolConfig.setMaxIdle(10);
        jedisPoolConfig.setMinIdle(5);
        String masterName = "mymaster";

        Set<String> sentinels= new HashSet<String>();
        sentinels.add(new HostAndPort("192.168.26.129",26379).toString());
        sentinels.add(new HostAndPort("192.168.26.129",26380).toString());
        sentinels.add(new HostAndPort("192.168.26.129",26381).toString());
        //JedisSentinelPool其实本质跟JedisPool类似,都是与redis主节点建立的连接池
        //JedisSentinelPool并不是说与sentinel建立的连接池,而是通过sentinel发现redis主节点并与其 建立连接
        JedisSentinelPool jedisSentinelPool = new JedisSentinelPool(masterName,sentinels,jedisPoolConfig,3000,"qianyue");
        Jedis jedis = null;
        try {
            jedis = jedisSentinelPool.getResource();
            System.out.println(jedis.set("sentinel","qianyue"));
            System.out.println(jedis.get("sentinel"));
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。
            if(jedis!=null){
                jedis.close();
            }
        }
    }

3.4 Spring集成Redis

1.引入pom

 		<dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starterartifactId>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>


        <dependency>
            <groupId>org.springframework.bootgroupId>
              <artifactId>spring-boot-starter-data-redisartifactId>
        dependency>

        <dependency>
            <groupId>org.apache.commonsgroupId>
            <artifactId>commons-pool2artifactId>
        dependency>


        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-tomcatartifactId>
            <version>2.3.4.RELEASEversion>
            <scope>compilescope>
        dependency>

2.编写配置文件

spring:
  redis:

    database: 0        #选择redis的第一个数据库
    password: qianyue   #redis密码
    sentinel:
      master: mymaster #哨兵的名字 #下面是所有哨兵集群节点
      nodes: 192.168.26.129:26379,192.168.26.129:26380,192.168.26.129:26381

3.编写测试类

@RestController
public class RedisController {

    private static final Logger logger = LoggerFactory.getLogger(RedisController.class);

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @RequestMapping("/testSentinel")
    public void testSentinel(){
        int i=1;
        while (true){
            try{
                stringRedisTemplate.opsForValue().set("qianyue"+i,i+"");
                System.out.println("设置key"+"qianyue"+i);
                i++;
                Thread.sleep(1000);

            }catch (Exception e){
                logger.error("错误"+e);
            }

        }
    }
}

4.测试

访问127.0.0.1:8080/testSentinel控制台打印以下结果

Redis集群架构搭建和原理_第9张图片

5.故意把主节点6380挂掉看控制台什么反应

[root@localhost bin]# ps -ef|grep redis
root      30129      1  0 09:14 ?        00:00:11 redis-server *:6380
root      30246      1  0 09:14 ?        00:00:09 redis-server *:6381
root      35956   2982  0 09:18 pts/1    00:00:00 redis-cli -p 6381
root      38707  36714  0 09:20 pts/3    00:00:00 redis-cli
root      61373      1  0 10:03 ?        00:00:08 redis-server *:6379
root     115946  66905  0 10:52 pts/5    00:00:00 grep --color=auto redis
root     119241      1  0 09:44 ?        00:00:10 redis-sentinel *:26379 [sentinel]
root     119506      1  0 09:44 ?        00:00:10 redis-sentinel *:26380 [sentinel]
root     119686      1  0 09:44 ?        00:00:10 redis-sentinel *:26381 [sentinel]
[root@localhost bin]# kill 30129

控制台输出

Redis集群架构搭建和原理_第10张图片

6.过了一会,新主节点选举成功后

Redis集群架构搭建和原理_第11张图片

哨兵模式下,客户端和哨兵连接,哨兵把主节点信息告诉客户端,客户端连接主节点,当主节点挂掉后,客户端重试几次,此时哨兵开始选举新的主节点,选好之后告诉客户端新的主节点,客户端重新连接新的主节点。这是一个消息监听机制实现的。

3.5 哨兵leader选举流程

当一个master服务器被某sentinel视为下线状态后,该sentinel会与其他sentinel协商选出sentinel的leader进行故障转移工作。每个发现master服务器进入下线的sentinel都可以要求其他sentinel选自己为sentinel的 leader,选举是先到先得。同时每个sentinel每次选举都会自增配置纪元(选举周期),每个纪元中只会选择一 个sentinel为leader。如果所有超过一半的sentinel选举某sentinel作为leader。之后该sentinel进行故障转移操作,从存活的slave中选举出新的master,这个选举过程跟集群的master选举很类似。 哨兵集群只有一个哨兵节点,redis的主从也能正常运行以及选举master,如果master挂了,那唯一的那个哨 兵节点就是哨兵leader了,可以正常选举新master。 不过为了高可用一般都推荐至少部署三个哨兵节点。为什么推荐奇数个哨兵节点原理跟集群奇数个master节点类似.

3.6 小结

在redis3.0以前的版本要实现集群一般是借助哨兵sentinel工具来监控master节点的状态,如果master节点异 常,则会做主从切换,将某一台slave作为master,哨兵的配置略微复杂,并且性能和高可用性等各方面表现 一般,特别是在主从切换的瞬间存在访问瞬断的情况,而且哨兵模式只有一个主节点对外提供服务,没法支持 很高的并发,且单个主节点内存也不宜设置得过大,否则会导致持久化文件过大,影响数据恢复或主从同步的效率 。

第四章 高可用集群

redis集群是一个由多个主从节点群组成的分布式服务器群,它具有复制、高可用和分片特性。Redis集群不需 要sentinel哨兵∙也能完成节点移除和故障转移的功能。需要将每个节点设置成集群模式,这种集群模式没有中 心节点,可水平扩展,据官方文档称可以线性扩展到上万个节点(官方推荐不超过1000个节点)。redis集群的 性能和高可用性均优于之前版本的哨兵模式,且集群配置非常简单

4.1 环境搭建

1.准备三台机器,每台机器一个主从架构,我这里有192.168.26.129、192.168.26.130、192.168.26.131三台机器

192.168.26.129 搭建8001、8004端口的节点

192.168.26.130 搭建8002、8005端口的节点

192.168.26.131 搭建8003、8006端口的节点

2.这里以一台机器为例,三台机器照旧就可以

(1)新建一个redis-cluster目录,并创建8001、8004文件夹

[root@localhost redis]# mkdir redis-cluster
[root@localhost redis]# cd redis-cluster/
[root@localhost redis-cluster]# mkdir 8001 8004

(2)复制原本的redis配置文件 到redis-cluter目录下

cp redis.conf  /redis-cluster/redis-8001.conf 
cp redis.conf  /redis-cluster/redis-8004.conf 

Redis集群架构搭建和原理_第12张图片

(3)修改配置文件的内容

daemonize yes #作为后台进程
port 8001#每个节点配置自己的端口
pidfile /var/run/redis_8001.pid # piffile文件名称改为自己端口结尾的
dir /usr/local/redis‐cluster/8001/#指定节点的数据存放位置
cluster‐enabled yes #开启集群模式
cluster‐config‐file nodes‐8001.conf #节点信息文件 和节点的端口匹配上
cluster‐node‐timeout 10000 #节点的超时时间
# bind 127.0.0.1(bind绑定的是自己机器网卡的ip,如果有多块网卡可以配多个ip,代表允许客户端通 过机器的哪些网卡ip去访问,内网一般可以不配置bind,注释掉即可)
protected‐mode no #(关闭保护模式)
appendonly yes 
requirepass qianyue #配置密码
masterauth zhuge  #集群之间访问的密码

其他节点的配置文件修改步骤和上边一样,只不过把端口改为自己的。

(4)启动所有的redis节点实例

[root@localhost redis-cluster]# /usr/local/redis/bin/redis-server  redis-8001.conf
[root@localhost redis-cluster]# /usr/local/redis/bin/redis-server  redis-8002.conf
[root@localhost redis-cluster]# /usr/local/redis/bin/redis-server  redis-8003.conf
[root@localhost redis-cluster]# /usr/local/redis/bin/redis-server  redis-8004.conf
[root@localhost redis-cluster]# /usr/local/redis/bin/redis-server  redis-8005.conf
[root@localhost redis-cluster]# /usr/local/redis/bin/redis-server  redis-8006.conf

(5)查看节点是否都启动成功

[root@localhost redis-cluster]#  ps -ef|grep redis

image-20220501160615340

(6)确保节点之间可以通信 这里是演示所以关闭了防火墙

[root@localhost redis-cluster]#  systemctl stop firewalld
[root@localhost redis-cluster]# systemctl disable firewalld

也可以打开redis服务端口和集群节点gossip通信端口 默认是redis端口号加10000

(7)创建集群

在任一服务器执行下边这行命令

-a qianyue 指定密码

1 代表的是一主一从

[root@localhost redis-cluster]# /usr/local/redis/bin/redis-cli  -a qianyue --cluster create --cluster-replicas  1 192.168.26.129:8001 192.168.26.131:8002 192.168.26.130:8003 192.168.26.129:8004 192.168.26.131:8005 192.168.26.130:8006

(8)查看集群是否搭建成功

连接8001客户端

[root@localhost redis-cluster]# /usr/local/redis/bin/redis-cli -p 8001
#查看集群信息
127.0.0.1:8001> 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:1991
cluster_stats_messages_pong_sent:1979
cluster_stats_messages_sent:3970
cluster_stats_messages_ping_received:1974
cluster_stats_messages_pong_received:1991
cluster_stats_messages_meet_received:5
cluster_stats_messages_received:3970
#查看集群节点信息
127.0.0.1:8001> cluster nodes
12dfcf49d13625ef56784ffdeeb47df4e62761a8 192.168.26.131:8002@18002 master - 0 1651393086869 2 connected 5461-10922
08e17547c07e6918708708bb974c9497d1336c06 192.168.26.130:8003@18003 master - 0 1651393087879 3 connected 10923-16383
b133e1dfd70740f4953845ec58bc448e86615489 192.168.26.129:8004@18004 slave 08e17547c07e6918708708bb974c9497d1336c06 0 1651393086000 4 connected
120157e4122a30fec051717712841c03773b42d1 192.168.26.130:8006@18006 slave 12dfcf49d13625ef56784ffdeeb47df4e62761a8 0 1651393086000 6 connected
5486ac47040a34e8fa64e3b4378b0fe637363b44 192.168.26.129:8001@18001 myself,master - 0 1651393088000 1 connected 0-5460
2cad756438fe1ccd417b38971f668c96c73e8b52 192.168.26.131:8005@18005 slave 5486ac47040a34e8fa64e3b4378b0fe637363b44 0 1651393088889 5 connected

4.2 Jedis操作集群

1.引入依赖

<dependency>
    <groupId>redis.clientsgroupId>
    <artifactId>jedisartifactId>
    <version>2.9.0version>
dependency>

2.测试代码

public class JedisClusterTest {

    public static void main(String[] args) {
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(20);
        config.setMaxIdle(10);
        config.setMinIdle(5);


        Set<HostAndPort> jedisClusterNode = new HashSet<HostAndPort>();
        jedisClusterNode.add(new HostAndPort("192.168.26.129",8001));
        jedisClusterNode.add(new HostAndPort("192.168.26.129",8004));
        jedisClusterNode.add(new HostAndPort("192.168.26.131",8002));
        jedisClusterNode.add(new HostAndPort("192.168.26.131",8005));
        jedisClusterNode.add(new HostAndPort("192.168.26.130",8003));
        jedisClusterNode.add(new HostAndPort("192.168.26.130",8006));

        JedisCluster jedisCluster = null;
        try {
            jedisCluster  = new JedisCluster(jedisClusterNode, 6000, 5000, 10, "qianyue", config);
            System.out.println(jedisCluster.set("cluster","qianyue"));
            System.out.println(jedisCluster.get("cluster"));
        }catch (Exception e){
            e.printStackTrace();
        }

    }
}

3.Springboot集成redis集群

1.引入依赖

 <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starterartifactId>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>


        <dependency>
            <groupId>org.springframework.bootgroupId>
              <artifactId>spring-boot-starter-data-redisartifactId>
        dependency>

        <dependency>
            <groupId>org.apache.commonsgroupId>
            <artifactId>commons-pool2artifactId>
        dependency>


        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-tomcatartifactId>
            <version>2.3.4.RELEASEversion>
            <scope>compilescope>
        dependency>

2.yaml配置

#高可用集群配置
spring:
  redis:
    cluster:
      nodes: 192.168.26.129:8001,192.168.26.131:8002,192.168.26.130:8003,192.168.26.129:8004,192.168.26.131:8005,192.168.26.130:8006
    password: qianyue

3.测试代码

@RestController
public class RedisController {

    private static final Logger logger = LoggerFactory.getLogger(RedisController.class);

    @Autowired
    private StringRedisTemplate stringRedisTemplate;


   @RequestMapping("/testSentinel")
   public void testSentinel(){
        stringRedisTemplate.opsForValue().set("myCluster","测试成功");
        System.out.println(stringRedisTemplate.opsForValue().get("myCluster"));
    }
}

4.3 集群操作-水平扩展

在之前六个节点的基础上上在添加两个节点,也作为主从架构

Redis集群架构搭建和原理_第13张图片

步骤:

1.增加两个8007,8008两个实例

步骤略,参照集群搭建的步骤来

2.添加8007节点到集群中

查看redis集群的帮助说明

[root@localhost redis-cluster]# /usr/local/redis/bin/redis-cli --cluster help
Cluster Manager Commands:
  create         host1:port1 ... hostN:portN  #创建一个集群
                 --cluster-replicas <arg> 
  check          host:port
                 --cluster-search-multiple-owners #检查集群状态
  info           host:port
  fix            host:port
                 --cluster-search-multiple-owners
  reshard        host:port #重新分片
                 --cluster-from <arg>
                 --cluster-to <arg>
                 --cluster-slots <arg>
                 --cluster-yes
                 --cluster-timeout <arg>
                 --cluster-pipeline <arg>
                 --cluster-replace
  rebalance      host:port
                 --cluster-weight <node1=w1...nodeN=wN>
                 --cluster-use-empty-masters
                 --cluster-timeout <arg>
                 --cluster-simulate
                 --cluster-pipeline <arg>
                 --cluster-threshold <arg>
                 --cluster-replace
  add-node       new_host:new_port existing_host:existing_port  #添加一个节点到集群中,第一个参数为新节点的ip:端口 第二位为已存在节点ip:端口
                 --cluster-slave
                 --cluster-master-id <arg>
  del-node       host:port node_id  #移除一个节点
  call           host:port command arg arg .. arg  #执行redis命令
  set-timeout    host:port milliseconds
  import         host:port
                 --cluster-from <arg>
                 --cluster-copy
                 --cluster-replace
  help           

For check, fix, reshard, del-node, set-timeout you can specify the host and port of any working node in the cluster.

把8007添加到集群中

[root@localhost redis-cluster]# /usr/local/redis/bin/redis-cli  -a qianyue --cluster add-node 192.168.26.129:8007 192.168.26.129:8001

查看集群状态

image-20220501184903534

此时8007是主节点(默认刚开始加入集群的节点都是主节点),此时是没有写入功能的,因为没有插槽分配

3.分配插槽

[root@localhost redis-cluster]# /usr/local/redis/bin/redis-cli -a qianyue --cluster reshard 192.168.26.129:8001
How many slots do you want to move (from 1 to 16384)? 600 #给新节点分配多少插槽
What is the receiving node ID? 6c6c9eaed7289dfee5ba1ecc61145e4a9bf7b558  #要接收插槽的id是多少
Please enter all the source node IDs.
  Type 'all' to use all the nodes as source nodes for the hash slots. #all 代表从全部主节点中一共选取600个插槽分为新节点
  Type 'done' once you entered all the source nodes IDs.

下面生成一个计划

Redis集群架构搭建和原理_第14张图片

Do you want to proceed with the proposed reshard plan (yes/no)?  #是否以这个计划生成槽位

image-20220501190046731

4.把8008作为8007的从节点

把8008加入集群中

[root@localhost redis-cluster]# /usr/local/redis/bin/redis-cli -a qianyue --cluster add-node 192.168.26.129:8008 192.168.26.129:8001

在8008客户端添加主节点

[root@localhost redis-cluster]# /usr/local/redis/bin/redis-cli -a qianyue -p 8008 进入8008节点客户端
127.0.0.1:8008> cluster replicate 6c6c9eaed7289dfee5ba1ecc61145e4a9bf7b558 #后边是主节点的id

查看集群情况

Redis集群架构搭建和原理_第15张图片

5.删除8008节点

[root@localhost redis-cluster]# /usr/local/redis/bin/redis-cli -a qianyue  --cluster del-node 192.168.26.129:8008 617c887064c73acf244921fced04c7a57618a187 #参数为 要删除的ip:端口 节点id

如下8008已不在集群中

image-20220501191052820

6.删除8007主节点

因为8007主节点是有槽位的,需要先做数据迁移才可以删除成功

[root@localhost redis-cluster]# /usr/local/redis/bin/redis-cli -a qianyue  --cluster reshard 192.168.26.129:8001
How many slots do you want to move (from 1 to 16384)? 600
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: 6c6c9eaed7289dfee5ba1ecc61145e4a9bf7b558 #从哪个节点挪出槽位
Source node done #表示结束

查看集群状况

image-20220501191903780

然后开始进行删除节点

[root@localhost redis-cluster]# /usr/local/redis/bin/redis-cli -a qianyue  --cluster del-node 192.168.26.129:8007 6c6c9eaed7289dfee5ba1ecc61145e4a9bf7b558

查看集群状况

image-20220501192015957

8.关闭一个集群

[root@localhost redis-cluster]# /usr/local/redis/bin/redis-cli -a qianyue -c -h 192.168.26.129 -p 8001  shutdown
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
[root@localhost redis-cluster]# /usr/local/redis/bin/redis-cli -a qianyue -c -h 192.168.26.131 -p 8002  shutdown
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
[root@localhost redis-cluster]# /usr/local/redis/bin/redis-cli -a qianyue -c -h 192.168.26.130 -p 8003  shutdown
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
[root@localhost redis-cluster]# /usr/local/redis/bin/redis-cli -a qianyue -c -h 192.168.26.131 -p 8005  shutdown
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
[root@localhost redis-cluster]# /usr/local/redis/bin/redis-cli -a qianyue -c -h 192.168.26.130 -p 8006  shutdown
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
[root@localhost redis-cluster]# /usr/local/redis/bin/redis-cli -a qianyue -c -h 192.168.26.129 -p 8004  shutdown
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.

4.4 高可用集群原理

Redis Cluster 将所有数据存储划分为 16384 个 slots(槽位),每个节点负责其中一部分槽位。槽位的信息存储于每 个节点中。 当 Redis Cluster 的客户端来连接集群时,它也会得到一份集群的槽位配置信息并将其缓存在客户端本地。这 样当客户端要查找某个 key 时,可以直接定位到目标节点。同时因为槽位的信息可能会存在客户端与服务器不 一致的情况,还需要纠正机制来实现槽位信息的校验调整。

1)槽位定位算法

Cluster 默认会对 key 值使用 crc16 算法进行 hash 得到一个整数值,然后用这个整数值对 16384 进行取模 来得到具体槽位。

HASH_SLOT = CRC16(key) mod 16384

2)跳转重定位

当客户端向一个错误的节点发出了指令,该节点会发现指令的 key 所在的槽位并不归自己管理,这时它会向客 户端发送一个特殊的跳转指令携带目标操作的节点地址,告诉客户端去连这个节点去获取数据。客户端收到指 令后除了跳转到正确的节点上去操作,还会同步更新纠正本地的槽位映射表缓存,后续所有 key 将使用新的槽 位映射表。

例如

127.0.0.1:8003> get myCluster  #myCluster存储在8002机器上
(error) MOVED 5948 192.168.26.131:8002

3)Redis集群节点之间的通信机制

redis cluster节点间采取gossip协议进行通信

维护集群的元数据(集群节点信息,主从角色,节点数量,各节点共享的数据等)有两种方式:集中式和gossip

集中式:

优点在于元数据的更新和读取,时效性非常好,一旦元数据出现变更立即就会更新到集中式的存储中,其他节 点读取的时候立即就可以立即感知到;不足在于所有的元数据的更新压力全部集中在一个地方,可能导致元数 据的存储压力。 很多中间件都会借助zookeeper集中式存储元数据

gossip:

gossip协议包含多种消息,包括ping,pong,meet,fail等等。

meet:某个节点发送meet给新加入的节点,让新节点加入集群中,然后新节点就会开始与其他节点进行通

信;

ping:每个节点都会频繁给其他节点发送ping,其中包含自己的状态还有自己维护的集群元数据,互相通过 ping交换元数据(类似自己感知到的集群节点增加和移除,hash slot信息等);

pong: 对ping和meet消息的返回,包含自己的状态和其他信息,也可以用于信息广播和更新;

fail: 某个节点判断另一个节点fail之后,就发送fail给其他节点,通知其他节点,指定的节点宕机了。

gossip协议的优点在于元数据的更新比较分散,不是集中在一个地方,更新请求会陆陆续续,打到所有节点上 去更新,有一定的延时,降低了压力;缺点在于元数据更新有延时可能导致集群的一些操作会有一些滞后。

4)Redis集群节点之间通信的端口

每个节点都有一个专门用于节点间gossip通信的端口,就是自己提供服务的端口号+10000,比如8001节点,他的通信节点就是18001。每个节点每隔一段时间都会往另外几个节点发送ping消息,同时其他几 点接收到ping消息之后返回pong消息。

5)网络抖动

真实世界的机房网络往往并不是风平浪静的,它们经常会发生各种各样的小问题。比如网络抖动就是非常常见 的一种现象,突然之间部分连接变得不可访问,然后很快又恢复正常。为解决这种问题,Redis Cluster 提供了一种选项cluster­node­timeout,表示当某个节点持续 timeout 的时间失联时,才可以认定该节点出现故障,需要进行主从切换。如果没有这个选项,网络抖动会导致主从频 繁切换 (数据的重新复制)

6)Redis集群选举原理分析

当slave发现自己的master变为FAIL状态时,便尝试进行Failover(故障转移),以期成为新的master。由于挂掉的master 可能会有多个slave,从而存在多个slave竞争成为master节点的过程, 其过程如下:

1.slave发现自己的master变为FAIL

2.将自己记录的集群currentEpoch(选举周期)加1,并广播FAILOVER_AUTH_REQUEST信息通知其他节点投票

3.其他节点收到该信息,只有master响应,判断请求者的合法性,并发送FAILOVER_AUTH_ACK,对每一个 epoch只发送一次ack

4.尝试failover的slave收集master返回的FAILOVER_AUTH_ACK

5.slave收到超过半数master的ack后变成新Master(这里解释了集群为什么至少需要三个主节点,如果只有两 个,当其中一个挂了,只剩一个主节点是不能选举成功的)

6.slave广播Pong消息通知其他集群节点。

从节点并不是在主节点一进入 FAIL 状态就马上尝试发起选举,而是有一定延迟,一定的延迟确保我们等待 FAIL状态在集群中传播,slave如果立即尝试选举,其它masters或许尚未意识到FAIL状态,可能会拒绝投票

延迟计算公式:

DELAY = 500ms + random(0 ~ 500ms) + SLAVE_RANK * 1000ms

SLAVE_RANK表示此slave已经从master复制数据的总量的rank。Rank越小代表已复制的数据越新。这种方 式下,持有最新数据的slave将会首先发起选举(理论上)

7)集群脑裂数据丢失问题

redis集群没有过半机制会有脑裂问题(一个小集群有多个主节点,各写各的数据),网络分区导致脑裂后多个主节点对外提供写服务,一旦网络分区恢复, 会将其中一个主节点变为从节点(此时这个主节点之前写的数据全没,而是从主节点重新同步),这时会有大量数据丢失。规避方法可以在redis配置里加上参数(这种方法不可能百分百避免数据丢失

# min‐replicas‐to‐write 1 //写数据成功最少同步的slave数量,这个数量可以模仿大于半数机制配置,比如 集群总共三个节点可以配置1,加上leader就是2,超过了半数

注意:这个配置在一定程度上会影响集群的可用性,比如slave要是少于1个,这个集群就算leader正常也不能提供服务了,需要具体场景权衡选择。

8)集群是否完整才能对外提供服务

当redis.conf的配置cluster-require-full-coverage为no时,表示当负责一个插槽的主库下线且没有相应的从 库进行故障恢复时,整个集群仍然可用,如果为yes则整个集群不可用

9)Redis集群为什么至少需要三个master节点,并且推荐节点数为奇数?

因为新master的选举需要大于半数的集群master节点同意才能选举成功,如果只有两个master节点,当其中 一个挂了,是达不到选举新master的条件的奇数个master节点可以在满足选举该条件的基础上节省一个节点,比如三个master节点和四个master节点的 集群相比,大家如果都挂了一个master节点都能选举新master节点,如果都挂了两个master节点都没法选举 新master节点了,所以奇数的master节点更多的是从节省机器资源角度出发说的。

10)Redis集群对批量操作命令的支持

对于类似mset,mget这样的多个key的原生批量操作命令,redis集群只支持所有key落在同一slot的情况,如果有多个key一定要用mset命令在redis集群上操作,则可以在key的前面加上{XX},这样参数数据分片hash计 算的只会是大括号里的值,这样能确保不同的key能落到同一slot里去,示例如下:

#错误的
127.0.0.1:8003> mset key1 value1 key2 value2
(error) CROSSSLOT Keys in request don't hash to the same slot
#正确的
127.0.0.1:8003> mset {same}:key1 value1 {same}:key2 value2
OK
127.0.0.1:8003> get {same}:key1
"value1"
127.0.0.1:8003> get {same}:key2
"value2"

你可能感兴趣的:(redis,redis,linux,数据库)