缓存
缓存穿透
当查询一个不存在的数据,mysql查询不到数据,无法写入缓存,导致每次都请求数据库
解决方法
- 缓存空数据,当查询结果未空,将结果进行缓存。 简单但是会消耗内存,而且会出现不一致情况。
- 布隆过滤器,检索一个元素是否在集合中,缓存预热时,也要预热过滤器。 内存占用少,不会产生多余key,但是实现复杂且存在误判情况。
缓存击穿
给key设置了过期时间,当该key过期时,出现大量针对该key的请求,可能会瞬间将数据库压垮。
解决方法
- 添加互斥锁
强一致性,但是性能差 - 逻辑过期
热点key不设置过期时间,设置一逻辑过期时间,对于逻辑过期的key,启用新线程更改数据,原线程仍然返回过期数据。
缓存雪崩
同一时间大量key过期,或redis服务宕机,导致大量请求同时达到数据库。
解决方法
- 给不同key的TTL添加随机值
- Redis集群 (哨兵模式、集群模式)
- 缓存业务添加降级限流机制
- 业务多级缓存
双写一致性
设置前提,介绍背景业务
实现方法
高一致性
- 延迟双删
修改数据库的同时也要更新缓存的数据,缓存于数据库保持一致。
读:命中直接返回,未命中则查询DB,将结果写入缓存,设定超时时间
写:延迟双删(依旧存在脏数据风险)
Quetion
-先删缓存还是先删数据库
无论先删除哪个都存在不一致性问题
- 为何要删除两次
防止在修改数据库期间,其他线程将旧的结果写入缓存 - 为何延时删除
保证redis集群的主从一致性。
- 分布式锁
保证缓存和数据库的绝对一致性,但是性能低。适用于读多写少的情况。
最终一致性
异步通知,保证数据的最终一致性
二进制日志记录(binlog)中记录所有 DDl(数据定义语言)和 DML(数据操纵语言),但不包括查询语言。
Redis持久化
RDB
RDB (Redis Database Backup file, Redis数据备份文件),也叫redis数据快照,将数据记录到磁盘中,每次redis实列故障重启后,都会从磁盘中读取快照,回复数据。
备份方式
- 主动备份
save #主线程备份
bgsave #启用子线程备份
- 内部触发
通过redis.conf文件中修改配置项实现
save 900 1 #900s内有一次修改
save 300 10
save 60 10000
RDB执行原理
bgsave开始时,通过fork主进程得到子进程。子进程共享fork内部数据,完成fork后读取内存写入RDB文件。
fork操作采用copy-on-write技术
- 当主进执行读操作时,访问共享内存
- 当主进程执行写操作时,会拷贝一份数据,再进行写操作
AOF
AOF(Append Only File, 追加文件),Redis处理的每一个写命令都会被记录再AOF文件中。
默认关闭,可以再redis.conf中配置选项。
AOF记录频率
模式 | 频率 | 优点 | 缺点 |
---|---|---|---|
always | 同步 | 可靠性高,几乎不会出现数据丢失现象 | 对性能影响大 |
everysec | 每秒 | 适中 | 最多丢失1s数据 |
no | 由操作系统控制 | 最好 | 可靠性差,可能丢失大量数据 |
由于是记录文件,会比RDB大很多。AOF会记录对于同一个key的多次写操作,但是只有最后一个才有意义。可以使用bgrewriteaof命令来使AOF执行重写操作,确保最少的命令达到相同效果。
也可以通过再conf文件中进行配置,使系统内部自动触发。
# AOF文件比上次增长百分之多少重写
auto-aof-rewrite-percentage 100
# AOF文件大小超过多少重写
auto-aof-rewrite-min-size 64mb
RDB与AOF之间比较
RDB | AOF | |
---|---|---|
持久化 | 定期对内存进行快照 | 记录每次执行的写操作 |
完整性 | 不完整,两次备份之间会存在丢失 | 相对完整,取决于刷新策略 |
文件大小 | 小 | 大 |
宕机恢复 | 快 | 慢 |
数据恢复优先级 | 低 | 高 |
系统资源占用 | 高,大量CPU和内存消耗 | 低,主要为磁盘IO资源,但是重写时会占用大量内存资源 |
使用场景 | 可容忍数分钟的数据丢失,追求更快的启动速度 | 对数据安全性要求更高 |
数据过期策略
Redis中设置有效期后的key,在数据过期后,需要将数据从内存中删除。Redis中有惰性删除和定期删除两种不同的数据过期策略,一般二者配合使用。
惰性删除
需要使用某key时,检查其是否过期,如过期则删除该key,反之直接返回该key对应值。
- 优点:CPU友好,只有需要使用该key时才会进行过期检查
- 缺点: 对内存不友好,过期的key如果不被访问,会一直存在于内存中,永远不会释放。
定期删除
每隔一段时间,从一定数量的数据库中随机选择一部分key进行检查,并删除过期key。
定期删除有两种模式:
SLOW: 定时任务,执行频率默认10hz,每次不超过25msFAST:执行频率不固定,但每次间隔不会低于2ms
优缺点
- 优点: 限制删除操作的时长和频率,减少对CPU影响。定期删除能有效释放过期key占用的内存。
- 缺点: 难以确定删除操作花费的时长和频率。
Redis数据淘汰策略
Redis内存不够时,接收到新的key,按照一定规则删除其中的数据。
8种淘汰策略
- noeviction: 不淘汰任何key,但内存写满时不写入(默认模式)。
- volatile-ttl: 对设置ttl的key,ttl越小越先淘汰
- allkeys-random: 对所有key随机淘汰
- volatile-random: 对所有设置ttl的key随机淘汰
- allkeys-lru: 对所有key,基于LRU算法淘汰
- volatile-lru: 对所有设置ttl的key,按照LRU算法淘汰
- allkeys-lfu: 对所有key,基于LFU算法淘汰
- volatile-lfu: 对设置了ttl的key,基于LFU算法淘汰。
LRU:最近最少使用;LFU:最少频率使用
使用场所
- allkeys-lru: 有明显冷热数据区分
- allkeys-random: 无明显冷热数据区分
- volatile-lru: 有置顶需求,置顶数据不设过期时间
- allkeys-lfu / volatile-lfu: 有短时高频访问数据
分布式锁
用于集群下定时、抢单功能
setnx
redis中自带命令,用于创建不存在的key, 如果key存在,返回null
获取锁
set lock value nx ex 10
释放锁
del key
如何控制有效时长
- 根据业务执行时间预估
- 给锁续期
如何实现
使用redisson实现分布式锁,底层为setnx和lua脚本。通过watchdog和给锁续期的方式来控制时长。
Redisson
实现分布式锁的流程
可重入性质
判断是否是同一个线程,同一个线程可重入。利用hash结构记录线程id和重入次数。
主从一致性
不能解决主从一致性问题,当主节点宕机后,watchdog(看门狗)会选择一个从节点提升为主节点。
红锁
在不止一个实例上加锁,一般加锁实例数量大于等于(n/2+1)。可以一定程度上实现主从一致性,但是实现复杂,并发性差,运维复杂。
如果必须要实现主从一致性,使用基于CP思想的缓存工具,如zookeeper。
Redis集群
主从复制
单节点Redis并发能力有上限,要进一步提高Redis并发能力,需要搭建组从集群,实现读写分离。一般是一主多从,主节点负责写数据,从节点负责读数据。
原理
全量同步
- replid 数据集的标记,id一致说明数据集一致
- offset:偏移量。slave的offset小于master的,说明slave的数据落后于master,需要同步
增量同步
slave重启或者后期数据变化
哨兵机制(sentinel)
Redis提供哨兵机制来实现主从集群的自动故障恢复。
特点
- 监控: sentinel不断检查master和slave是否按预期工作
- 自动恢复: 如果master节点故障,哨兵集群会将一个新的slave节点提升为master节点,并在之后一直以该master节点为主
- 通知redis客户端: 哨兵充当客户端的服务通知来源,当集群发生故障转移时,会将最新信息推送道redis客户端。
作用机制
-
服务状态监控
哨兵基于心跳机制监测服务状态,每个1s向集群中的每个实例发送ping命令 -
判断redis节点下线
主观下线: 某个sentinel节点发现某一个实例未在规定时间内进行响应,认为该实例主观下线。
客观下线: 超过指定数量(quorum,一般超过哨兵节点数量的一半)的哨兵节点都认为某一实例主观下线,则认为该实例已经客观下线。 -
哨兵挑选新主节点规则
1. 判断主节点与从节点断开时间长短,排除超过阈值的从节点
2. 判断从节点的slave-priority值,值越小优先级越高
3. 如果slave-priority值相同,则判断从节点的offset值,offset值越大的优先级越高。
4. 在优先级相同的节点中随机选择一个节点。
如何解决redis集群脑裂
集群脑裂是由于主节点、从节点和sentinel集群不在同一分区,使得sentinel无法通过心跳感知到主节点,通过选举提升了一个新的主节点。同时,由于旧主节点没有真实下线,客户端还在向旧主节点写入数据,新主节点无法同步数据。当网络回复后,sentinel讲旧的主节点降为从节点,再从新主节点中同步数据。这会导致客户端向旧主节点中写入的数据丢失。
解决方法: 可以通过修改redis配置文件,设置最少从节点数量和主从数据同步要求(数据复制和同步的延迟阈值),达不到要求就拒绝请求,由此避免大量数据的损失。
分片集群
用于解决海量数据存储和高并发写的问题
特点
- 集群中有多个master,每个master节点保存不同数据
- 每个master节点有多个slave节点
- master节点之间通过ping值监测彼此的健康状态
- 客户端可以向任意节点发送请求,最终都会被转发到正确节点
redis分片集群如何读写
- 引入哈希槽的概念,redis集群有16384个哈希槽
- 将16384个插槽分配到不同实例
- 读写数据时,利用key的有效部分计算hash,对16384取余,余数作为插槽,最后寻找插槽所属实例。
其它
Redis是单线程的,但是为什么速度快?
- 最主要原因是Redis是纯内存操作,执行速度快
- 采用单线程,避免了不必要的上下文切换可竞争条件
- 使用多路I/O复用模型,非阻塞IO。例如,bgsave和bgwriteaof都是在后台执行,不影响主线程的正常使用,不会产生阻塞
I/O多路复用模型
I/O多路复用模型是指利用单个线程来同时监听多个socket,并在某个Socket可读、可写时得到通知,从而避免了无效的等待,充分利用CPU资源。目前的I/O多路复用模型都是通过epoll实现,它会在通知用户Socket就绪的同时,将已就绪的Socket写入用户空间,不需要挨个遍历Socket来判断是否就绪,提升了性能。
其中Redis的网络模型就是使用I/O多路复用结合时间的处理器来应对多个Socket的请求,比如,提供了连接应答处理器、命令回复处理器,命令请求处理器;在Reids6.0之后,为了更好提升性能,在命令回复处理器中使用了多线程来处理回复事件,在命令请求处理器中,将命令的转换使用了多线程,增加命令转换速度,在命令执行的时候,也依然是单线程。