一、 Redis的高可用
1. RDB
- 定义:又名RDB(Redis Database)持久化,通过拍摄快照的方式来实现持久化,本质是将某个时间点的内存中的数据存储在一个rdb文件中(dump.rdb),在Redis重启的时候会加载rdb中的文件数据
- 配置 redis.conf:
参数 | 默认值 | 说明 |
save | 900 1 | 900秒之后至少有一个key被修改就执行一次快照 |
save | 300 10 | 300秒之后至少有10个key被修改就执行一次快照 |
save | 60 10000 | 60秒之后至少有10000个key被修改就执行一次快照 |
save | "" | 关闭RDB自动生成快照功能,可以通过执行bgsave生成快照 |
stop-writes-on-bgsave-error | yes | 拍摄快照失败是否继续执行写命令 |
rdbcompression | yes | 是否对快照文件进行压缩 |
rdbchecksum | yes | 是否数据校验 |
dbfilename | dump.rdb | 快照文件存储的名称 |
dir | ./ | 快照文件存储的位置 |
- 触发时机:
- 通过上面在redis.conf中配置,自动触发
- 手动触发
- bgsavez指令: fork一个子进程,不会阻塞主线程,有可能不是最新的数据
- save 阻塞主线程,保证数据一致性
- 指令触发
- shutdown触发save指令
- flushall 触发save指令
- 优点
- RDB是一个非常简洁的文件,是保存了某个时间点的数据,很适合做数据的备份。
- RDB适合用于灾备,单文件很方便传输到网络中
- RDB文件的性能很好,持久化的时候可以通个bgsave命令可以fork一个新的线程单独处理备份
- 缺点
- 拍摄快照的频率容易造成数据丢失,安全性低。
- RDB文件比较大的情况下,持久化会比较花费时间,容易造成系统卡顿。
2. AOF
- 定义:与快照持久化通过直接保存Redis的键值对数据不同,AOF持久化是通过保存Redis执行的写命令来记录Redis的内存数据,理论上说,只要我们保存了所有修改的Redis内存数据命令(也就是写命令),那么根据这些保存的写命令,我们可以重新恢复Redis的内存状态。AOF持久化正是利用这个原理来实现数据的持久化与数据的恢复的
- 配置: 默认不开启,开启后默认用aof做备份恢复
参数 | 默认值 | 说明 |
appendonly | yes | 是否开启AOF持久化 |
appendfilename | "appendonly.aof" | 存储的文件的名称 |
appendfsync | everysec | 同步频率everysec每隔一秒钟持久话一次,always 每执行一条命令之旧话一次no持久化的时机交给操作系统处理,linux系统默认30s |
no-appendfsync-on-rewrite | no | 执行压缩时是否执行同步操作 |
auto-aof-rewrite-percentage | 100 | 当前AOF文件超过上次AOF文件百分比后才进行持久化操作 |
auto-aof-rewrite-min-size | 64mb | 自动重新AOF操作文件最小的大小要达到的大小 |
- 备份机制:
- redis7.0之前
- Redis fork一个子进程,在一个临时文件中写入新的AOF(当前内存的数据生成的新的AOF)
- 在写入新的AOF的时候,主进程还会有指令进入,那么主进程会在内存缓存区中累计新的指令(同时会写入旧的AOF文件中,就算重写失败,也不会导致AOF损坏或者数据丢失)
- 如果子进程重写完成,父进程会受到完成信号,并且把内存缓存中的指令追加到新的AOF文件中
- 替换旧的AOF文件,并且将新的指令附加到重写好的AOF文件中。
- redis7.0之后
- 子进程开始一个临时文件中写入新的基础AOF
- 父级打开一个新的增量AOF文件以继续写入更新。如果重写失败,旧的基础和增量文件(如果有的话)加上这个新打开的增量文件就代表了完整的更新数据集
- 当子进程完成了基础文件的重写后,父进程收到一个信号,并使用新打开的增量文件和子进程生成的基础文件来构建临时清单,并将其持久化。
- 现在Redis对清单文件进行原子交换,以便此AOF重写的结果生效。Redis还会清理旧的基础文件和任何未使用的增量文件。
- redis7.0之前
- 优点:
- AOF数据安全性更高,提供了各种频率来同步
- AOF通过命令追加的方式来构造,在运行过程中同步数据效率更高,最多只会导致丢失1s的数据·
- AOF文件格式的可读性更高,即使我们不小心执行了flushAll命令,在重写还没进行之前我们可以通过AOF文件来恢复数据
- AOF由于某些原因(磁盘满了等)导致追加失败,也能通过redis-check-aof工具来修复
- 缺点:
- 对于具有相同数据的Redis,AOF的体积会更大
- 虽然提供了多种同步频率,每秒同步的频率也具有较高的性能,但是在Redis负载较高的情况下,RDB比AOF具有更好的性能保障
- Redis7.0之前 重写的时候,新的指令会缓存在内存中,所以导致大量的内存使用,并且重写期间,会跟磁盘进行2次IO,一个是写入老的aof文件,一个是写入进的aof文件
二、Redis主从模式
1. 作用
- 故障恢复:master节点宕机后,从节点有数据可以恢复
- 负载均衡:流量分发,主节点写数据,从节点读数据,减少单实例的读写压力
- 高可用:集群、哨兵都是基于主从实现的
2.数据同步
全量数据
- 在slave节点发给master节点同步数据指令时,会传递参数master_replid和offset(偏移量)两个参数
- master节点通过判断master_replid参数与master节点的replid是否一致,如果不一致触发全量数据同步
- master开始执行bgsave,生成一个RDB文件,并且把RDB文件传输给slave节点,同时把master的offset更新
- slave接收到rdb文件后,清空slave内存中的数据,然后用rdb来加载数据,保证了slave数据是master节点生成的rdb的最新数据
- 由于master生成rdb文件期间,会接受到新的指令,这些指令会保存在一个内存区间(replication_buffer),在slave节点同步完rdb后传输内存空间的指令完成增量同步
增量同步
- slave发送同步指令给master,master接收到指令后,先判断master_replid是否与本身的replid是否一致。
- 判断一致后,通过offset(偏移量)判断积压缓存(replication_backlog_buffer)是否有slave需要同步的数据,如果没有需要同步的数据(由于积压缓存大小可设置,需要的数据可能被封盖),就做全量同步,否则只做增量同步
注
- 在Redis4.0之前用runId作为参数,由于每次实例重启时都会改变runId,会导致主从切换或者重启的时候,都需要全量同步,所以Redis4.0后改为replid了
- replid有matser_replid和master_replid2,当主从切换的时候,升级为master的slave会继承之前的replid,所以不是每次主从切换,都出发全量同步,同时slave会把master的master_replid持久化到磁盘
- replication_buffer内存大小设置(redis.conf):client-output-buffer-limit replica 256mb 64mb 60
- 256mb 硬性限制,大于256mb断开链接
- 64mb 60 软限制,超过64mb 并且超过60s还没有进行同步, 内存数据就会断开连接
- replication_buffer 内存空间不能太小,如果太小,为了数据安全,会关闭跟从库的网络连接。再次链接的重新全量同步,但是问题还在,会导致无限的在同步
- 积压缓存存在的先决条件:有slave节点,所有指令才会写入积压缓存
- 积压缓存大小设置:redis.conf中设置 repl-backlog-size 1mb 默认1mb
3. 数据同步流程图
三、哨兵
1. 作用
- 监控主数据库和从数据库是否正常运行
- 主数据库出现故障时自动将从数据库转换为主数据库
- sentinel可以提供Redis的master实例地址,那么客户端只需要 跟sentinel进行连接,master挂了后会提供新的master
2. 哨兵模式原理
- 每10秒钟哨兵会向主数据库和从数据库发送INFO命令
- 每2秒钟哨兵会向主数据库和从数据库的sentinel:hello频道发送自己的消息
- 每1秒钟哨兵会向主数据、从数据库和其他哨兵节点发送PING命令
3. 主节点选取
- 主观下线:当超过down-after-milliseconds指定时间后,如果被PING的数据库或节点仍然未回复,则哨兵认为其主观下线(subjectively down),主观下线表示从当前的哨兵进程来看,该节点已经下线
- 客观下线:在主观下线后,如果该节点是主数据库,则哨兵会进一步判断是否需要对其进行故障恢复,哨兵发送SENTINEL is-master-down-by-addr命令询问其他哨兵节点以了解是否也认为该数据库节点主观下线了。如果达到指定数量时,哨兵会认为其客观下线(objectivelydown),并选举领头的哨兵节点对主从系统发起故障恢复,这个指定数量就是前面配置的quorum参数
- 哨兵选举:
- 发现主数据库客观下线的哨兵节点(A)向每个哨兵节点发送命令,要求对方选择自己称为领头哨兵
- 如果目标哨兵节点没有选过其他人,则会同意将A设置为领头哨兵
- 如果A发现有超过半数且超过quorum参数值的哨兵节点同意选择自己称为领头哨兵,则A成功称为领头哨兵
- 当有多个哨兵节点同时参选领头哨兵,则会发现没有任何节点当选的可能,此时每个参选节点将等待一个随机事件重新发起参选请求进行下一轮选举,直到选举成功
- 主节点选取:首先领头哨兵将从停止服务的主数据库的从数据库中挑选一个来充当新的主数据库,挑选依据如下
- 所有在线的从数据库中,选择优先级最高的从数据库,优先级通过replica-priority参数设置
- 优先级相同,则复制的命令偏移量越大(复制越完整)越优先
- 如果以上都一样,则选择运行ID较小的从数据库
- 升级从从节点为主节点:领头哨兵将向从数据库发送SLAVEOF NO ONE命令使其升格为主数据库,而后领头哨兵向其他从数据库发送SLAVEOF命令来使其称为新主数据库的从数据库,最后一步则是更新内部的记录,将已经停止服务的旧数据库更新为新的主数据库的从数据库,使得当其恢复服务时自动以从数据库得身份继续服务。
注:
- 如果slave与master断开链接时间超过主服务器配置的超时时间(down-after-milliseconds)的十倍,失去竞选资格
- 配置优先级 repica-priority 默认值为100,越小优先级越高,配置为0时,失去竞选资格
4. 脑裂:
- 定义:由于网络问题,哨兵之间只会选取自己通讯内的节点为主节点,这个时候会出现多个master节点,客户端会从不同的master节点写数据,从而在master恢复的时候会导致数据丢失
- 解决方案:
- 至少有几个slave点同步到master节点的数据,默认为1:min-replicas-to-write
- slave节点的延迟时间必须小于等于时间的配置,默认为10s:min-replicas-max-lag
注:
- 满足以上要求,是可以尽量保证较少数据一致性问题,由于同步都是异步完成的因此还是会存在数据一致性问题的
- 一般部署都会为计数,因为偶数和奇数都一样必须保证挂掉的哨兵不能超过总哨兵数的一半
四、集群
1. 定义
- Redis集群是一个提供在多个Redis间节点间共享数据的程序集
- Redis集群并不支持处理多个key的命令,因为这需要在不同的节点间移动数据,从而达不到像Redis那样的性能,在高负载的情况下可能会导致不可预测的错误
- Redis集群通过分区来提供一定程度的可用性,在实际环境中当某个节点宕机或者不可达的情况下继续处理命令
2.Redis集群优势
- 自动分割数据到不同的节点上
- 整个集群的部分节点失败或者不可达的情况下能够继续处理命令
3.分片策略
Redis集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽,集群的每个节点负责一部分hash槽
那么为什么是16384个槽:
- 我们发现有个char类型的 unsigned char myslots[CLUSTER_SLOTS/8]; 其中大小为当前的槽的数量/8
- 16384/8 一个char在c语言中大小被定义为1Byte。所 以中大小为16384/8/1024=2kb
- 因为crc16算法得到的hash值是16bit 最大的值是65536、通过计算能达到 8k,数据传输会很慢。
- 并且一般场景下,16384个节点已经能满足我们的需求。所以取了一个 16384,提升了我们的传输性能。
注:
- 这种结构很容易添加或者删除节点,比如如果我想添加节点D,那我需要从节点A、B、C中得到部分槽到D上,如果我想移除节点A需要将A中的槽移到B和C节点上,然后将没有任何槽的A节点从集群中移除即可,由于从一个节点将哈希槽移动到另一个节点并不会停止服务,所以无论添加删除或者改变某个节点上的哈希槽的数量都不会造成集群不可用的状态(集群中的节点的哈希槽号都不是联系的可能是很散乱的)
- 每个hash槽都必须有一个对应的节点
- 节点与hash槽之间的对应关系可以通过配置更换,key与hash槽的对应关系不会变
- 如果想把两个key放到同一个hash槽里的话设置key时大括号里的值保持一致即可
- java{18} 跟 c{18} 就会在一个虚拟槽
4. Redis的主从复制模型
- 目的:为了使在部分节点失败或者大部分节点无法通信的情况下集群仍然可用,所以集群使用了主从复制模型,每个节点都会有N-1个复制品
- 举例:我在在例子中具有A、B、C三个节点的集群,在没有复制模型的情况下,如果节点B失败了,那么整个集群就会缺少5501-11000这个范围的槽而不可用,然而如果在集群创建的时候(或者过一段时间)我们为每个节点添加一个从节点A1、B1、C1,那么整个集群边有三个master节点和三个slave节点组成,这样在节点B失败后,集群便会选举B1为新的主节点继续服务,整个集群便不会因为槽找不到而不可用了(不过当B和B1都失败后,集群就不可用了)
五、Redis缓存常见问题
1. 缓存雪崩
定义:是指某一个时间段,缓存集中过期失效
解决方案:解决方案就是设置过期时间不要都一致,分类设置,同一类别增加一个随机因子
2. 缓存击穿
- 定义:指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就混击穿缓存,直接请求数据库,就像在一个屏障上凿开一个洞
- 解决方案:使用互斥锁,回写redis,并且采用双重检查锁来提升性能,减少对DB的访问或者不设置过期时间