这是我参与「第五届青训营 」伴学笔记创作活动的第 19 天
-
缓存的适用场景
-
精简为四字就是:读多写少
- 访问量很大,需要使用缓存来承担一部分压力(读多、写少)
- 即时性要求高,能承受一定时间内的数据不一致性。
- 较长时间不会改变的数据,如后台管理的菜单列表,商品分类列表等等。
-
-
使用java自带的序列化缺点
- 它只适用于Java项目,对其他语言编写的项目不兼容,如Go或者PHP
- 在Redis的可视化页面,无法进行较好的展示
- Redis 的默认序列化机制改为
JSON格式,一方面兼容性较高,另一方面在可视化界面也较好查看
-
常用Redis链接工厂比较
-
Lettuce
- SpringBoot 2.0 及之后版本 Redis 的默认连接工厂
Lettuce则完全克服了其线程不安全的缺点;Lettuce是一个可伸缩的线程安全的Redis客户端,支持同步、异步和响应式模式。多个线程可以共享一个连接实例,而不必担心多线程并发问题。- 它基于优秀
Netty NIO框架构建,支持 Redis 的高级功能,如 Sentinel,集群,流水线,自动重新连接和 Redis 数据模型。
-
Jedis
- Jedis 是一个优秀的基于 Java 语言的 Redis 客户端
- Jedis 在实现上是直接连接 Redis-Server,在多个线程间共享一个 Jedis 实例时是线程不安全的,如果想要在多线程场景下使用 Jedis,需要使用连接池,每个线程都使用自己的 Jedis 实例,当连接数量增多时,会消耗较多的物理资源。
-
-
缓存和限流的区别
- 限流会限制最高的访问人数,保证系统的正常运行,不会崩溃。
- 服务降级,会返回用户一些合适的提示信息,对于用户而言,刷新个四五次还是有可能访问成功的。
- 都可以保证系统的运行,不至于让系统崩溃。
-
缓存常见问题
-
缓存穿透
- 缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中,将去查询数据库,但是数据库也没有,如果我们不存储这个空数据,那么持续的访问就会导致我们的数据库压力倍增,此时我们就可以将空结果(null)存入到缓存中并且设置一个较短的过期时间。
- 接口层增加校验,如用户鉴权校验,编写一些特殊数据的校验,预防这样的事故的发生。如将id<=0的查询请求直接拒绝掉。
-
缓存雪崩
-
部署角度:实现 Redis 的高可用,主从+哨兵,Redis集群。
-
应用程序角度:
- 本地缓存 + 限流&降级
- 允许的话,设置热点数据永远不过期
- 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生(但实际而言,这一点很多时候其实不做的,因为如果加上随机时间后,再碰撞又该如何呢?)
-
恢复角度:Redis 的 RDB+AOF组合持久化策略,方便redis宕机后及时恢复数据
-
-
缓存击穿
- 设置热点数据不过期;
- 第一时间去数据库获取数据填充到redis中,并且在请求数据库时需要加锁,避免所有的请求都直接访问数据库,一旦有一个请求正确查询到数据库时,将结果存入缓存中,其他的线程获取锁失败后,让其睡眠一会,之后重新尝试查询缓存,获取成功,直接返回结果;获取失败,则再次尝试获取锁,重复上述流程。
-
-
更新数据库和缓存的常见四种方式
- 先更新缓存,再更新数据库
- 先更新数据库,再更新缓存
- 先删除缓存,再更新数据库
- 先更新数据库,再删除缓存
-
更新数据的两种
- 实际上就是由于第二个更新的数据载体可能更新失败
- 所以为了保证数据正确,应该先将缓存失效即删除后再进行操作
-
删除缓存和更新数据库的两种
-
先删除缓存,再更新数据库
-
先更新数据库再清除缓存
- 实际上这两种还是都会产生并发条件下的数据异常,但是第二种数据异常的概率小,其需要满足以下三个情况
- 时刻1:读请求的时候,缓存正好过期
- 时刻2:读请求在写请求更新数据库之前查询数据库,
- 时刻3:写请求,在更新数据库之后,要在读请求成功写入缓存前,先执行删除缓存操作
-
-
数据库缓存操作总结
- 无论是更新缓存还是删除缓存,一般都是选择先对数据库操作优先,而对缓存操作稍后
- 实际上这属于一种最终一致性的实现,因为如果真的要保证强一致性,正确的做法应该是使用MySQL并且限流降级
-
真正的数据一致性实现
-
使用消息队列异步保证事务
-
首先消息队列在高并发的场景下,可以毋庸置疑的说是一个非常重要的组件啦,所以引入消息队列以及维护消息队列,其实都不能算是额外的负担。
其次消息队列具有持久化,即使项目重启也不会丢失。
最后消息队列自身可以实现可靠性
- 保证消息成功发送,发送到交换机;
- 保证消息成功从交换机发送至队列;
- 消费者端接收到消息,采用手动
ACK确认机制,成功消费后才会删除消息,消费失败则重新投递
-
-
Canal 订阅日志实现
- 可以使用 alibaba 的开源组件
Canal,订阅数据库变更日志,当数据库发生变更时,我们可以拿到具体操作的数据,然后再去根据具体的数据,去删除对应的缓存。当然Canal也是要配合消息队列一起来使用的,因为其Canal本身是没有数据处理能力的。
- 可以使用 alibaba 的开源组件
-
延迟双删
- 解决问题:【先删除缓存,再更新数据库】在读写并发时,会产生缓存是旧数据,而数据库是新数据的问题,这该如何解决呢?
- 在执行完更新数据库的操作后,先休眠一会儿,再进行一次缓存的删除,以确保数据一致性,这也就是市面上给出的主流解决方案--延时双删。
- 首先延迟删除的时间需要大于
1号用户执行流程的总时间即:【1号用户从数据库读取数据+写入缓存】时间 - 但是要说具体是多长,这无法给出一个准确答复,只能经过不断的压测和实验,预估一个大概的时间,尽可能的去降低发生数据不一致的概率罢了。
-