分布式缓存——Redis高级篇
分布式缓存——Redis高级篇
单节点Redis问题
- 数据丢失问题:实现Redis数据持久化
- 并发能力问题:搭建主从集群,实现读写分离
- 故障恢复问题:Redis哨兵,实现健康检测和自动恢复
- 存储能力问题:搭建分片集群,利用插槽机制实现动态扩容
一、持久化
方式之一:RDB持久化
RDB全称RedisDatabaseBackup file(Redis数据备份文件)
有两个命令可以进行RDB持久化
- save命令:由主进程执行RDB,会阻塞所有命令
- bgsave命令:开子进程执行RDB,避免主进程受影响
默认Redis停机时会执行一次RDB
(停机≠宕机)
可以通过修改redis.conf文件来配置相关的RDB参数
1 | save 900 1 #900秒内由1次修改,就会执行bgsave。参数可以进行修改 |
bgsave流程
bgsave开始时会fork主进程得到子进程(在fork过程中主进程会被阻塞),子进程共享主进程的内存数据。fork后主进程可以正常读写,子进程会读取内存数据并写入RDB文件。
如果只是这样可能会有脏数据的问题,就是说在子进程读的时候主进程可能更改了数据。
要理解这个问题,需要理解内存结构。
如图是我参考课程使用visio绘制的fork框图,下面结合这个来解释一下fork的流程。
首先是关于linux的结构,在linux中,进程都是不能直接更改物理内存的,它在虚拟内存中,通过一个页表来维护关系,进程通过修改页表修改内存中的数据。
当使用bgsave的时候,首先会进行fork,会开启子进程,子进程复制主进程的页表,复制完后,为避免脏读的情况,会将数据A和数据B设为只读的共享内存,这样子进程在存储RDB时就不会被主进程干扰。
相应的主进程要更改数据,数据B会被拷贝为数据B的副本,主进程通过修改副本实现修改的功能。
因为内存进行了拷贝,所以极限情况下可能会占用两倍的内存,这也就是为什么使用Redis都要预留一定的内存空间。
RDB缺点:
- RDB间隔较长,两次RDB间由数据丢失风险
- fork子进程、压缩写出RDB比较耗时
方式二:AOF持久化
AOF全程Append Only File(追加文件),可以看成命令日志文件
可以通过redis.conf配置文件开启和配置AOF
1 | #开启 |
AOF体积压缩
因为AOF记录的是每条命令,多条命令叠加下可能很多命令是无效的,所以体积会很大。
可以使用bgrewriteaof命令将AOF文件进行重写以减小体积
什么时候进行重写?
可以在redis.conf文件中配置
1 | auto-aof-rewrite-percentage 100#AOF文件增长超过多少百分比触发重写 |
RDB和AOF对比
各有优缺点,如果对数据安全性要求较高,在实际开发中往往会将两种方式结合起来使用
RDB | AOF | |
---|---|---|
持久化方式 | 定时对整个内存做快照 | 记录每一次执行的命令 |
数据完整性 | 不完整,两次备份之间会丢失 | 相对完整,取决于刷盘策略 |
文件大小 | 会有压缩,文件体积小 | 记录命令,文件体积大 |
宕机恢复速度 | 很快 | 慢 |
数据恢复优先级 | 低,因为数据完整性不如AOF | 高,因为数据完整性高 |
系统资源占用 | 高,大量CPU和内存消耗 | 低,主要是磁盘IO资源,但AOF重写时会占用大量CPU和内存资源 |
使用场景 | 可以容忍数分钟的数据丢失,追求更快的启动速度 | 对数据安全性要求较高 |
二、主从集群
- 搭建主从架构
开启主从关系
1 | replicaof/slaveof(5.0之前) host port(主节点的) |
开启后主从搭建就完成了,主节点写,从节点只读
- 数据同步原理
全量同步
首次请求进行全量同步
增量同步
在上边全量同步的流程中,如果发现replid一致,就会进行增量同步。主节点会从log文件中获取offset以后的数据,发送offset之后的命令给从节点。
上边暂存的lrepl_baklog可以理解为是一个环形的结构,而这个环形的结构是有存储上限的。当slave和master之间差的offset超过repl——baklog的大小时,不能进行增量同步
优化:
- 在master中配置repl-diskless-sunc yes,启用无磁盘复制,避免全量同步时的磁盘IO
- Redis单节点的内存占用不要太大,减少RDB导致的过多磁盘IO
- 提高log的大小,发现slave宕机时尽快恢复,尽可能避免全量同步
- 限制一个master的slave数量,如果是在太多可以使用主从从结构
三、Redis哨兵
哨兵的作用:
- 监控:Sentinel会不断检查您的master和slave是否按预期工作
- 自动故障恢复:如果master故障,Sentinel会将一个slave提升为master,当故障实例恢复后也会以新的master为主
- 通知:Sentinel充当Redis客户端的服务发现来源,当集群发生故障转移时,会将最新消息推送给Redis的客户端
Sentinel基于心跳机制监测服务状态,每隔1秒向集群的每个实例发送ping命令
主观下线:如果Sentinel节点发现某实例未在规定时间内响应,则认为该实例主观下线
客观下线:若超过指定数量(quorum)的Sentinel都认为该实例主观下线,则该实例客观下线。quorum的值最好超过Sentinel实例数量的一半。
选择新的master的优先级:
- 断开时间长短
- slave-priority
- offset
- 运行id,越小优先级越高
故障转移流程
- 选择新的master,执行slave of no one
- 其他节点slave of新的master
- 修改故障节点配置,添加slave of新的master
搭建哨兵集群
- 新建sentinel.conf
- 配置ip、主从集群、目录
RedisTemplate的哨兵模式
- 导入依赖
- 在yaml文件中配置sentinel集群地址
- 配置主从读写分离
四、Redis分片集群
分片集群的作用:
海量数据存储
高并发写操作
分片集群说明:
- 集群中有多个master
- 每个master都有多个slave
- master之间通过ping监测批次的健康状态
- 客户端请求可访问集群的任意节点,最终都会被转发到正确节点
搭建
redis-cli –-cluster create --cluster-replicas 1 X X X Y Y Y
因为设置每个master的slave个数为1,所以前三个为master,后三个是slave
散列插槽
Redis会把每一个master节点映射到0~16383共16384个插槽(hashslot)上,查看集群信息就能看到
数据key不是与节点绑定,而是与插槽绑定,redis会根据key的有效部分计算插槽值,分两种情况
- key中含有”{}“且{}中至少包含一个字符,则{}内的部分有效
- key不是不含{},整个key有效
计算方法是有CRC16算法的一个hash值,然后对16384取余。
如何将同一类数据固定的保存在同一个Redis实例?
使用相同的有效部分,也就是{}内的部分相同
添加一个节点到集群
例:想集群中添加一个master节点,向其中添加一个num=10数据
- 添加 使用addnode指令
- 插槽分配 使用
redis-cli —cluster reshard host port
故障转移
当集群中有一个master宕机会发生什么?
自动故障转移:
- 该实例失去连接
- 疑似宕机
- 确定下线,自动提升一个slave为新的master
如果想要手动更换master节点,使用failover指令进行故障转移
具体流程如下:
- slave节点告诉master节点拒绝任何客户端请求
- master返回当前的数据offset给slave
- slave等待offset与master一致
- slave、master开始故障转移
- slave标记自己为master,广播故障转移的结果
- 原master收到广播,开始处理客户端读的请求
failover有3种模式:
- 缺省,就是上述的默认模式
- force,省略一致性检验的过程
- takeover,直接执行步骤5
RedisTemplate访问分片集群
与哨兵的配置类似
只是在yaml文件的配置不同
今天试了试用图床来插入图片,我已经受够了糟糕的mermaid流程图,还是visio画着舒服❤