分布式缓存
缓存的基本作用是在高并发场景下对应服务的保护缓冲
-- 基于Redis集群解决单机Redis存在的问题
单机的Redis存在四大问题:
- redis由于高强度性能采用内存 但是意味着丢失的风险
- 单结点redis并发能力有限
- 分布式服务中数据过多 依赖内存的redis 明显单机不能满足
- 如果发生故障 需要能继续服务
很明显上述效果需要集群才能实现
解决方案
1.Redis持久化
Redis有两种持久化方案:
- RDB持久化
- AOF持久化
1.1.RDB持久化
RDB全称Redis Database Backup file(Redis数据备份文件),也被叫做Redis数据快照。简单来说就是把内存中的所有数据都记录到磁盘中。当Redis实例故障重启后,从磁盘读取快照文件,恢复数据。快照文件称为RDB文件,默认是保存在当前运行目录。
1.1.1.执行时机
RDB持久化在四种情况下会执行:
- 执行save命令
- 执行bgsave命令
- Redis停机时
- 触发RDB条件时
1)save命令
执行save的命令,可以立即执行一次RDB:
redis是单线程,所以这里会耗时较久
save命令会导致主进程执行RDB,这个过程中其它所有命令都会被阻塞。只有在数据迁移时可能用到。
2)bgsave命令
下面的命令可以异步执行RDB(background save):
这个命令执行后会开启独立进程完成RDB,主进程可以持续处理用户请求,不受影响。(异步执行持久化)
3)停机时
Redis停机时会执行一次save命令,实现RDB持久化。
比如先启动redis
然后停机
发现redis 输出log 数据保存到磁盘, 以及保存到磁盘上(一般是运行目录无论windos还是linux)
再次启动
之前保存的数据被读取 实现redis的持久化
日志输出数据从磁盘读取
4)触发RDB条件
Redis内部有触发RDB的机制,可以在redis.conf文件中找到,格式如下:
properties
# 900秒内,如果至少有1个key被修改,则执行bgsave , 如果是save "" 则表示禁用RDB
save 900 1
save 300 10
save 60 10000
RDB的其它配置也可以在redis.conf文件(windos系统是redis.windos.conf)中设置:
redis
默认持久化策略
properties
# 是否压缩 ,建议不开启,压缩也会消耗cpu,磁盘的话不值钱
rdbcompression yes
# RDB文件名称
dbfilename dump.rdb
# 文件保存的路径目录
dir ./
这里为了测试把触发持久化事件改短一点
5秒内发生修改就持久化
重启后 设置一个数据
日志输出
后台备份成功
设置备份时间的间隔应该考虑业务中数据量的大小和耗时
1.1.2.RDB原理
bgsave开始时会fork主进程得到子进程,子进程共享主进程的内存数据。完成fork后读取内存数据并写入 RDB 文件。
fork采用的是copy-on-write技术:
- 当主进程执行读操作时,访问共享内存;
- 当主进程执行写操作时,则会拷贝一份数据,执行写操作。
所以如果在分布式业务中,有一台服务器是专门用于做缓存,那么的为了redis的备份机制正常运行,应该预留一些内存给子进程fork
1.1.3.小结
RDB方式bgsave的基本流程
- fork主进程得到一个子进程,共享内存空间
- 子进程读取内存数据并写入新的RDB文件
- 用新RDB文件替换旧的RDB文件
RDB会在什么时候执行
- 默认是服务停止时
- 可以通通过配置环境修改频率
RDB的缺点
- RDB执行间隔时间长(如果修改配置备份时间变短,会大致大量资源消耗,耗时压力更大),两次RDB之间写入数据有丢失的风险
- fork子进程、压缩、写出RDB文件都比较耗时
1.2.AOF持久化
1.2.1.AOF原理
AOF全称为Append Only File(追加文件)。Redis处理的每一个写命令都会记录在AOF文件,因为记录的不是完整的数据,而是数据的操作命令 可以看做是命令日志文件。
当redis 宕机 ,redis重启后 读取aof文件,执行指令来恢复数据
1.2.2.AOF配置
AOF默认是关闭的,需要修改redis.conf配置文件来开启AOF:
properties
# 是否开启AOF功能,默认是no
appendonly yes
# AOF文件的名称
appendfilename "appendonly.aof"
选择执行AOF前先将rdb 策略进行注释
开启aof策略
重启redis
log rdb 没有读取 说明确实禁止了
然后写几个存储
此时aof 文件出现当前目录
打开查看 确实记录的是命令
关闭服务且从重启
关闭时候同步到本地日志
读取数据 成功 并没丢失
AOF的命令记录的频率也可以通过redis.conf文件来配:
properties
# 表示每执行一次写命令,立即记录到AOF文件 redis是单线程业务 这样的同步需求 对性能消耗较大
appendfsync always
# 写命令执行完先放入AOF缓冲区,然后表示每隔1秒将缓冲区数据写到AOF文件,是默认方案
appendfsync everysec
# 写命令执行完先放入AOF缓冲区,由操作系统决定何时将缓冲区内容写回磁盘
appendfsync no
三种策略对比:
1.2.3.AOF文件重写
因为是记录命令,AOF文件会比RDB文件大的多。而且AOF会记录对同一个key的多次写操作,但只有最后一次写操作才有意义。通过执行bgrewriteaof命令,可以让AOF文件执行重写功能,用最少的命令达到相同效果。
如图,AOF原本有三个命令,但是set num 123 和 set num 666
都是对num的操作,第二次会覆盖第一次的值,因此第一个命令记录下来没有意义。
所以重写命令后,AOF文件内容就是:mset name jack num 666
在最后一次写入操作后执行
Redis也会在触发阈值时自动去重写AOF文件。阈值也可以在redis.conf中配置:
properties
# AOF文件比上次文件 增长超过多少百分比则触发重写
auto-aof-rewrite-percentage 100
# AOF文件体积最小多大以上才触发重写
auto-aof-rewrite-min-size 64mb
1.3.RDB与AOF对比
RDB和AOF各有自己的优缺点,如果对数据安全性要求较高,在实际开发中往往会结合两者来使用。
2.Redis主从
2.1.搭建主从架构
单节点Redis的并发能力是有上限的,要进一步提高Redis的并发能力,就需要搭建主从集群(之前的常用集群模式是负载均衡),实现读写分离。
Redis集群和主从复制是两种不同的Redis部署模式,它们各自解决不同的问题和提供不同的功能。以下是它们的主要区别和用途:
-
Redis主从复制(Replication):
-
用途: 主从复制的主要目的是提高数据的可用性和可靠性。在主从复制中,一个Redis主节点(master)会将数据复制给一个或多个从节点(slaves)。
-
数据复制: 主节点负责写操作,从节点复制主节点的数据。这有助于实现数据备份和故障恢复。如果主节点出现故障,可以快速切换到从节点来提供服务,从而降低了系统的单点故障风险。
-
性能: 主从复制通常不旨在提高性能,因为主节点仍然处理所有的写操作,而从节点主要用于读取和故障切换。
Redis集群(Cluster):
- 用途: Redis集群的主要目的是横向扩展Redis以处理大规模的数据和请求。它是一种分布式系统,用于提高性能、负载均衡和高可用性。
7.数据分片: Redis集群将数据分为多个分片,每个分片可以在集群中的不同节点上。这有助于平衡负载和提高性能,因为请求可以同时分布到多个节点上。 - 高可用性: Redis集群也提供了高可用性,因为它可以自动管理节点故障,并在需要时重新分配分片。
为什么不采用负载均衡来搭建Redis集群呢?
负载均衡和Redis集群的目标不同: 负载均衡通常用于分发流量到多个相同的后端服务器,以提高性能和可用性。但Redis集群的目标更广泛,包括数据分片、高可用性、负载均衡等,它们是分布式系统的一部分。
数据一致性和分片: 在负载均衡中,通常不会考虑数据一致性,因为它主要关注请求的分发。然而,Redis集群需要确保数据的一致性和可用性,因此它使用数据分片和复制来实现这一目标。性能和可用性需求: 如果您需要横向扩展Redis以处理大量请求并提供高可用性,那么Redis集群是更合适的选择。负载均衡通常在单一后端服务或多个相同的后端服务之间分发请求,但它不提供数据的分片和自动故障切换。
综上所述,Redis主从复制和Redis集群是针对不同需求的两种不同部署方式。您应根据您的具体需求和目标来选择适当的部署方式。如果需要横向扩展和高可用性,Redis集群可能更适合您。如果只需备份和故障切换,主从复制可能足够。负载均衡通常在应用层用于分发请求到多个Redis节点,但它通常不提供数据分片和高可用性功能。
这里采用docker实现采用
Docker 部署主从redis集群
创建一个目录作为演示集群的文件目录,其中创建三个7001,7002,7003表示redis的三台实列目录,端口和目录名字对应
进行各个子目录创建对应数据卷挂载目录
创建一个data目录 和re创建一个redis.config
配置文件的内容就按照自己从默认reidis中按照需求写更改
然后运行docker 进行挂载
挂载数据券和配置文件
bash
docker run -d -p 7001:6379 -v /home/hadoop/Redis-cluster/7001/redis-data-7001/:/data
-v /home/hadoop/Rediscluster/7001/redis.config:/data/redis.conf --name redis7001 redis redis-server /data/redis.conf
进入容器
java
docker exec -it redis7001 redis-cli
根据配置文件的密码登录后创建数据,在进行重启
关闭服务
bash
docker stop redis7001
日志和之前一样
重启
bash
docker start redis7001
日志输出
读取数据 发现可以做到数据持久化
按照同样步骤在启动俩台redis容器
成功启动三台实列
为了实现主从同步 ,将aof模式关闭,开始RDB配置
bash
# 开启RDB
# save ""
save 3600 1
save 300 100
save 60 10000
# 关闭AOF
appendonly no
集群配置
在使用Docker搭建Redis集群时,Redis官方提供了一个叫做Redis集群模式的特性。使用集群模式,Redis实例可以在不修改配置文件的情况下组成集群。Redis集群模式是通过Redis的内建集群支持实现的,不需要手动修改配置文件。
在Redis集群中,每个节点都可以是主节点也可以是从节点。节点之间通过Gossip协议进行通信。在Docker中,你可以简单地通过运行多个Redis容器,并在它们之间配置集群。
以下是在Docker中使用集群模式配置Redis集群的步骤:
-
创建Redis集群网络:
首先,创建一个Docker网络,以便在Redis容器之间进行通信。你可以使用以下命令创建一个名为redis-net的网络:
docker network create redis-net
-
启动Redis容器:
运行多个Redis容器,并将它们加入到redis-net网络中。在这里,你只需要指定容器的名称、网络、端口映射等信息。Docker会为每个容器自动分配一个唯一的Container ID和IP地址。
bash
docker run -d --name redis-node1 --net redis-net -p 7001:6379 redis
docker run -d --name redis-node2 --net redis-net -p 7002:6379 redis
docker run -d --name redis-node3 --net redis-net -p 7003:6379 redis
# ...添加更多的Redis节点
- 配置集群:
连接到任意一个Redis容器中,并使用redis-cli命令配置集群。在下面的命令中,你需要指定所有的Redis节点地址及端口,以及--cluster-replicas 1参数表示每个主节点有一个从节点:
bash
docker exec -it redis-node1 redis-cli --cluster create \
172.18.0.2:6379 172.18.0.3:6379 172.18.0.4:6379 \
172.18.0.5:6379 172.18.0.6:6379 172.18.0.7:6379 \
--cluster-replicas 1
上述命令中,172.18.0.2至172.18.0.7是各个Redis容器的IP地址。运行这个命令后,Redis集群就会自动配置好,每个节点都知道其他节点的信息。
- 验证集群配置:
你可以使用以下命令验证Redis集群的配置是否成功:
bash
docker exec -it redis-node1 redis-cli cluster nodes
这将显示Redis集群的节点信息。如果所有节点都显示正常,那么你的Redis集群就已经配置成功了,这里主要演示主从结果,所以不做演示
请注意,这种配置方式是基于Docker内建的集群支持实现的,不需要手动修改配置文件。但在生产环境中,你可能需要进一步配置安全性、持久性和高可用性等方面的参数,以确保集群的稳定性和安全性。
2.2.主从数据同步原理
2.2.1.全量同步
主从第一次建立连接时,会执行全量同步,将master节点的所有数据都拷贝给slave节点,流程:
这里有一个问题,master如何得知salve是第一次来连接呢??
有几个概念,可以作为判断依据:
- Replication Id:简称replid,是数据集的标记,id一致则说明是同一数据集。每一个master都有唯一的replid,slave则会继承master节点的replid,从节点第一次申请同步id不同,所以会接收到 主节点的数据,后续操作进行通信时,id相等,说明以及是同步
- offset:偏移量,随着记录在repl_baklog中的数据增多而逐渐增大。slave完成同步时也会记录当前同步的offset。如果slave的offset小于master的offset,说明slave数据落后于master,需要更新。
因此slave做数据同步,必须向master声明自己的replication id 和offset,master才可以判断到底需要同步哪些数据。
因为slave原本也是一个master,有自己的replid和offset,当第一次变成slave,与master建立连接时,发送的replid和offset是自己的replid和offset。
master判断发现slave发送来的replid与自己的不一致,说明这是一个全新的slave,就知道要做全量同步了。
master会将自己的replid和offset都发送给这个slave,slave保存这些信息。以后slave的replid就与master一致了。
因此,master判断一个节点是否是第一次同步的依据,就是看replid是否一致。
如图:
完整流程描述:
- slave节点请求增量同步
- master节点判断replid,发现不一致,拒绝增量同步
- master将完整内存数据生成RDB,发送RDB到slave
- slave清空本地数据,加载master的RDB
- master将RDB期间的命令记录在repl_baklog,并持续将log中的命令发送给slave
- slave执行接收到的命令,保持与master之间的同步
2.2.2.增量同步
全量同步需要先做RDB,然后将RDB文件通过网络传输个slave,成本太高了,资源消耗过多。因此除了第一次做全量同步,其它大多数时候slave与master都是做增量同步。
什么是增量同步?就是只更新slave与master存在差异的部分数据(主节点数据更新,从结点因为宕机或网络问题没有一直,只做这部分差异更新)。如图:
2.2.3.repl_backlog原理
master怎么知道slave与自己的数据差异在哪里呢?
这就要说到全量同步时的repl_baklog文件了。
这个文件是一个固定大小的数组,只不过数组是环形,也就是说角标到达数组末尾后,会再次从0开始读写,这样数组头部的数据就会被覆盖。
repl_baklog中会记录Redis处理过的命令日志及offset,包括master当前的offset,和slave已经拷贝到的offset:
slave与master的offset之间的差异,就是salve需要增量拷贝的数据了。
随着不断有数据写入,master的offset逐渐变大,slave也不断的拷贝,追赶master的offset:
直到数组被填满:
此时,如果有新的数据写入,就会覆盖数组中的旧数据。不过,旧的数据只要是绿色的,说明是已经被同步到slave的数据,即便被覆盖了也没什么影响。因为未同步的仅仅是红色部分。
但是,如果slave出现网络阻塞,导致master的offset远远超过了slave的offset:
如果master继续写入新数据,其offset就会覆盖旧的数据,直到将slave现在的offset也覆盖:
棕色框中的红色部分,就是尚未同步,但是却已经被覆盖的数据。此时如果slave恢复,需要同步,却发现自己的offset都没有了,无法完成增量同步了。只能做全量同步。
2.3.主从同步优化
主从同步可以保证主从数据的一致性,非常重要。
可以从以下几个方面来优化Redis主从就集群:
- 在master中配置repl-diskless-sync yes启用无磁盘复制,避免全量同步时的磁盘IO。(要求服务器网络高,而读取磁盘慢,不然会造擦网络服务堵塞)
- Redis单节点上的内存占用不要太大,减少RDB导致的过多磁盘IO
- 适当提高repl_baklog的大小,发现slave宕机时尽快实现故障恢复,尽可能避免全量同步
- 限制一个master上的slave节点数量,如果实在是太多slave,则可以采用主-从-从链式结构,减少master压力
主从从架构图:
主从关系实现
现在三个实例还没有任何关系,要配置主从可以使用replicaof 或者slaveof(5.0以前)命令。
有临时和永久两种模式:
-
修改从结点配置文件(永久生效)
官方配置文件默认注释
- 在redis.conf中添加一行配置:
slaveof <masterip> <masterport>
- 有密码的需要配置密码
- 在redis.conf中添加一行配置:
java
slaveof 192.168.249.132 7001
masterauth 222222
-
使用redis-cli客户端连接到redis服务,执行slaveof命令(重启后失效):
shslaveof <masterip> <masterport>
这里有密码 所以配置主从模式的信息 7002,7003配置
删除旧容器,按照配置文件在启动一遍
关闭任一容器查看日志 修改配置成功变回了redis默认的RDB
进入主节点容器7001 使用
bash
# 查看状态
info replication
发现只有master一个结点,这里需要配置
docker 容器进入同一网络
因为docker 容器是互相独立的是,很多时候网络不在一起无法链接删除三个容器,先配置在同一docker网络中
- 创建网络
bash
docker network create my_redis_network
2.运行时候指定docker网络 三个容器使用同一个网络
network=xxxx
改进后
bash
docker run -d -p 7001:6379 -v /home/hadoop/Redis-cluster/7001/redis-data-7001/:/data -v /home/hadoop/Redis-cluster/7001/redis.config:/data/redis.conf --network my_redis_network --name redis7001 redis redis-server /data/redis.conf
docker run -d -p 7002:7002 -v /home/hadoop/Redis-cluster/7002/redis-data-7002/:/data -v /home/hadoop/Redis-cluster/7002/redis.config:/data/redis.conf --network my_redis_network --name redis7002 redis redis-server /data/redis.conf
docker run -d -p 7003:6379 -v /home/hadoop/Redis-cluster/7003/redis-data-7003/:/data -v /home/hadoop/Redis-cluster/7003/redis.config:/data/redis.conf --network my_redis_network --name redis7003 redis redis-server /data/redis.conf
- 三个容器运行时候都拉入同一网络,查看从结点日志
这个时候不在拒绝而是successful ,进入主节点容器后查看结点状态 - docker exec -it redis7001 redis-cli进入容器
- 登录redis后查看状态
输入INFO
此时就可以看到完整的主从结构
主结点写一个数据
从结点可以读取出来
并且无法在进行写数据了
所以默认主从结点完成读写分离,从结点失去写入功能,从节点作为数据读取的副本
这里采用指令链接,但是后续搭配哨兵机制以后修改为配置文件链接,因为测试哨兵机制需要多次重启,并且由主从结构的日志截图可以看出,同一网络下运行的docker容器交互采用的是容器内的端口,为了防止后期哨兵集群部署后,结点数据量大,容器内端口冲突等,建议容器内端口修改为和映射宿主机端口一致
这样避免一些为网络问题
docker运行redis主从模式总结
1.首先配置创建对应的redis容器的数据卷和配置文件,方便挂载
数据备份采用默认的RDB
创建一个docker 网络 运行时候保证在同一网络
4.子实列配置文件写清楚主节点信息
bash
slaveof 主结点ip 端口
masterauth 密码
- docker 运行时指定network
2.4.小结
简述全量同步和增量同步区别?
- 全量同步:master将完整内存数据生成RDB,发送RDB到slave。后续命令则记录在repl_baklog,逐个发送给slave。
- 增量同步:slave提交自己的offset到master,master获取repl_baklog中从offset之后的命令给slave
什么时候执行全量同步?
- slave节点第一次连接master节点时
- slave节点断开时间太久,repl_baklog中的offset已经被覆盖时
什么时候执行增量同步?
- slave节点断开又恢复,并且在repl_baklog中能找到offset时
3.Redis哨兵
微服务中有专门的安全保护框架seatenl来对整个分布式进行保护,redis集群躲起来以后,同样的也内置了redis缓存集群监控保护机制
Redis提供了哨兵(Sentinel)机制来实现主从集群的自动故障恢复。
3.1.哨兵原理
3.1.1.集群结构和作用
哨兵的结构如图:
哨兵的作用如下:
- 监控:Sentinel 会不断检查您的master和slave是否按预期工作
- 自动故障恢复:如果master故障,Sentinel会将一个slave提升为master。当故障实例恢复后也以新的master为主
- 通知:Sentinel充当Redis客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给Redis的客户端
3.1.2.集群监控原理
Sentinel基于心跳机制监测服务状态,每隔1秒向集群的每个实例发送ping命令:
•主观下线:如果某sentinel节点发现某实例未在规定时间响应,则认为该实例主观下线。
•客观下线:若超过指定数量(quorum)的sentinel都认为该实例主观下线,则该实例客观下线。quorum值最好超过Sentinel实例数量的一半。
3.1.3.集群故障恢复原理
一旦发现master故障,sentinel需要在salve中选择一个作为新的master,选择依据是这样的:
- 首先会判断slave节点与master节点断开时间长短,如果超过指定值(down-after-milliseconds * 10)则会排除该slave节点
- 然后判断slave节点的slave-priority值,越小优先级越高,如果是0则永不参与选举
- 如果slave-prority一样,则判断slave节点的offset 值,越大(偏移量越大,主从数据越接近)说明数据越新,优先级越高
- 最后是判断slave节点的运行id大小,越小优先级越高。
当选出一个新的master后,该如何实现切换呢?
流程如下:
这里假设master7001出现故障
- sentinel给备选的slave1节点发送slaveof no one命令,让该节点成为master
- sentinel给所有其它slave发送slaveof 192.168.150.101 7002 命令,让这些slave成为新master的从节点,开始从新的master上同步数据。
- 最后,sentinel将故障节点标记为slave,当故障节点恢复后会自动成为新的master的slave节点
3.1.4.小结
Sentinel的三个作用是什么?
- 监控
- 故障转移
- 通知
Sentinel如何判断一个redis实例是否健康?
- 每隔1秒发送一次ping命令,如果超过一定时间没有相向则认为是主观下线(和nacos一样的心跳机制)
- 如果大多数sentinel都认为实例主观下线,则判定服务下线
故障转移步骤有哪些?
- 首先选定一个slave作为新的master,执行slaveof no one
- 然后让所有节点都执行slaveof 新master
- 修改故障节点配置,添加slaveof 新master
3.2.搭建哨兵集群 (docker实现)
依旧采用docker 实现,相比于直接部署服务器,docker 环境隔离,轻量的优点可太爽了
- 创建Docker网络:
首先确保所有的Redis实例和哨兵实例都在同一个网络中:
bash
docker network create redis-net #之前的redis 主从集群就已经运行到一个网络了
- 启动Redis容器:
您可以启动多个Redis容器实例。假设您启动了三个:
bash
docker run -d --name redis1 --net redis-net redis
docker run -d --name redis2 --net redis-net redis
docker run -d --name redis3 --net redis-net redis
这里之前就已经创建并且启动了三个容器
- 创建哨兵配置文件:
新建三个目录存放哨兵文件,以及后续的docker中哨兵工作目录的挂载
s1,s2,s3
创建一个基础的sentinel.conf文件:
bash
port 27001 #端口
sentinel monitor mymaster 192.168.249.132 7001 2
# 设置主服务器结点密码
sentinel auth-pass mymaster 222222
sentinel down-after-milliseconds mymaster 5000 #超时时间默认
sentinel failover-timeout mymaster 60000
解读:
port 27001
:是当前sentinel实例的端口sentinel monitor mymaster 192.168.249.132 7001 2
:指定主节点信息 (通过主节点就可以检测到从结点,所以redis主从架构中,主节点可以代表一个集群)mymaster
:主节点名称,自定义,任意写192.168.249.132 7001
:主节点的ip和端口2
:选举master时的quorum(这里三个哨兵 超过2就检查不到心跳ping就客观下下线)值- 如果不i是docker的话 工作目录实在这个配置文件中指定的目录
dir "/tmp/s1"
s1新建一个如上配置
- 把配置文件复制到其他哨兵工作目录:
然后修改端口,端口需要修改的,虽然docker 可以映射到宿主机的端口,但是docker网络内通信,使用的是docker 容器端口
- 启动哨兵容器:
启动前先查看docker 网络,要确定哨兵和容器能够在同一网络,确保容器通行
bash
docker network ls
就像之前的主从结构一样,docker容器之间通信需要保证同一网络,并且在通信端口是容器内端口
可以申明时定义同一网络 端口和ip
使用这两个参数后,从节点发送给主节点的ip和端口信息就是这里设定好了
。
实列在其他电脑,在Docker哨兵的配置中将sentinel announce-ip设置为远程计算机上Redis节点的IP地址。这样,Docker哨兵将正确地宣告Redis节点的位置给其他哨兵和Redis客户端,以便它们知道如何连接到Redis节点。
bash
replica-announce-ip 5.5.5.5
replica-announce-port 1234
哨兵的配置
bash
sentinel announce-ip <ip>
sentinel announce-port <port>
sentinel announce-ip :此选项允许您指定Redis Sentinel应向其他Sentinel实例宣告其监视的Redis实例的IP地址。您应该将其设置为Redis实例所在主机的IP地址。这在Redis实例的实际IP地址与其他Redis Sentinel实例和客户端应用于连接的IP地址不同时非常有用。例如,如果Redis运行在Docker容器中,您可能希望宣告主机机器的IP地址。
sentinel announce-port :此选项指定Redis Sentinel应向其他Sentinel实例宣告的Redis实例的端口。您应该将其设置为Redis实例实际监听的端口。
如果出现同一dockker网络下无法通信就要配置上面的试试
每个哨兵都需要有它自己的配置文件,并挂载到对应的工作目录:
bash
docker run -d --name sentinel1 --net my_redis_network -p 27001:27001 -v /home/hadoop/Redis-cluster/s1:/data -v /home/hadoop/Redis-cluster/s1/sentinel.conf:/etc/sentinel.conf redis redis-sentinel /etc/sentinel.conf
docker run -d --name sentinel2 --net my_redis_network -p 27002:27002 -v /home/hadoop/Redis-cluster/s2:/data -v /home/hadoop/Redis-cluster/s2/sentinel.conf:/etc/sentinel.conf redis redis-sentinel /etc/sentinel.conf
docker run -d --name sentinel3 --net my_redis_network -p 27003:27003 -v /home/hadoop/Redis-cluster/s3:/data -v /home/hadoop/Redis-cluster/s3/sentinel.conf:/etc/sentinel.conf redis redis-sentinel /etc/sentinel.conf
docker ps检查运行状态
现在,哨兵容器会使用s1,s2和s3这三个目录作为它们的工作目录(突然发现好多容器的工作目录都是/data 配置文件目录是/etc)。当哨兵进行某些操作时,您可以查看这些目录来观察它们的状态。
- 测试您的哨兵集群:
停止主节点,并观察哨兵是否将其他节点提升为新的主节点:
bash
docker stop redis7001
但是发现并没有切换主节点 并且发现日志
bash
Could not rename tmp config file (Device or resource busy)
WARNING: Sentinel was not able to save the new configuration on disk!!!: Device or resource busy
细节
因为Sentinel会在启动后向自己的配置文件中追加内容,它采用的是先创建一个临时配置文件,然后使用它替换掉原来的配置文件的方式。
如果是使用挂载卷直接挂载文件的方式,docker貌似不允许这样操作,所以会出现这个错误,你可以将配置文件放到单独的目录中,然后将目录挂载到容器。
所以三个哨兵目录创建config进行配置目录挂载,而不是文件
每个哨兵文件 新建配置目录config,并把之前的配置文件放进入
进入整个目录的挂载
bash
docker run -d --name sentinel1 --net my_redis_network -p 27001:27001 -v /home/hadoop/Redis-cluster/s1/data/:/data -v /home/hadoop/Redis-cluster/s1/config/:/etc/ redis redis-sentinel /etc/sentinel.conf
docker run -d --name sentinel2 --net my_redis_network -p 27002:27002 -v /home/hadoop/Redis-cluster/s2/data/:/data -v /home/hadoop/Redis-cluster/s2/config/:/etc/ redis redis-sentinel /etc/sentinel.conf
docker run -d --name sentinel3 --net my_redis_network -p 27003:27003 -v /home/hadoop/Redis-cluster/s3/data:/data -v /home/hadoop/Redis-cluster/s3/config/:/etc/ redis redis-sentinel /etc/sentinel.conf
此时查看任一哨兵日志
不在出现警告无法写入磁盘
并且进入任一哨兵容器
bash
docker exec -it 哨兵容器 redis-cli -p 哨兵端口
查看监视该集群的哨兵由那些,就能知道是否能通信了
bash
SENTINEL sentinels <master-name>
发现可以监控
日志哨兵日志
发现成功监控并且知道从主从信息已经同一监控
现在测试停止监控的redis集群 7001
输出哨兵日志,哨兵监控到结点服务异常
最重要的一条
发现选择哨兵选择了7003结点代替原来的主节点,进入7003容器
bash
docker exec -it redis7003 redis-cli -p 7003
输入info
此时7003成为主要结点,说明哨兵的功能起到作用,防止业务瘫痪,监视集群并且替换主节点
3.2.1 哨兵集群结构
3.3.RedisTemplate
在Sentinel集群监管下的Redis主从集群,其节点会因为自动故障转移而发生变化,Redis的客户端必须感知这种变化,及时更新连接信息。Spring的RedisTemplate底层利用lettuce实现了节点的感知和自动切换。
下面,我们通过一个测试来实现RedisTemplate集成哨兵机制。
假如一个一般springboot项目
主要就以下一个controller 通过restful接口 风格操作redis
bash
@RestController
public class HelloController {
@Autowired
private StringRedisTemplate redisTemplate;
@GetMapping("/get/{key}")
public String hi(@PathVariable String key) {
return redisTemplate.opsForValue().get(key);
}
// 接口操作数据库
@GetMapping("/set/{key}/{value}")
public String hi(@PathVariable String key, @PathVariable String value) {
redisTemplate.opsForValue().set(key, value);
return "success";
}
}
3.3.2.引入依赖
在项目的pom文件中引入依赖:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
3.3.3.配置Redis地址
然后在配置文件application.yml中指定redis的sentinel相关信息:
在sentinel 模式下,主从模式的集群有可能会随着业务变更,所以只需要配置监控者,就可以得到被监控的信息
java
spring:
redis:
sentinel:
master: mymaster
nodes:
- 192.168.249.132:27001
- 192.168.249.132:27002
- 192.168.249.132:27003
password: 222222
我redis密码集群吧都是222222
3.3.4.配置读写分离
在项目的启动类中,添加一个新的bean:
Lettuce 是实现redisTemplate客户端的底层实现框架
java
@Bean
public LettuceClientConfigurationBuilderCustomizer clientConfigurationBuilderCustomizer(){
//接口不能new 而只有一个方法明显是函数接口
return clientConfigurationBuilder -> clientConfigurationBuilder.readFrom(ReadFrom.REPLICA_PREFERRED);
}
主节点设置数据
测试游览器输入对应api 路径
游览器操作redis设置数据
因为controller 返回的
在redis 图形化库查看
说明resttemplate都能实现,并且日志也很详细
从集群中的7001端口结点读取
从7003端口的结点写入
这个时候手动挂机主节点看看日志'
哨兵是已经发先了,并且switch 7002作为新的主结点
而java客户端的日志是不断的发同步数据
最后确定新的主从结构
检查新的现象 。。。。详细信息
这个bean中配置的就是读写策略,包括四种:
- MASTER:从主节点读取
- MASTER_PREFERRED:优先从master节点读取,master不可用才读取replica
- REPLICA:从slave(replica)节点读取
- REPLICA _PREFERRED:优先从slave(replica)节点读取,所有的slave都不可用才读取master
4.Redis分片集群
4.1.搭建分片集群
主从和哨兵可以解决高可用、高并发读的问题。但是依然有两个问题没有解决:
-
海量数据存储问题
-
高并发写的问题
基本只要可以存储数据的分布式中间件都是采用这种方式,比如hadfs使用分片集群可以解决上述问题,如图:
分布式场景建议单结点的redis不应该给予太多内存资源,数据输入量大无论是RDB,AOF,数据备份占用的内存都是很高的,大量的io会给架构带来压力
分片集群特征:
-
集群中有多个master,每个master保存不同数据,保证存储大量数据
-
每个master都可以有多个slave节点,读写分离,扩大存储量的同时还不会降低读的能力
-
master之间通过ping监测彼此健康状态,这样就代表不在需要哨兵机制,集群间相互通信,自动检查集群内存状态
-
客户端请求可以访问集群任意节点,最终都会被转发到正确节点
4.1.0 docker实现redis分片集群
因为我的虚拟机内存大小优先
-
删除之前的7001、7002、7003这几个目录,重新创建出7001、7002、7003、8001、8002、8003目录:
每个文件的结构
-
新建一个配置文件 通用的 redis.conf
bash
#端口
port 7001
#2、修改bind或注释掉
bind 0.0.0.0
#3、保护模式关闭,因为用docker否则无法访问
protected-mode no
#4、关闭后台运行
daemonize no
#5、设置密码
#requirepass myredis
#6、配置与主节点验证的密码
#masterauth 设置密码
#7、开启aof日志
appendonly yes
#8、开启集群模式
cluster-enabled yes
#9、根据你启用的节点来命名,最好和端口保持一致,这个是用来保存其他节点的名称,状态等信息的
cluster-config-file nodes_7001.conf
#10、超时时间
cluster-node-timeout 5000
#11、集群节点 IP:服务器就填公网ip,或者内部对应容器的ip
cluster-announce-ip 192.168.249.132
#12、集群节点映射端口
cluster-announce-port 7001
#13、总线监控ip默认端口+10000 如 7001 就是 17001
cluster-announce-bus-port 17001
每个Redis集群中的节点都需要打开两个TCP连接。用于客户端提供服务,比如6379,还有一个额外的端口(通过在这个端口号上加10000)作为数据端口,例如:redis的端口为6379,那么另外一个需要开通的端口是:6379 + 10000, 即需要开启 16379。16379端口用于集群总线,这是一个用二进制协议的点对点通信信道。这个集群总线(Cluster bus)用于节点的失败侦测 ,如果不配置该项,很可能出现创建集群中一直waiting 线程堵塞
复制到每个文件夹,按照自己的需求进行更改
运行
bash
docker run -d -p 7001:7001 -p 17001:17001 -v /home/hadoop/Redis-cluster/7001/data/:/data/ -v /home/hadoop/Redis-cluster/7001/conf/redis.conf:/data/redis.conf --network my_redis_network --name redis7001 redis redis-server /data/redis.conf
docker run -d -p 7002:7002 -p 17002:17002 -v /home/hadoop/Redis-cluster/7002/data/:/data/ -v /home/hadoop/Redis-cluster/7002/conf/redis.conf:/data/redis.conf --network my_redis_network --name redis7002 redis redis-server /data/redis.conf
docker run -d -p 7003:7003 -p 17003:17003 -v /home/hadoop/Redis-cluster/7003/data/:/data/ -v /home/hadoop/Redis-cluster/7003/conf/redis.conf:/data/redis.conf --network my_redis_network --name redis7003 redis redis-server /data/redis.conf
docker run -d -p 8001:8001 -p 18001:18001 -v /home/hadoop/Redis-cluster/8001/data/:/data/ -v /home/hadoop/Redis-cluster/8001/conf/redis.conf:/data/redis.conf --network my_redis_network --name redis8001 redis redis-server /data/redis.conf
docker run -d -p 8002:8002 -p 18002:18002 -v /home/hadoop/Redis-cluster/8002/data/:/data/ -v /home/hadoop/Redis-cluster/8002/conf/redis.conf:/data/redis.conf --network my_redis_network --name redis8002 redis redis-server /data/redis.conf
docker run -d -p 8003:8003 -p 18003:18003 -v /home/hadoop/Redis-cluster/8003/data/:/data/ -v /home/hadoop/Redis-cluster/8003/conf/redis.conf:/data/redis.conf --network my_redis_network --name redis8003 redis redis-server /data/redis.conf
容器运行成功
)构建集群
我们使用的是Redis6.2.4版本,集群管理以及集成到了redis-cli中,格式如下:
sh
redis-cli --cluster create --cluster-replicas 1 192.168.249.132:7001 192.168.249.132:7002 192.168.249.132:7003 192.168.249.132:8001 192.168.249.132:8002 192.168.249.132:8003
redis-cli --cluster
或者./redis-trib.rb
:代表集群操作命令create
:代表是创建集群--replicas 1
或者--cluster-replicas 1
:指定集群中每个master的副本个数为1,此时节点总数 ÷ (replicas + 1)
得到的就是master的数量。因此节点列表中的前n个就是master,其它节点都是slave节点,随机分配到不同master
因为docker的部署的redis,redis-cli 在容器内 所以运行
bash
docker exec -it redis7001(任一结点) redis-cli --cluster create --cluster-replicas 1 192.168.249.132:7001 192.168.249.132:7002 192.168.249.132:7003 192.168.249.132:8001 192.168.249.132:8002 192.168.249.132:8003
结构输出
redis集群正在分配插槽
,然后询问你是否满意这样的分配
回复yes后开始创建集群
如果出现上述截图就是线程堵塞了,需要开启线程总线配置,并且除了redis端口还要自己暴露总线结点
修改后命令
bash
docker run -d -p 7001:7001 -p 17001:17001 -v /home/hadoop/Redis-cluster/7001/data/:/data/ -v /home/hadoop/Redis-cluster/7001/conf/redis.conf:/data/redis.conf --network my_redis_network --name redis7001 redis redis-server /data/redis.conf
docker run -d -p 7002:7002 -p 17002:17002 -v /home/hadoop/Redis-cluster/7002/data/:/data/ -v /home/hadoop/Redis-cluster/7002/conf/redis.conf:/data/redis.conf --network my_redis_network --name redis7002 redis redis-server /data/redis.conf
docker run -d -p 7003:7003 -p 17003:17003 -v /home/hadoop/Redis-cluster/7003/data/:/data/ -v /home/hadoop/Redis-cluster/7003/conf/redis.conf:/data/redis.conf --network my_redis_network --name redis7003 redis redis-server /data/redis.conf
docker run -d -p 8001:8001 -p 18001:18001 -v /home/hadoop/Redis-cluster/8001/data/:/data/ -v /home/hadoop/Redis-cluster/8001/conf/redis.conf:/data/redis.conf --network my_redis_network --name redis8001 redis redis-server /data/redis.conf
docker run -d -p 8002:8002 -p 18002:18002 -v /home/hadoop/Redis-cluster/8002/data/:/data/ -v /home/hadoop/Redis-cluster/8002/conf/redis.conf:/data/redis.conf --network my_redis_network --name redis8002 redis redis-server /data/redis.conf
docker run -d -p 8003:8003 -p 18003:18003 -v /home/hadoop/Redis-cluster/8003/data/:/data/ -v /home/hadoop/Redis-cluster/8003/conf/redis.conf:/data/redis.conf --network my_redis_network --name redis8003 redis redis-server /data/redis.conf
此时运行成功
测试链接任一主节点
集群数据操作时,需要给redis-cli
加上-c
参数才可以:
sh
redis-cli -c -p 7001
发现保存数据到该节点,最后却转发到另一个结点
集群中数据插入是根据集群算法来的,会根据转发到这个插槽的结点
redis集群中保存数据,会先对key进行计算,然后保存到计算放置的插槽节点中,并且转发到该结点,这样取值的时候也会根据插槽
进行转发
查看结点情况
bash
docker exec -it redis7001 redis-cli -p 7001 cluster nodes
4.2.散列插槽
4.2.1.插槽原理
Redis会把每一个master节点映射到0~16383共16384个插槽(hash slot)上,查看集群信息时就能看到:
数据key不是与节点绑定,而是与插槽绑定。redis会根据key的有效部分计算插槽值,分两种情况:
- key中包含"{}",且"{}"中至少包含1个字符,"{}"中的部分是有效部分
- key中不包含"{}",整个key都是有效部分
例如:key是num,那么就根据num计算,如果是{itcast}num,则根据itcast计算。计算方式是利用CRC16
算法得到一个hash值,然后对16384取余,得到的结果就是slot值。
如图,在7001这个节点执行set a 1时,对a做hash运算,对16384取余,得到的结果是15495,因此要存储到103节点。
到了7003后,执行get num
时,对num做hash运算,对16384取余,得到的结果是2765,因此需要切换到7001节点
4.2.1.小结
Redis如何判断某个key应该在哪个实例?
- 将16384个插槽分配到不同的实例
- 根据key的有效部分计算哈希值,对16384取余
- 余数作为插槽,寻找插槽所在实例即可
如何将同一类数据固定的保存在同一个Redis实例?(数据分类)
- 这一类数据使用相同的有效部分,例如key都以{typeId}为前缀
比如我这里对name 作为key 进行分组,每个组的前缀不同作为分组
4.3.集群伸缩
redis-cli --cluster提供了很多操作集群的命令,可以通过下面方式查看:
比如,添加节点的命令:
4.3.1.需求分析
需求:向集群中添加一个新的master节点,并向其中存储 num = 10
- 启动一个新的redis实例,端口为7004
- 添加7004到之前的集群,并作为一个master节点
- 给7004节点分配插槽,使得num这个key可以存储到7004实例
这里需要两个新的功能:
- 添加一个节点到集群中
- 将部分插槽分配到新插槽
4.3.2.创建新的redis实例
创建一个文件夹:
sh
mkdir 7004
拷贝之前配置文件:
sh
cp redis.conf /7004
修改配置文件:
sh
sed /s/6379/7004/g 7004/redis.conf
启动
4.3.3.添加新节点到redis
添加节点的语法如下:
执行命令:
sh
docker exec -it redis7001 redis-cli --cluster add-node 192.168.249.132:7004 192.168.249.132:7001
显示ok 已经成功
通过命令查看集群状态:
sh
redis-cli -p 7001 cluster nodes
如图,7004加入了集群,并且默认是一个master节点:
但是,可以看到7004节点的插槽数量为0,因此没有任何数据可以存储到7004上
4.3.4.转移插槽
我们要将num存储到7004节点,因此需要先看看num的插槽是多少:
如上图所示,num的插槽为2765.
我们可以将0~3000的插槽从7001转移到7004,命令格式如下:
具体命令如下:
建立连接:
得到下面的反馈:
询问要移动多少个插槽,我们计划是3000个:
复制这个id(nodes 命令中7004的id),然后拷贝到刚才的控制台后:
这里询问,你的插槽是从哪里移动过来的?
- all:代表全部,也就是三个节点各转移一部分
- 具体的id:目标节点的id
- done:没有了
这里我们要从7001获取,因此填写7001的id:可以从多结点分配插槽
填完后,点击done,这样插槽转移就准备好了:
确认要转移吗?输入yes:
日志不断输出转移结果
然后,通过命令查看结果:
docker exec -it redis7001 redis-cli -p 7001 cluster nodes
可以看到:
目的达成。
4.4.故障转移
现在集群状态是这样的:
其中7001、7002、7003,7004都是master,我们计划让7002宕机。
4.4.1.自动故障转移
当集群中有一个master宕机会发生什么呢?
直接停止一个redis实例,例如7002:
sh
docker stop redis7002
1)首先是该实例与其它实例失去连接
2)然后是疑似宕机:
3)最后是确定下线,自动提升一个slave为新的master:
4)当7002再次启动,就会变为一个slave节点了:
这样就通过各个主机之间的心跳检查实现了哨兵的故障转移机制
4.4.2.手动故障转移
利用cluster failover命令可以手动让集群中的某个master宕机,切换到执行cluster failover命令的这个slave节点,实现无感知(一般是用于服务器硬件升级)的数据迁移。其流程如下 :
这种failover命令可以指定三种模式:
- 缺省:默认的流程,如图1~6歩
- force:省略了对offset的一致性校验
- takeover:直接执行第5歩,忽略数据一致性、忽略master状态和其它master的意见
比如我这个演示的集群手动把7002结点重新变为master
bash
docker exec -it redis7002 redis-cli -p 7002
查找结点情况
bash
docker exec -it redis7001 redis-cli -p 7001 cluster nodes
7002再次成为master
4.5.RedisTemplate访问分片集群
RedisTemplate底层同样基于lettuce实现了分片集群的支持,所以使用的步骤与哨兵模式基本一致:
1)引入redis的starter依赖
2)配置分片集群地址
3)配置读写分离
与哨兵模式相比,其中只有分片集群的配置方式略有差异,如下:
哨兵
yaml
spring:
redis:
password: 222222
sentinel:
master: mymaster
nodes:
- 192.168.249.132:27001
- 192.168.249.132:27002
- 192.168.249.132:27003
集群
yaml
spring:
redis:
cluster:
nodes:
- 192.168.150.101:7001
- 192.168.150.101:7002
- 192.168.150.101:7003
- 192.168.150.101:8001
- 192.168.150.101:8002
- 192.168.150.101:8003
测试
使用之前演示的restful操作
读取成功
查看日志
发现主节点7002写内存
而从节点8002读
说明实现了读写分离
并且保存结点是根据key计算来确定插槽位置的