参考文档:一文读懂Redis
1. 数据一致性
链接:数据一致性问题
在分布式环境下,缓存和数据库很容易出现数据一致性问题,如果项目对缓存的要求是强一致性,那就不要使用缓存。
我们只能在项目中使用策略降低缓存与数据库一致性的概率,是无法保障两者的强一致性,一般策略包括缓存更新机制,更新数据库后及时更新缓存、缓存失败时增加重试机制。
2. 缓存雪崩
2.1 什么是缓存雪崩
在了解雪崩溃之前,我们先了解什么是缓存雪崩现象,假设 A 系统每秒需要处理 5000 个请求,但数据库每秒只能处理 4000 个请求,某一天,缓存机器出现了宕机,挂了,这时候所有的请求一下子全部落在数据库上,数据库肯定扛不住,报警挂掉了,这时候如果没有采取缓存设施,数据库又急着用,重新重启数据库,刚重启完成(有可能没启动完),请求又来,数据库立马挂掉。这就是雪崩事件,是 Redis 缓存中最致命问题之一(有一个是穿透)。大家可以看看下图
2.2 解决方案
2.2.1 简单处理方案
- 加锁或者队列方式,加锁或者队列方式保证缓存的单线程(进程)写,从而避免大量并发请求落到底层存储系统上。比如某个 Key 只允许一个线程查询和写缓存,其他线程等待。
- 设置分散缓存失效时间,这个是个比较简单处理方案,就是将缓存失效时间分散开,比如我们在原有失效时间上增加一个随机值,如1~6 分钟随机,尽量让缓存不要同时失效,从而尽量避免缓存雪崩。
- 单独处理热门数据, 对于一些热门数据的持续读取,这种缓存数据也可以采取定时更新的方式来刷新缓存,避免自动失效。
- 从服务器和接口处解决。如果服务和接口都有限流机制,就算缓存全部失效了,但是请求的总量是有限制的,可以在承受范围之内,这样短时间内系统响应慢点,但不至于挂掉,影响整个系统。
2.2.2 高可用方案
出现雪崩事件后不要急不要慌,我们可以在事故前中后三个方面来思考解决方案
- 事故前:redis 高可用方案,主从+哨兵,集群方案,避免全盘崩溃
- 事故中:较少数据库的压力,本地 Ehcache 缓存+限流及降级,避免超过数据库承受压力
- 事故后:做 redis 持久化,一旦 Redis 重启,可从磁盘中快速恢复数据
我们来看看改造后的数据流程,假设用户A发送一个请求,系统先请求本地 Ehcache 是否有数据,如果没有再去 Redis 请求数据,如果没有再去数据库请求数据,获取到数据后同步到 Ehcache 和 redis
限流组件的作用:可以设置每秒请求数次,有多少通过请求,剩余的未通过的可以走降级处理,返回一些默认的值,或者友情提示等默认操作。具体流程可以看看下图:
这样做的好处是:
- 数据库安全:在限流组件可用的情况下,数据库不会挂掉,限流根据确保了每秒多少请求能通过。
- 部分请求可以被处理:数据库没挂,就意味着至少2/5的请求可以被处理掉
- 高峰时期部分请求无法处理到,需要用户多次点击,因为只有2/5的请求被处理,剩下的请求,用户刷不出来界面,需要多点击几下
3. 缓存穿透
缓存穿透是指缓存和数据库中都没有的数据,用户(黑客)不断发起请求,导致请求直接查询数据库,这种恶意行为攻击场景的会直接导致数据库挂掉,数据流程如下图所示
处理这种情况相对比较简单点,这种情况是绕过 redis 或本地缓存直接到达数据库,可以采取以下方案:
- 在请求接口层可以做一些校验,比如用户签权、参数校验,不合法的请求直接 return,
- 还可以针对有效 id 做认证或直接拦截,不符合的 id 直接过滤或采用统一 key 保存到 redis,下次不合法的 id 请求时,直接到缓存中获取数据
- 采用 redis 的高级接口 Bloom Filter,利用高效的数据结构和算法快速判断出你这个 Key 是否在数据库中存在,不存在你 return 就好了,存在你就去查 DB 刷新 KV 再 return
4. 缓存击穿
上面讲的穿透是针对大面积数据请求,那么击穿是针对一点(一个 key)来来导致 redis 异常,但某个 key 是非常热点,请求非常频繁,处于集中式访问现象,当这个 key 失效(过期)时,大量的请求就会击穿了缓存,直接请求数据库,就像在屏障中凿开了一个洞。
不同场景下缓存击穿解决方案
- 数据基本不变:热点数据 value 基本不更新时,可以设置成永不过期
- 数据更新不频繁:缓存刷新流程耗时较少时,可采用 redis、zookeeper 等分布式中间件的分布式互斥锁或者本地互斥锁保证少量的请求能请求到数据库并重新更新缓存,其他的流程等锁释放后才可以访问新缓存
- 数据更新频繁:采用定时线程,在缓存过期前主动重新构建缓存或延长过期时间,保证所有的请求能一直访问缓存
5. Redis 为什么如此快
Redis 官方介绍可以达到 10W+ 的 QPS,这个数据不比 MEMCache 差,而且 Redis 是单进程单线程的模型,完全基于内存的操作,CPU不是 Redis 的瓶颈,Redis 的瓶颈是内存及网络带宽,有以下特点:
- 使用类似于 HashMap 的原理,HashMap 的查询及操作的时间复杂度是 O(1),且绝大多数请求是纯碎的内存操作,数据存在内存中
- 数据结构简单,对数据操作也简单,基于 KV
- 采用单线程操作,避免了不必要的上下文切换及竞争条件,不存在 CPU 切换现象,也就不存在考虑各种锁的问题
- 使用非阻塞 IO,多路复用 IO 模型
6. Redis 过期策略和内存淘汰策略
7. Redis 持久化
Redis 持久化策略有两种:
- RDB:快照形式是直接把内存中的数据保存到一个 dump 的文件中,定时保存,保存策略。
- AOF:把所有的对 Redis 的服务器进行修改的命令都存到一个文件里,命令的集合。Redis 默认是快照 RDB 的持久化方式。
如果非常关心你的数据,但仍然可以承受数分钟内的数据丢失,那么可以额只使用 RDB 持久。
AOF 将 Redis 执行的每一条命令追加到磁盘中,处理巨大的写入会降低Redis的性能,不知道你是否可以接受。
数据库备份和灾难恢复:定时生成 RDB 快照非常便于进行数据库备份,并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度快。
当然了,Redis 支持同时开启 RDB 和 AOF,系统重启后,Redis 会优先使用 AOF 来恢复数据,这样丢失的数据会最少。
8. Redis 主从复制
- 从节点执行
slaveof [masterIP] [masterPort]
,保存主节点信息。 - 从节点中的定时任务发现主节点信息,建立和主节点的 Socket 连接。
- 从节点发送 Ping 信号,主节点返回 Pong,两边能互相通信。
- 连接建立后,主节点将所有数据发送给从节点(数据同步)。
- 主节点把当前的数据同步给从节点后,便完成了复制的建立过程。接下来,主节点就会持续的把写命令发送给从节点,保证主从数据一致性。
9. 哨兵模式
我们先说说主从复制会存在问题:
- 一旦主节点宕机,从节点晋升为主节点,同时需要修改应用方的主节点地址,还需要命令所有从节点去复制新的主节点,整个过程需要人工干预。
- 主节点的写能力受到单机的限制。
- 主节点的存储能力受到单机的限制。
- 原生复制的弊端在早期的版本中也会比较突出,比如:Redis 复制中断后,从节点会发起 psync。
- 此时如果同步不成功,则会进行全量同步,主库执行全量备份的同时,可能会造成毫秒或秒级的卡顿。
哨兵的架构模式如下:
该系统可以执行以下四个任务:
- 监控:不断检查主服务器和从服务器是否正常运行。
- 通知:当被监控的某个 Redis 服务器出现问题,Sentinel 通过 API 脚本向管理员或者其他应用程序发出通知。
- 自动故障转移:当主节点不能正常工作时,Sentinel 会开始一次自动的故障转移操作,它会将与失效主节点是主从关系的其中一个从节点升级为新的主节点,并且将其他的从节点指向新的主节点,这样人工干预就可以免了。
- 配置提供者:在 Redis Sentinel 模式下,客户端应用在初始化时连接的是 Sentinel 节点集合,从中获取主节点的信息。