文章目录
后端开发技能学习(十)redis学习(下篇)
哨兵
基本概念
在上一章节中,我们学习到了redis的主从模式,在这种模式下,redis能接收高并发的读操作,但这种模式同样也会产生一定的问题。
- 主节点如何产生,发生故障怎么办?
- 从节点发生故障,其他节点如何得知和恢复?
而在redis2.8中,启用了哨兵的结构来对多节点的情况下出现的问题进行监控与恢复。
实现原理
哨兵是redis中为了实现高可用而设计的结构,一般来说,在分布式情况下,每一个集群都有若干个哨兵负责该节点的监控与恢复,那么哨兵的实现原理是什么呢?
三个定时任务
对于故障监控,哨兵是通过三个定时的监控任务来完成,包括
- 每隔10s,每一个Sentinel节点会向主节点和从节点发送一个info命令来更新整个结构的拓扑结构
- 每隔2s,每个Sentinel节点会向主节点的一个订阅频道上发送自己的信息,同时也可以更新其他所有哨兵的信息
- 每隔1s,每一个Sentinel节点会向所有的节点和哨兵发送一次心跳检测(ping),来判断该节点是否正常
若某一个哨兵心跳检测时发现一个节点没有响应,则该哨兵认为该节点主观下线,若该节点为主节点,那么哨兵会对所有的哨兵询问主节点的状态,若认为主节点主观下线的哨兵数量大于一定值,则认为主节点已经客观下线,需要进行故障转移
领导者选举
当主节点客观下线后,则需要一个哨兵对该节点进行故障恢复,那么由哪一个哨兵来进行故障转移呢,redis中采用的是raft算法
raft算法是分布式集群中很经典的算法
大致过程如下:
- 每一个哨兵在认为主节点主观下线之后,会向所有的哨兵发送一条征票信息,为自己争取选票
- 每一个哨兵接收到该征票信息后,如果没有将自己的票投出,则返回一条信息表示选择该节点
- 若哨兵发现自己的得票数哨兵数的一半,则自己成为领导者进行故障转移
故障转移
领导者已经选出,那么该哨兵如何进行故障转移呢?换句话说,哨兵选择哪一个节点作为新的主节点呢?
有如下规则:
- 过滤掉主观下线的节点
- 选择优先级最高的节点
- 优先级同样的情况下,选择数据最完备的节点(即复制偏移量最大的节点)
- 都一样的情况下,选择runid最小的从节点
集群
主从复制、哨兵解决了redis高可用的问题,但遇到单机内存、并发、流量等瓶颈时,则需要一个分布式的架构将数据存放在不同的节点上。
redis3.0推出了redis cluster,有效的解决了redis分布式方面的需求。
在集群模式下,单个redis节点有如下限制:
- 不支持不同节点上的批操作
- 不支持不同节点上的事务
- 不能将一个list分不到不同节点
- 一个节点只能拥有一个数据空间
- 不支持嵌套复制(即从节点不能成为主节点)
数据分布理论
数据库的分库分表
这里顺便扯一扯数据库的分库分表。
为什么要分库?
当数据库的读/写QPS过高,导致数据库的连接数不足时,需要对库进行切分,减少单库的并发量
为什么要分表
当单表数据量过大,导致查询缓慢且存储不足时,需要对表进行切分
如何进行切分?
对与简单的切分方式,一般有垂直切分和水平切分
垂直切分即将一张表按列分成多个不同的表,这种方式代价较大,需要根据表的内容进行一定细分,且切分度有上限,不能解决记录过多的情况。
水平切分即将一张表按行切分成一样的表,这种方式比较常用。
切分方式
redis中当然不存在表的概念,所以都是类似水平切分的方式来进行
那么怎么进行分区呢?
常见的分区规则有哈希分区和顺序分区
两者的优缺点如下:

在redis中采取的时哈希分区,有如下几种哈希规则:
- 节点取余
简单,但是遇到扩容或收缩时会导致大量的数据迁移,常用于数据库的分库分表
- 一致性哈希

实现一个哈希环,每次查找时,先计算哈希值,然后去找里该哈希值最近的节点,这样就减少了大量的重新映射,但是存在一些问题:
a. 加减节点时虽然不用将所有节点进行重新计算,但还是会出现一部分节点的数据需要更新
b. 当节点数量较少时,需要进行一次很长的遍历,效率不高
c. 容易发生数据倾斜,导致整体性能不高
后两点可通过增加虚拟节点解决
- 虚拟槽分区
虚拟槽分区使用分散度良好的哈希函数把所有的数据隐射到一个固定范围的整数集合中(redis有16383个槽)。
与一致性哈希不同,虚拟槽中的每一个槽都被分配到了一个节点中被负责管理,
这样既保证了查询速度,又能很方便的进行扩容和收缩
缓存中的常见问题
过期淘汰策略
缓存毕竟时缓存,由于存在内存中,必要要有一个过期淘汰策略来消除一些元素,在redis中有8种淘汰策略:
noeviction : 默认策略,当内存达到最大值时,对所有申请内存的操作都报错
volatile-ttl:对设置过期时间的key根据过期时间淘汰
对象\策略 | LRU | RANDOM | LFU |
---|---|---|---|
所有key | allkeys-lru | allkeys-lfu | allkeys-random |
设置了过期时间的key | volatile-lru | volatile-lfu | volatile-random |
穿透优化
穿透是指存在大量的空请求导致请求发现缓存中不存在依然去对数据库发送请求,导致数据库压力过大
一般有如下优化策略:
- 在缓存中设置一些空指针,并设置很短的过期时间
- 使用布隆过滤器对这些请求进行过滤
无底洞优化
在集群模式下,由于不支持不同节点上的批操作,所以会造成节点越多,批操作越慢的情况,一般有如下策略:

雪崩优化
雪崩是指缓存突然崩溃或者说缓存中的大量键值同时到期,导致请求全部到达数据库。
有如下策略:
对于第一种情况,建立高可用的redis架构,防止崩溃的发生
对于第二种情况,可以为键值的过期时间加一个随机的偏移防止同时到期
热点key重建优化
当一个热点数据失效时,可能会发生多个线程同时更新缓存的情况,这时候就会发生数据不一致的问题,存在一定的危险
解决这种情况,有以下策略
- 设置互斥锁: 保证同时只有一个线程能更新缓存,可以用setnx来实现,也能用第三方的分布式锁如zookeeper实现
- 设置热点数据永不过期
bigkey优化
bigkey指的是key对应的value占用的空间过大,一般大于10KB就是bigkey,其有如下危害
-
集群模式下,内存分配不均匀
-
超市阻塞和网络阻塞
优化方法:拆分(一般bigkey都是hash,list等结构)
热点key优化
上面说到,对于热点数据,我们应该设置它永不过期,那么怎么去找到这些热点数据呢?
- 客户端:使用全局字典来统计请求次数,由此判别热点数据
- 代理端:若使用了代理服务器,则可以让代理服务器来管理
- 服务端:使用monitor命令统计热点key是很多开发和运维人员首先想到,monitor 命令可以监控到Redis执行的所有命令
- 机器:也可对tcp的数据进行抓包统计
以下是其优缺点

发现了热点key,那么怎么减轻单个节点对热点key的压力呢?
- 若热点key为复杂数据结构,那么可以对其进行拆分,这样就会被分配到不同的节点
- 迁移热点key,将热点key所在的槽单独迁移到新的节点
- 增加一级缓存,将热点数据缓存一份在hashmap中,是其性能更高