Redis单机、主从、哨兵、集群演进之路

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
日志服务 SLS,月写入数据量 50GB 1个月
简介: Redis架构演进1.为什么需要主从2.为什么需要哨兵3.有了主从又有了哨兵,为什么有需要分片集群呢?

1.前言

1.1 单机时代

刚接触redis的时候,为了能快速学习和了解这门技术,我们通常会在自己的电脑上部署一个redis服务,以此来开启redis学习之路

1.2 主从时代

随着对redis的进一步深入,很快就会发现这门技术在很多场景下都能得到应用,比如:并发场景下对共享资源的控制(分布式锁)、高并发场景下对系统的保护(限流)、高并发场景下对响应时间的要求(缓存)

在生产环境使用redis服务是否能像当初我们学习时那样,仅仅部署一个单实例redis就可以呢?如果选择单实例部署,当该实例出现故障,使用redis的业务场景都会随之受影响。为了降低单实例故障带来的影响,通常会选择冗余的方式来保证服务的高可靠性,在redis中,我们称之为主-从

1.3 哨兵时代

redis进行主-从部署后是不是就可以高枕无忧了呢?当然不是,你还需要时时刻刻监控redis主的健康状态,当其出现故障后能第一时间发现并能在从库中完成选主任务,否则同样会给相关业务带来影响。在redis中,我们通常会使用哨兵机制来帮我们完成监控选主通知操作,从而使我们的redis服务具备一定的高可靠性

1.4 集群分片时代

随着业务的飞速发展,redis实例中存放的数据也越来越多,当需要存储25G以上的数据时,估计你会选择一台32G的机器进行部署这个看似简单的选择题却隐藏着很严重的问题:

  • redis中存放的数据越多,意味着宕机后的恢复时间也越长,从而导致服务长时间不可用
  • 数据越多进行rdb fork操作的时候,阻塞redis主线程的时间也越长,从而导致服务响应慢

面对此类问题我们通常会基于大而化小、分而治之的思想进行解决,在redis中我们称之为集群分片技术

开篇对单机、主从、哨兵、分片进行了简单介绍,下文将结合实战展开细说。

2.redis安装

磨刀不误砍柴工,在开始之前首先要保证我们的redis服务可以正常启动并能提供指令操作。

2.1下载安装包

wget https://download.redis.io/redis-stable.tar.gz

2.2解压

tar -zxvf redis-stable.tar.gz

2.3 编译

➜  redis-stable make & make install

2.4 启动

redis-server

2.5 客户端连接测试

redis-cli
127.0.0.1:6379> set name bobo
OK
127.0.0.1:6379> get name
"bobo"

完成服务的安装与测试,接下来就可以放手进行实战,实战的第一部分主从

3.主从

3.1 部署图

主从一共由3台机器组成,演示环境通过端口号进行区分

3.2 创建conf目录

主从由3个redis服务组成,每个redis服务对应的配置都不相同,因此需要创建conf文件夹用于存放配置文件

➜  redis-stable mkdir conf

3.3 生成redis配置文件

默认情况下redis.conf配置文件会有很多注释说明,为了让配置文件看上去清晰明了,使用如下命令来去除配置文件中的注释以及空行

➜  redis-stable cat redis.conf | grep -v "#" | grep -v "^$" > ./conf/redis-6379.conf

3.3 添加复制配置

主从方式,需要知道应该从哪一个进行数据复制,因此需要在再配置文件中添加如下配置

replicaof 127.0.0.1 6380

3.4 拷贝redis配置文件

之前也有过说明,演示环境通过端口号进行区分,因此配置文件中除了端口号以及数据保存路径不一样之外,其它的都一样,这里可以通过如下命令进行配置文件拷贝

➜  redis-stable sed 's/6379/6380/g' conf/redis-6379.conf > conf/redis-6380.conf
➜  redis-stable sed 's/6379/6381/g' conf/redis-6379.conf > conf/redis-6381.conf

3.5 启动redis

万事俱备,只欠东风,准备工作完成之后,只需要逐个启动服务即可

3.5.1 启动主6379

在启动的时候需要和建立连接,因此应先启动主6379

通过日志文件可以了解到主6379在启动的过程中会去加载rdb文件用于数据恢复,并且从rdb文件中加载了一个key

3.5.2 启动从6380

从6380启动后,可以看到主6379的日志内容有所增加

从6380主6379请求数据同步,由于是第一次会进行全量同步,主6379 fork进程生成rdb文件,然后将生成好的rdb文件传输给6380

从6380启动过程中会去连接主6379,接收主6379传过来的rdb文件,在清空旧数据之后加载rdb文件进行数据同步

3.5.3 启动从6381

从6381启动和从6380启动是一样的流程,不再进行细说

