redis分布式缓存
分布式缓存
之前学的都是单点Redis,而这种单点Redis存在一些问题:
- 数据丢失
- 并发能力差
- 存储量小
- 故障恢复能力弱
我们可以使用Redis分布式缓存解决以上问题。
Redis持久化解决数据丢失问题
RDB持久化
RDB全称 Redis Database Backup file
(Redis数据备份文件),也可以称为叫Redis数据快照。简单来说就是把内存中的所有数据都记录到磁盘中。当Redis实例故障重启后,从磁盘读取快照文件,恢复数据。快照文件称为RDB文件,默认是保存在当前运行目录。
RDB持久化的执行条件有save命令、bfsave命令、Redis手动停机、触发系统内置RDB条件时。
save命令
使用redis-cli
链接到redis中后使用save
命令可以立刻执行一次RDB。save命令会导致主进程执行RDB,这个过程中其它所有命令都会被阻塞。只有在数据迁移时可能用到。
bgsave命令
与save
命令的执行步骤相同,但bgsave
命令执行是异步的,执行后会开启独立进程完成RDB,主进程可以持续处理用户请求,不受影响。
停机
主动停机时会自动执行一次RDB
Redis内置RDB条件
Redis内部有触发RDB的机制,可以在redis.conf
文件中找到(保持默认即可),格式如下:
900秒内,如果至少有1个key被修改,则执行bgsave, 如果是save "" 则表示禁用RDB |
RDB原理
当 bgsave 执行时,主进程会 fork(fork()是unix和linux这种操作系统的一个api)一个子进程,子进程共享主进程的内存数据,完成fork后子进程读取内存数据并写入 RDB 文件。
当子进程读取内存数据写入 RDB 文件时,主进程可以继续进行工作,依靠的是 copy-on-write
技术。
- 当主进程执行读操作时,直接访问共享内存
- 当主进程执行写操作时,则会在内存中拷贝一份数据,对拷贝的数据执行写操作,这样不会影响到子进程读取的内存数据
如果不使用copy-on-write会怎么样?
不使用copy-on-write,就意味着子进程在进行写RDB文件时,主进程可以修改子进程要读取的内存数据,那么就无法保证某一时刻数据的一致性。
RDB的缺点
- RDB执行间隔时间长,两次RDB之间写入数据有丢失风险
- fork子进程、压缩、写出RDB文件都比较耗时
AOF持久化
AOF全称为Append Only File
(追加文件)。Redis处理的每一个写命令都会记录在AOF文件,可以看做是命令日志文件。(由主进程先写入到缓冲区,之后由后台线程将缓冲区中的数据写入到AOF文件)
AOF配置
AOF在redis.conf
中默认是关闭的,需要修改redis.conf
配置文件开启AOF:
是否开启AOF功能,默认是no |
appendfsync
三种策略对比:
配置项 | 刷盘实机 | 优点 | 缺点 |
---|---|---|---|
always | 同步刷盘 | 可靠性高,几乎不丢失数据 | 性能影响大 |
everysec | 每秒刷盘 | 性能适中 | 可能会丢失1秒内的数据 |
no | 操作系统控制 | 性能最好 | 可靠性较差,可能丢失大量数据 |
AOF文件重写
因为是记录命令,AOF文件会比RDB文件大的多。而且AOF会记录对同一个key的多次写操作,但只有最后一次写操作才有意义。通过执行bgrewriteaof
命令,可以让AOF文件执行重写功能,用最少的命令达到相同效果。
如果一直没执行此命令,Redis也会在触发阈值时自动重写AOF文件(异步执行)。阈值也可以在redis.conf
中配置:
AOF文件比上次文件 增长超过多少百分比则触发重写 |
RDB与AOF对比
在实际开发中往往会结合两者来使用
RDB | AOF | |
---|---|---|
持久化方式 | 定时对整个内存做快照 | 每一次执行的命令 |
数据完整性 | 不完整,两次备份之间会丢失 | 相对完整,取决于刷盘策略 |
文件大小 | 会有压缩,文件体积小 | 记录命令,文件体积很大 |
宕机恢复速度 | 快 | 慢 |
数据恢复优先级 | 低,数据完整性不如AOF | 高,因为数据完整性更高 |
系统资源占用 | 高,大量CUP和内存消耗 | 低,主要是磁盘IO占用,但AOF重写时会占用大量CPU和内存资源 |
使用场景 | 可容忍数分钟的数据丢失,追求更快的启动速度 | 对数据安全性要求较高 |
总结
RDB
- RDB是一种快照持久化方法,它会在指定的时间间隔内生成数据的完整快照
- 适合于灾难恢复,可以很方便的被迁移到另一个数据中心
- RDB在保存快照时速度快,恢复时也非常迅速,适合用作备份
- 最后一次快照之后的数据可能会丢失,因为这部分数据还没有被写入快照
AOF
- AOF记录每一条写命令
- AOF提供了更好的数据安全性,可以配置为每秒同步一次,或者每写入一条命令就同步一次
- AOF文件通常会比RDB文件更大,且恢复速度可能会更慢,但可以通过AOF文件重写进行压缩
- AOF在系统崩溃时能最大化数据恢复,最多只丢失几秒钟的数据
如果需要快速恢复且可以接受少量数据丢失,RDB可能是更好的选择。如果注重数据完整性且可以接受较慢的恢复速度,则应该使用AOF。在很多场景下,结合使用RDB和AOF能提供更为可靠的数据保护机制。
主从集群解决并发问题
单节点Redis的并发能力是有上限的,要进一步提高Redis的并发能力,就需要搭建主从集群,实现读写分离。
主从集群可以是一台Redis服务器作为主节点(master),数台服务器作为从节点(slave),主节点只负责写数据,从节点只负责读数据。
数据同步原理
主从之间的第一次同步全量同步,既要加载RDB文件,又要读取并执行repl_baklog中的命令。
如何判断是不是第一次同步?
- replication id:简称replid,数据集标记,id一致则说明是同一数据集。每一个master都有唯一的replid,slave则会继承master节点的replid
- offset:偏移量,随着记录在
repl_baklog
中的数据增多而逐渐增大。slave完成同步时也会记录当前同步的offset。如果slave的offset小于master的offset,说明slave数据落后于master,需要更新。
因此slave做数据同步,必须向master声明自己的replication id和offset,master才可以判断到底需要同步哪些数据
一般来讲,如果id能对上就不用做全量同步,但是repl_baklog
大小有上限,写满后会覆盖最早的数据。如果slave断开时间过久,导致尚未备份的数据被覆盖,则无法同步基于log做增量同步,只能再次做全量同步。
优化主从集群性能
- 在master中配置
repl-diskless-sync yes
启用无磁盘复制,避免全量同步时的磁盘IO。 - Redis单节点上的内存占用不要太大,减少RDB导致的过多磁盘IO
- 适当提高
repl_baklog
的大小,发现slave宕机时尽快实现故障恢复,尽可能避免全量同步 - 限制一个master上的slave节点数量,如果实在是太多slave,则可以采用主-从-从链式结构,减少master压力
Redis哨兵解决故障恢复问题
slave
节点宕机恢复后可以找master
节点同步数据,那master
节点宕机怎么办?
这就需要指定一个slave
节点为新的master
,执行写操作。这个操作不需要人工手动执行,因为Redis提供了哨兵(Sentinel)机制来实现主从集群的自动故障恢复。哨兵本身也是一个集群。
哨兵的结构和作用
- 监控:Sentinel会不断检查master和slave是否按预期工作
- 自动故障恢复:如果master故障,Sentinel会将一个slave提升为master。当故障实例恢复后也以新的master为主
- 通知:Sentinel充当Redis客户端的服务发现来源,当集群发生故障转移时,会将最新消息推送给Redis的客户端
服务状态监控
Sentinel
基于心跳机制检测服务状态,每隔1秒向集群的每个实例发送ping命令:
- 主观下线:如果
Sentinel
节点发现某实例未在规定时间相应,则认为该实例主观下线 - 客观下线:若超过指定数量(
quorum
)的Sentinel
都认为该实例主观下线,则该实例客观下线。quorum
的取值最好超过Sentinel
数量的一半
选举新的master
一旦发现master
故障,Sentinel
需要在salve
中选择一个作为新的master
,选择依据是这样的:
- 首先会判断
slave
节点与master
节点断开时间长短,如果超过指定值(down-after-miliseconds*10)则会排除该slave
节点 - 然后判断
slave
节点的slave-priority
值,越小优先级越高,如果是0则永不参与选举 - 如果
slave-prority
一样,则判断slave
节点的offset
值,越大说明数据越新,优先级越高 - 最后是判断
slave
节点的运行id大小,越小优先级越高
实现故障转移
当选中了其中一个slave
为新的master
后:
Sentinel
给备选的slave
节点发送slaveof no one
命令,让该节点成为master
Sentinel
给所有其它slave
发送slaveof IP地址 端口
命令,让这些slave
成为新master
的从节点,开始从新的master
上同步数据- 最后,
Sentinel
将故障节点标记为slave
,当故障节点恢复后会自动成为新的master
的slave
节点
脑裂问题与解决方案
脑裂问题是指在分布式系统中,由于网络分区导致集群被分割成多个独立的部分,每个部分都可能认为自己是唯一正常运行的集群,从而导致数据不一致或服务冲突。
原因:
- 网络分区:集群节点之间的网络通信中断,导致部分节点无法互相通信
- 主从切换:在网络分区后,部分节点可能选举出新的主节点,导致同一集群中出现多个主节点
- 数据不一致:不同主节点可能同时接收写请求,导致数据冲突或丢失
表现:
- 集群被分割为多个独立子集群,每个子集群可能都有自己的主节点
- 客户端可能连接到不同的子集群,读取或写入不一致的数据
- 网络恢复后,集群可能无法自动合并,导致数据冲突
解决方案
- 集群配置参数
min-slaves-to-write
:主节点至少需要多少个从节点才能接收写请求。如min-slaves-to-write 1
表示主节点至少需要1个从节点才能写入数据。在网络分区后,如果主节点失去足够多的从节点,则停止写入,避免数据不一致min-slaves-max-lag
:从节点与主节点的最大延迟时间(秒)。如min-slaves-max-lag 10
表示从节点的复制延迟不能超过10秒。如果从节点延迟过高,主节点会停止写入,确保数据同步
- 数据分片
- Redis集群将数据划分为16384个哈希槽,每个主节点负责一部分槽
- 在网络分区后,只有持有完整槽信息的分区才能提供服务,避免数据冲突
Java中使用RedisTemplate配置哨兵集群
引入依赖
<dependency> |
配置Redis
logging: |
配置主从读写分离
|
ReadForm
函数是配置Redis的读取策略,有以下选择:
- MASTER:从主节点读取
- MASTER_PREFERRED:优先从master节点读取,master不可使用才读取slave
- REPLICA:从slave节点读取
- REPLICA_PREFERRED:优先从slave节点读取,所有的slave都不可用才读取master
Redis分片集群解决海量数据存储问题
分片集群特征:
- 集群中有多个
master
,每个master
保存不同数据 - 每个
master
都可以有多个slave
节点 master
之间通过ping监测彼此健康状态- 客户端请求可以访问集群任意节点,最终都会被转发到正确节点
散列插槽
Redis会把每一个master节点映射到0~16384
个插槽上,通过查看集群信息时即可看到每个节点插槽的区间
redis-cli -p 7001 cluster nodes |
集群伸缩
redis-cli –cluster提供很多操作集群的命令,可以通过下面的命令查看:
redis-cli --cluster help |
故障迁移
如果集群中某个master宕机了,则该master下优先级最高的slave节点会变成master,选举出的新master会接管原master的槽位(slot)和数据,继续对外提供服务。其他节点会更新自己的路由表,将请求转发到新的master。如果原master重新上线,它会成为新master的slave节点,开始同步数据。
Redis访问分片集群
RedisTemplate底层同样基于lettuce实现了分片集群的支持,而使用的步骤与哨兵模式基本一致:
- 引入redis的starter依赖
- 配置分片集群地址
- 配置读写分离
- 与哨兵模式相比,其中只有分片集群的配置方式略有差异,如下:
spring: |