日常测试和学习的过程中,我们都习惯用Redis单节点来进行操作。但是单节点Redis往往会暴露出一些问题。如:
所以在企业开发的过程中,通常使用的是Redis集群来构建,从而来保证Redis的高可用性和健全性。
Redis中有两种持久化方式:RDB持久化、AOF持久化。
Redis Database Backup file :Redis数据备份文件,也称为Redis数据快照。简单来说就是将内存中的所有数据都记录到磁盘中。当Redis实例故障重启之后,从磁盘中读取快照文件,恢复数据。快照文件称为RDB文件,默认保存在当前运行目录中。
127.0.0.1:6379> save #由Redis主进程来执行RDB,会阻塞所有命令
ok
127.0.0.1:6379> bgsave #开启子进程执行RDB,避免主进程受到影响
Background saving started
当Redis停机时会自动执行一次RDB。但如果Redis是突然宕机,而非停机,则这种RDB机制会失效。但是面对这种问题,Redis内部有触发RDB的机制,可以在redis.conf文件中找到,格式如下:
# 900秒内,如果至少有1个key被修改,则执行bgsave
save 900 1
# 300秒内,如果至少有10个key被修改,则执行bgsave
save 300 10
# 60秒内,如果至少有10000个key被修改,则执行bgsave
save 60 10000
# 禁用RGB
save ""
RDB的其它配置也可以在redis.conf文件中设置:
# 是否压缩,建议不开启(默认开启),压缩会消耗cpu资源,cpu资源相比磁盘资源来说更为珍贵
rdbcompression yes
# RDB文件名称(可修改)
dbfilename dump.rdb
# 文件保存的路径目录
dir ./
bgsave在开始时会fork主进程得到子进程,子进程共享主进程的内存数据。完成fork后读取内存数据并写入RDB文件。fork采用的是copy-on-write技术:
其基本流程(RDB方式)是:
RDB存在的缺点有:
有什么别的持久化能弥补RDB的缺陷呢?
Append Only File(追加文件)。Redis处理的每一个写命令都会记录在AOF文件,可以看作时命令日志文件。
AOF默认是关闭的,需要手动去修改redis.conf配置文件来开启AOF。
# 是否开启AOF功能,默认为no
appendonly yes
# AOF文件的名称(可修改)
appendfilename "appendonly.aof"
也可以通过修改redis.conf文件来调节AOF的命令记录频率
# 标识每执行一次写命令,立即记录到AOF文件
appendfsync always
# 写命令执行完先放入AOF缓冲区,然后表示每隔1秒讲缓冲区数据写到AOF文件(默认方案)
appendfsync everysec
# 写命令执行完先放入AOF缓冲区,由操作系统决定何时将缓冲区内容写回磁盘
appendfsync no
配置项 | 刷盘时机 | 优点 | 缺点 |
---|---|---|---|
always | 同步刷盘 | 可靠性高,几乎不丢数据 | 性能影响大 |
everysec | 每秒刷盘 | 性能适中 | 最多丢失一秒数据 |
no | 操作系统控制 | 性能最好 | 可靠性较差,可能丢失大量数据 |
因为AOF是靠记录命令来达到恢复数据的效果,所以AOF文件会比RDB文件大的多。而且AOF会记录对同一个key的多次写操作,但是只有最后一次写操作才有意义。为了解决这个问题,AOF提供了一个办法,通过执行bgrewriteaof
命令,可以让AOF文件执行重写功能,用最少的命令达到同样的效果。
Redis会在触发阈值时自动重写AOF文件。阈值也可以在redis.conf中进行配置
# AOF文件比上次文件的大小增长超过百分之多少时则会触发重写
auto-aof-rewrite-percentage 100
# AOF文件体积超过多大以上才触发重写
auto-aof-rewrite-min-size 64mb
RDB与AOF各自都有优缺点,在实际开发中往往会结合两者一起使用。
RDB | AOF | |
---|---|---|
持久化方式 | 定时对整个内存做快照 | 记录每一次执行的命令 |
数据完整性 | 不完整,两次备份之间会有数据丢失 | 相对完整(取决于刷盘策略) |
文件大小 | 文件体积小(有压缩) | 文件体积大(记录命令) |
宕机恢复速度 | 快 | 慢 |
数据恢复优先级 | 低(数据完整性不如AOF) | 高(数据完整性更高) |
系统资源占用 | 高(大量的CPU和内存消耗) | 低(主要是磁盘的IO资源,但在AOF重写时会占用大量的CPU和内存资源) |
使用场景 | 追求更快的启动速度(可以容忍几分钟的数据丢失) | 对数据安全性要求较高的 |
单节点Redis的并发能力有上限,要进一步提高Redis的并发能力,就需要搭建主从集群,实现读写分离。
如下图为Redis主从集群的基本结构
集群中至少会包含三个节点,一个主节点,两个从节点,从而实现读写分离。
在同一台虚拟机中开启三个实例,就必须准备三份不同的配置文件和目录,配置文件所在的目录即为工作目录。
1、创建目录
创建三个文件夹,名字分别为5206、7749、8391(名字随意就好)
# 进入tmp目录
cd /tmp
# 创建目录
mkdir 5206 7749 8391
2、恢复原始配置
如果修改了redis.conf文件中的配置(没有则跳过此步骤),需要将起其中的持久化模式改为默认的RDB模式,并保持AOF为关闭状态。
# 开启RDB
# save ""(如果添加了此命令,将其注释掉)
# redis.conf文件中下面三行是默认注释,默认是保持开启状态的
save 3600 1
save 300 100
save 60 10000
# 关闭AOF
appendonly no
3、拷贝配置文件至每个实例目录
将redis.conf文件拷贝至刚才创建好的三个目录中(在/tmp目录下执行):
# 方式一、逐个拷贝
cp redis/redis.conf 5206
cp redis/redis.conf 7749
cp redis/redis.conf 8391
# 方式二、管道组合命令,一键拷贝
echo 5206 7749 8391 | xargs -t -n 1 cp /usr/local/redis/redis.conf
注意
:redis.conf所在文件的根目录为自己redis所在的工作目录(一般为解压目录)。
4、修改每个实例的端口、工作目录
修改每个文件夹中的配置文件,将端口分别改成对应的5206、7749、8391,并将RDB文件保存位置也都修改成文件所在目录(在/tmp目录下执行)。
sed -i -e 's/6379/5206/g' -e 's/dir .\//dir \/tmp\/5206\//g' 5206/redis.conf
sed -i -e 's/6379/7749/g' -e 's/dir .\//dir \/tmp\/7749\//g' 7749/redis.conf
sed -i -e 's/6379/8391/g' -e 's/dir .\//dir \/tmp\/8391\//g' 8391/redis.conf
5、修改每个实例的声明IP
虚拟机本身有多个IP,为了避免将来混乱,我们需要在redis.conf文件中指定每一个实例的绑定ip信息,格式如下:
# redis实例的声明IP
replica-announce-ip 192.168.xxx.xxx
每个目录都需要修改,下面给出命令,一键完成修改(在工作目录下执行,修改的ip为自己虚拟机的ip)
# 逐一执行
sed -i '1a replica-announce-ip 192.168.xxx.xxx' 5206/redis.conf
sed -i '1a replica-announce-ip 192.168.xxx.xxx' 7749/redis.conf
sed -i '1a replica-announce-ip 192.168.xxx.xxx' 8391/redis.conf
# 或者一键修改
printf '%s\n' 5206 7749 8391 | xargs -I{} -t sed -i '1a replica-announce-ip 192.168.xxx.xxx' {}/redis.conf
6、启动redis
修改完成之后,开启三个redis实例(在/tmp目录下执行)
# 逐个执行
redis-server 5206/redis.conf
redis-server 7749/redis.conf
redis-server 8391/redis.conf
执行完成后,使用ps -ef | grep redis
命令来查看redis服务状态。
三个实例已经成功启动了。
三个实例已经启动,但是它们之间并没有任何关系,可以使用replicaof或者slaveof( redis5.0以前 )命令。
有临时和永久两种模式:
slaveof
slaveof <masterip> <masterport>
注意:在 redis5.0 之后新增命令replicaof与salveof效果是一样的。
这里使用临时的模式来进行演示
# 在5206的实例中
redis-cli -p 5206 # 连接5206实例
slaveof 192.168.xxx.xxx 6379 # 这里的ip为主机的ip(以6379为例)
# 在7749的实例中
redis-cli -p 7749 # 连接7749实例
slaveof 192.168.xxx.xxx 6379 # 这里的ip为主机的ip(以6379为例)
# 在8391的实例中
redis-cli -p 8391 # 连接8391实例
slaveof 192.168.xxx.xxx 6379 # 这里的ip为主机的ip(以6379为例)
执行完成之后,在6379的Redis主机上输入
# 进入6379主机(已进入则忽略)
redis-cli # 或者redis-cli -p 6379
# 查看主从关系及其状态
info replication
看到如下状态,说明主从关系已经开启
此时可以进行一些读写测试一下主从节点之间是否是数据同步的。
注意
:在主节点上可以实现读写操作,而在从节点上只能进行读操作,无法进行写操作。(说明已经实现了读写分离)
主从第一次同步为全量同步(只有第一次同步,此次同步非常消耗资源)。
那么,master是怎么判断slave是否是第一次来同步数据呢?
通过对比replid与offset来实现
Replication Id
:简称replid,是数据集的标记,id一致说明是同一数据集。每一个master都有唯一的replid,slaveo则会继承master节点的replidoffset
:偏移量,随着记录在repl_baklog中的数据增多而逐渐增大。slave完成同步时也会记录当前同步的offset。如果slave的offset小于master的offset,说明slave数据落后于master,则需要更新。因此slave在做数据同步的时候,必须向master声明自己的replication id和offset,master才能判断到底需要同步哪些数据。
具体的流程为:
当slave重启后进行的同步为增量同步
repl_baklog类似于一个环形数组,master记录的数据会跟着环形移动,slave也会跟着数据进行同步,当环中数据绕满一圈时,master继续存储数据,则会覆盖掉上一圈的数据,只要slave紧跟master同步,就不会出现数据无法同步的状态(左边的圆圈)。一旦slave断开时间过久,导致尚未备份的数据被覆盖,则无法基于log做增量同步,此时只能再次做全量同步(右边圆圈)。
针对上面的问题,可以从以下几点来优化Redis主从集群:
全量同步与增量同步的区别:
全量同步与增量同步执行的时机:
从上面可以得知,如果slave宕机了,只要及时恢复,则依然可以正常工作。那如果master突然宕机了呢?要知道,在master宕机的过程中,用户就无法进行写的操作,体验性就会降低,所以就需要在slave中及时选出一个节点充当master,当之前的master恢复之后,将其变为slave即可。这样实现主从切换可以在用户毫无感觉的情况下完成,不会影响到用户体验。实现这个过程,就需要提到Redis的哨兵机制。
Redis提供的哨兵( Sentinel )机制来实现主从集群的自动故障恢复。哨兵的作用:
Sentinel基于心跳机制检测服务状态,每隔一秒向集群的每个实例发送ping命令:
当Sentinel一旦发现master发生故障,需要按照以下选择依据从slave中选择一个作为新的master:
当选中其中一个slave作为新的master后(如slave1),故障转移步骤为:
跟创建Redis集群一样,这里创建的哨兵集群依然是以三个为一组。
在同一台虚拟机上开启三个实例,创建三个文件夹,名字为s1、s2、s3。
# 进入/tmp目录
cd /tmp
# 创建目录
mkdir s1 s2 s3
然后在s1,目录下创建一个sentinel.conf文件,并添加以下内容
# 当前Sentinel实例的端口
port 7001
# 指定当前实例ip地址(避免混乱)
sentinel announce-ip 192.168.xxx.xxx
# 指定主节点信息(mymaster为现在主节点的别名,ip为现在主节点的ip,2为选举master时的quorum值)
sentinel monitor mymaster 192.168.xxx.xxx 6379 2
# slave与master断开最长超时时间
sentinel down-after-milliseconds mymaster 5000
# slave故障恢复的超时时间
sentinel failover-timeout mymaster 60000
# 工作目录
dir "/tmp/s1"
将这个sentinel.conf文件拷贝到s2、s3两个目录中(在/tmp吗目录下执行命令)
# 方式一:逐个拷贝
cp s1/sentinel.conf s2
cp s1/sentinel.conf s2
# 方式二:管道组合命令,一键拷贝
echo s2 s3 | xargs -t -n 1 cp s1/sentinel.conf
修改s2、s3两个文件夹内的配置文件,将端口分别修改为7002、7003(在/tmp目录下执行命令)
sed -i -e 's/7001/7002/g' -e 's/s1/s2/g' s2/sentinel.conf
sed -i -e 's/7001/7003/g' -e 's/s1/s3/g' s3/sentinel.conf
修改完成后,启动这三个实例(在/tmp目录下执行命令)
# 第一个
redis-sentinel s1/sentinel.conf
# 第二个
redis-sentinel s2/sentinel.conf
# 第三个
redis-sentinel s3/sentinel.conf
启动成功之后,其他的事情就全部交给Sentinel自动完成即可。
在Sentinel集群监管下的Redis主从集群,其节点会因为自动故障转移而发生变化,Redis的客户端必须感知这种变化,及时更新连接信息。Spring的RedisTemplate底层利用lettuce实现 了节点的感知和自动切换。
1、在pom.xml中引入redis的starter依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
2、在配置文件application.yml中指定sentinel相关信息
spring:
redis:
sentinel:
master: mymaster # 指定master名称
nodes: # 指定redis-sentinel集群信息
- 192.168.xxx.xxx:7001
- 192.168.xxx.xxx:7002
- 192.168.xxx.xxx:7003
3、配置读写分离
@Bean
public LettuceClientConfigurationBuilderCustomizer configurationBuilderCustomizer(){
return configBuilder -> configBuilder.readFrom(ReadFrom.REPLICA_PREFERRED);
}
ReadFrom是配置Redis的读取策略,是一个枚举,与以下选择:
MASTER
:从主节点读取。MASTER_PREFERRED
:优先从master节点读取,master不可用才读取replica。REPLICA
:从slave(replica)节点读取。REPLICA_PREFERRED
:优先从slave(replica)节点读取,所有slave都不可用才读取master。到此,关于RedisTemplate的哨兵模式已经配置完毕,剩余的事情就交给Sentinel自动完成。
主从架构与哨兵机制虽然可以解决高可用、读的高并发问题,但依然还有两个问题没有解决:
解决以上问题,可以使用分片集群。因为分片集群具有以下特征:
分片集群需要的节点较多,这里使用三主三从的方式搭建一个最小型的分片集群。
在一台虚拟机上开启6个redis实例,模拟分片集群,信息如下:
IP | port | 角色 |
---|---|---|
192.168.xxx.xxx | 5206 | master |
192.168.xxx.xxx | 7749 | master |
192.168.xxx.xxx | 8391 | master |
192.168.xxx.xxx | 7001 | slave |
192.168.xxx.xxx | 7002 | slave |
192.168.xxx.xxx | 7003 | slave |
删掉之前搭建主从集群的目录,避免一些配置产生干扰
# 进入/tmp目录
cd /tmp
# 删除旧的目录
rm -rf 5206 7749 8391
# 创建目录
mkdir 5206 7749 8391 8001 8002 8003
准备一份新的redis.conf文件
port 6379
# 开启集群功能
cluster-enabled yes
# 集群的配置文件名称,不需要我们创建,由redis自己维护
cluster-config-file /tmp/6379/nodes.conf
# 节点心跳失败的超时时间
cluster-node-timeout 5000
# 持久化文件存放目录
dir /tmp/6379
# 绑定地址
bind 0.0.0.0
# 让redis后台运行
daemonize yes
# 注册的实例ip(虚拟机ip)
replica-announce-ip 192.168.xxx.xxx
# 保护模式
protected-mode no
# 数据库数量
databases 1
# 日志
logfile /tmp/6379/run.log
将这个配置文件拷贝至每个目录下
# 进入/tmp目录
cd /tmp
# 执行拷贝
echo 5206 7749 8391 8001 8002 8003 | xargs -t -n 1 cp redis.conf
修改每个目录下的redis.conf,将其中的6379修改成对应目录名为端口。
# 进入/tmp目录
cd /tmp
# 修改配置文件
printf '%s\n' 5206 7749 8391 8001 8002 8003 | xargs -I{} -t sed -i 's/6379/{}/g' {}/redis.conf
因为配置了后台启动模式,所以可以直接一键启动服务
# 进入/tmp目录
cd /tmp
# 一键启动所有服务
printf '%s\n' 5206 7749 8391 8001 8002 8003 | xargs -I{} -t redis-server {}/redis.conf
启动之后,可以查看服务是否真的已启动
ps -ef | grep redis
如果需要关闭所有进程,则可以执行以下命令(推荐使用第二种,当第二种无法正常结束进程时使用第一种)
# 第一种
ps -ef | grep redis | awk '{print $2}' | xargs kill
# 第二种
printf '%s\n' 5206 7749 8391 8001 8002 8003 | xargs -I{} -t redis-cli -p {} shutdown
服务已经全部启动,但是目前所有服务之间都是独立的,没有任何联系。
需要执行命令来创建集群(Redis5.0之前创建集群比较麻烦,但在5.0之后集群管理命令都集成到redis-cli中)。
5.0之前
Redis5.0之前集群命令都是用redis安装包下的src/redis-trib.rb来实现的。因为redis-trib.rb是用ruby语言编写的所有需要安装ruby环境
# 安装依赖
yum -y install zlib ruby rubygems
gem install redis
然后通过命令来管理集群
# 进入redia的src目录
cd /tmp/redis-6.2.4/src
# 创建集群
./redis-trib.rb create --replicas 1 192.168.xxx.xxx:5206 192.168.xxx.xxx:7749 192.168.xxx.xxx:8391 192.168.xxx.xxx:8001 192.168.xxx.xxx:8002 192.168.xxx.xxx:8003
5.0以后
集群管理已经集成到了redis-cli中
redis-cli --cluster create --cluster-replicas 1 192.168.xxx.xxx:5206 192.168.xxx.xxx:7749 192.168.xxx.xxx:8391 192.168.xxx.xxx:8001 192.168.xxx.xxx:8002 192.168.xxx.xxx:8003
输入命令之后,可以发现,redis将前三个节点作为了master,后三个作为了slave
可以通过命令来查看集群中的状态
redis-cli -p 5206 cluster nodes
这里如果需要打开redis命令行,可以执行以下命令
# 进入/tmp目录
cd /tmp
# 打开5206端口的redis(这里的-c就是-cluster,为集群模式)
redis-cli -c -p 5206
# 打开7749端口的redis(这里的-c就是-cluster,为集群模式)
redis-cli -c -p 7749
Redis会将每一个master节点映射到0~16383共16384个插槽(hash slot)上,通过查看集群信息是就可以看到
数据key不是与节点绑定,而是与插槽绑定。redis会根据key的有效部分计算插槽值,分两种情况:
例如: key是num,那么就根据num计算,如果是{xxx}num,则根据xxx计算。计算方式是利用CRC16算法得到一
个hash值,然后对16384取余,得到的结果就是slot值。
那么Redis是如何判断某个key应该在哪个实例上的呢?
如果将同一类数据固定存储在同一个Redis实例,应该怎么办?
redis-cli --cluster 提供了很多操作集群的命令,可以通过下面方式查看:
redis-cli --cluster help
如下图为添加节点的命令
重新分配插槽的命令
如果集群中有一个master宕机了会发生什么?
这里可以根据watch命令来实时监控集群中的变化过程(可以自行尝试手动停止master节点来模拟宕机情况)
# 进入/tmp
cd /tmp
# watch集群
watch redis-cli -p 5206 cluster nodes
利用cluster failover命令可以手动让集群中的某个master宕机,切换到执行cluster failover命令的这个slave节点,实现无感知的数据迁移。其流程如下:
手动的Failover支持三种不同模式(默认推荐使用缺省):
RedisTemplate底层同样基于lettuce实现了分片集群的支持,而且使用步骤也与哨兵模式基本一致:
与哨兵模式相比,只有分片集群的配置方式略有差异
spring:
redis:
cluster:
nodes: # 指定集群中的每一个节点信息
- 192.168.xxx.xxx:5206
- 192.168.xxx.xxx:7749
- 192.168.xxx.xxx:8391
- 192.168.xxx.xxx:8001
- 192.168.xxx.xxx:8002
- 192.168.xxx.xxx:8003