3.5.4 重启6381

到这里,你已经知道在第一次连接后会进行全量同步,估计也会好奇非第一次连接会如何进行同步。想知道结果,只需要重启其中一个即可,这里选择重启从6381

重启后,会发现从6381请求的是增量同步而非全量同步

通过主6379的日志可以看到其接受了从6381增量同步的请求,并从backlog偏移量183开始发送了245字节的数据

这里可以猜想一下,主6379若想知道应该增量同步哪些数据给从6381,那么它一定得知道从6381上一次同步到哪里,因此重启再次连接的时候,从6381应该会将上一次同步到哪了的信息发给了主6379

关于数据同步部分,可以得出不知道从哪开始同步就选择全量同步,知道从哪开始同步就选择增量同步的结论

3.6 主从如何保证数据一致性?

前面提到过,主6379在接到从的全量同步请求后会生成rdb文件,在生成rdb文件的过程中以及将rdb文件传给并且使用rdb文件恢复数据的过程中都没有新命令产生,那么主从的数据就可以保持一致。

如果这期间产生了新的命令会不会导致主从数据不一致?

根据官方文档 How Redis replication works中的介绍,我们可以知道期间产生的新命令会被缓存起来,在加载完rdb文件数据之后,会将这期间缓存的命令发送给在接受并执行完这些新命令后,就可以继续保持与数据的一致性

4.哨兵

redis主从固然可以提升服务的高可靠性,却依然需要人为去进行监控选主通知。看上去似乎不是很靠谱,因为我们不可能做到7 * 24小时盯着redis服务,在其出问题后手动进行故障转移并通知客户端新主的地址。

redis中我们可以通过哨兵机制来实现监控选主通知流程自动化,一来可以减轻开发人员压力;二来可以降低人为误操作率;三来可以提升故障恢复时效性。

接下来会展示如何去搭建哨兵集群以及如何进行选主通知客户端新主地址

4.1 生成sentinel配置文件

➜  redis-stable cat sentinel.conf | grep -v "#" | grep -v "^$" > ./conf/sentinel-26379.conf

4.2 拷贝sentinel配置文件

➜  redis-stable sed 's/26379/26380/g' conf/sentinel-26379.conf > conf/sentinel-26380.conf
➜  redis-stable sed 's/26379/26381/g' conf/sentinel-26379.conf > conf/sentinel-26381.conf

4.3 启动sentinel

4.3.1 启动26379

通过日志可以看到sentinel在启动的时候会生成一个唯一id,也就是Sentinel Id,并且还打印出了redis主从的相关信息,可是根据sentinel配置文件中的配置sentinel monitor mymaster 127.0.0.1 6379 2sentine是不知道的相关信息,那么它是从哪得到这些信息的呢?

要想获得这些信息,sentinel只需要给监控的发送info命令即可

4.3.2 启动26380

启动sentinel26380的时候通过日志可以看到其发现了sentinel26379的存在

查看配置文件,可以看到配置文件中新增了和其它sentitnel相关配置

4.3.3 启动26381

同理sentinel26381启动的时候发现了sentinel26379sentinel26380的存在并在配置文件中新增了相关配置

4.3 sentinel是如何发现彼此的存在?

sentinel26381启动完成后,sentinel集群也就搭建完成了,在搭建的过程中也留下了一个疑问:sentinel是如何发现彼此的存在?

根据官方文档High availability with Redis Sentinel中Sentinels and replicas auto discovery的介绍,可以了解到sentinel是通过Pub/Sub 机制来发现彼此的存在,当一个sentinel连接后,可以在__sentinel__:hello通道中发布其对应的ipportrunid,同时订阅__sentinel__:hello通道,这样其它sentinel发布消息的时候就可以得知对应的ipport

4.4 sentinel功能验证

sentinel集群搭建完成后,我们需要停掉来验证其是否完成监控选主通知任务。

首先停掉主6379,分别观察3个sentinel的变化

4.4.1 观察sentinel26379

4.4.2 观察sentinel26380

4.4.3 观察sentinel26381

通过观察日志可以看到主6379下线后的一些变化:

  • 每个sentinel都监控到主6379下线,主观上认为其下线了(由于网络原因,可能存在误判),对应日志中的+sdown部分
  • 当半数以上的sentinel认为主6379下线,则客观上认为其下线了(排除误判),对应日志中的+odown部分
  • sentinel集群通过投票方式选出sentinel26379作为leader来执行选主操作
  • sentinel26379选择redis6380作为新的,修改slave配置

4.4.5 重启redis6379

sentinel选出新主后,是否将新主地址通知到各个客户端。想要验证这个问题,只需要重启redis6379即可。

redis6379启动后成功连接上新主redis6380并进行数据同步

4.5 从和重启后的旧主是如何知道新主的地址?

通过上面的实战演示,可以得知,宕机选出新主后,剩余的会自动连接上新主并进行数据同步,旧主重启后也可以正常连接上新主并进行数据同步。对于能连接上新主还可以理解,毕竟redis主从切换的过程中,起码是运行状态,但是旧主redis主从切换的过程中处于宕机状态,为什么在重启后还可以正常连接上新主

猜想应该是:在redis主从切换完成后,sentinel leader发送了slaveof 新主host 新主port命令,这样就可以与新主连接并进行数据同步。针对旧主在其重启后,sentinel与之建立连接并发送slaveof 新主host 新主port命令,这样旧主也可以与新主连接并进行数据同步。

4.6 客户端应用是如何知道新主的地址?

使用springboot集成redis sentinel,通常会在application.yml中添加如下配置:

spring:
  redis:
    sentinel:
      master: mymaster
      nodes:
        - 127.0.0.1:26379
        - 127.0.0.1:26380
        - 127.0.0.1:26381

配置文件中仅仅只配置了sentinel节点信息,并没有配置redis相关地址信息,客户端是如何知道redis地址信息,当redis发送主从切换,客户端又是如何知道新主的地址信息?

根据官方文档High availability with Redis Sentinel中Obtaining the address of the current master的介绍,客户端可以通过SENTINEL get-master-addr-by-name mymaster命令获取当前的地址信息

结合客户端源码分析

我们可以得知客户端是通过向sentinel发送get-master-addr-by-name mymaster命令来获取redis主的连接地址

4.7 总结

  • sentinel之间通过redispub/sub机制发现彼此的存在
  • redis主从切换后,sentinel旧主发送slaveof host port命令来连接新主
  • 客户端通过向sentinel发送get-master-addr-by-name mymaster命令来获取redis主的连接地址

5.集群

redis主从模式在大多数场景下都是比较适用,在面对需要持久化大量数据的场景下会变得比之前慢。慢的主要原因是:在进行rdb持久化会fork出一个子进程,fork操作会阻塞主线程并且阻塞时间与内存中的数据成正相关。因此,在面对大量数据需要持久化的场景时,可以考虑选择redis-cluster来进行应对。

在使用集群时,需要考虑下面问题:

  • key/value在集群中如何分布
  • 想对某个key/value进行操作时,该连接那个节点
  • 集群节点数量发生变化,还用原来的地址操作key/value是否可行?

为了弄明白这些问题,按照惯例,先来搭建redis集群

5.1 集群搭建

5.1.1 创建conf目录

➜  redis-stable mkdir conf

5.1.2 创建配置文件

conf目录下创建redis-7001.conf配置文件,内容如下:

port 7001
cluster-enabled yes
cluster-config-file nodes-7001.conf
cluster-node-timeout 5000
appendonly yes

5.1.3 拷贝集群配置文件

➜  redis-stable sed 's/7001/7002/g' conf/redis-7001.conf > conf/redis-7002.conf
sed 's/7001/7003/g' conf/redis-7001.conf > conf/redis-7003.conf
sed 's/7001/7004/g' conf/redis-7001.conf > conf/redis-7004.conf
sed 's/7001/7005/g' conf/redis-7001.conf > conf/redis-7005.conf
sed 's/7001/7006/g' conf/redis-7001.conf > conf/redis-7006.conf

5.1.4 分别启动redis服务

➜  redis-stable redis-server conf/redis-7001.conf
➜  redis-stable redis-server conf/redis-7002.conf
➜  redis-stable redis-server conf/redis-7003.conf
➜  redis-stable redis-server conf/redis-7004.conf
➜  redis-stable redis-server conf/redis-7005.conf
➜  redis-stable redis-server conf/redis-7006.conf

5.1.5 创建集群

redis-cli --cluster create 127.0.0.1:7001 127.0.0.1:7002 \
127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 127.0.0.1:7006 \
--cluster-replicas 1

输入完命令后,可以看到如下分配方案,针对分配方案输入yes即可

为了方便理解,可以看下图

到此,redis集群就搭建完成了

5.2 客户端连接集群

5.2.1 缓存slot与实例映射关系

搭建完redis集群后,可以通过客户端来连接集群,为了能更清晰了解客户端连接集群都做了些什么,这里选择使用spring boot应用作为客户端。通过源码调试,可以看到如下结果:

客户端应用在启动的时候会向redis集群实例发送cluster slots命令获取slot分配信息,然后在本地缓存slotredis实例的映射关系

5.2.2 根据key计算对应slot

客户端在对key/value进行操作时,会对key进行CRC计算并和cluster slot数量进行运算得到最终的slot

5.2.3 根据slot获取实例

5.2.1中提到客户端会缓存slot实例映射关系,5.2.2根据计算得到对应的slot,若想与redis实例交互,此时只需要从映射缓存中获取对应的实例既可

5.2.4 刷新slot与实例映射缓存关系

集群环境往往会伴随着实例的上线与下线,不管是上线还是下线,都会使得slot重新分配,原本在某个实例上的slot会被分配到新的实例上,针对这种情况,客户端如果还是请求旧实例会发生什么?客户端又如何知道slot对应的实例发生了变化?

为了演示该场景,会再运行两台redis实例,分别为:redis7007redis7008redis实例启动完成之后,通过redis-cli --cluster add-node 127.0.0.1:7007 127.0.0.1:7001命令将redis7007master的方式加入到已存在的集群中,再通过redis-cli --cluster add-node 127.0.0.1:7008 127.0.0.1:7000 --cluster-slave命令将redis7008slave的方式加入到已存在的集群中。

redis实例加入集群后,可以通过redis-cli --cluster check 127.0.0.1:7001命令来查看集群的当前情况

根据输出内容可以看到新加入的redis7007实例还没有分配slot,可以通过redis-cli --cluster reshard 127.0.0.1:7001命令来分配slot,分配成功之后原本在redis7001redis7002redis7003实例上的slot会移动到redis7007上。

找一个原本在redis7001实例上后来被移动到redis7007实例上的key,执行get命令

执行完get命令后,可以看到服务端返回了错误,并告诉我们当前key已经移动到redis7007实例上,这是redis-cli客户端执行后的结果,应用客户端会如何处理呢?

通过源码分析,可以得知当出现JedisMovedDataException异常后,应用客户端会重新发送cluster slots命令来刷新本地缓存

5.3 总结

  • 集群部署后,16384slot会散落在各个redis master实例上
  • 客户端通过发送cluster slots命令在本地缓存slotredis实例的映射关系
  • key/value进行操作时,key通过CRC计算并和slot数量 - 1进行运算后得到对应slot,得到slot,又知道slotredis实例的映射关系,就可以对redis实例进行访问
  • slot迁移后,客户端还用原来的实例执行命令,会出现异常,针对异常客户端会通过发送cluster slots命令来刷新本地slotredis实例的映射关系
相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
3月前
|
Kubernetes NoSQL Redis
k8s快速部署Redis单机
k8s快速部署Redis单机
|
3月前
|
监控 NoSQL Redis
看完这篇就能弄懂Redis的集群的原理了
看完这篇就能弄懂Redis的集群的原理了
102 0
|
22天前
|
监控 NoSQL 算法
Redis Sentinel(哨兵)详解
Redis Sentinel(哨兵)详解
|
2月前
|
存储 NoSQL Redis
SpringCloud基础7——Redis分布式缓存,RDB,AOF持久化+主从+哨兵+分片集群
Redis持久化、RDB和AOF方案、Redis主从集群、哨兵、分片集群、散列插槽、自动手动故障转移
SpringCloud基础7——Redis分布式缓存,RDB,AOF持久化+主从+哨兵+分片集群
|
2月前
|
NoSQL Linux Redis
linux安装单机版redis详细步骤,及python连接redis案例
这篇文章提供了在Linux系统中安装单机版Redis的详细步骤,并展示了如何配置Redis为systemctl启动,以及使用Python连接Redis进行数据操作的案例。
58 2
|
21天前
|
缓存 NoSQL 关系型数据库
单机版Redis
【10月更文挑战第3天】
31 0
|
3月前
|
运维 监控 NoSQL
【Redis】哨兵(Sentinel)原理与实战全解~炒鸡简单啊
Redis 的哨兵模式(Sentinel)是一种用于实现高可用性的机制。它通过监控主节点和从节点,并在主节点故障时自动进行切换,确保集群持续提供服务。哨兵模式包括主节点、从节点和哨兵实例,具备监控、通知、自动故障转移等功能,能显著提高系统的稳定性和可靠性。本文详细介绍了哨兵模式的组成、功能、工作机制以及其优势和局限性,并提供了单实例的安装和配置步骤,包括系统优化、安装、配置、启停管理和性能监控等。此外,还介绍了如何配置主从复制和哨兵,确保在故障时能够自动切换并恢复服务。
|
3月前
|
NoSQL Redis
Redis——单机迁移cluster集群如何快速迁移
Redis——单机迁移cluster集群如何快速迁移
110 0
|
3月前
|
NoSQL Linux Redis
使用docker-compose搭建redis-cluster集群
使用docker-compose搭建redis-cluster集群
479 0
|
存储 NoSQL 网络安全
Redis安装(单机、主从、哨兵、集群)
Redis安装(单机、主从、哨兵、集群)
164 1