高并发场景下缓存与数据库双写一致性保障方案
前言
我们接下来说的读请求,指的是 读取数据库+更新缓存操作,写请求包含删除缓存和更新数据库操作。
高并发场景下缓存与数据库双写不一致的问题
(高并发场景下)
- 一个请求更新缓存,先删除缓存成功,但是还没有更新数据库;
- 此时另一个读请求过来了,发现缓存中没有数据,就去数据库中读取,并将数据写入缓存;缓存中此时是更新前的数据,脏数据,但是此时更新数据库操作成功了,导致数据库是新数据,缓存是旧数据,缓存和数据库不一致
通过数据库与缓存 更新、读取操作异步串行化保障一致性
-
更新数据时,根据数据的唯一标识,路由到一个jvm内存队列中
-
读取数据,发现数据不在缓存中,则将读取数据库+更新缓存的操作,根据唯一标识路由以后,也发送到同一个jvm内存队列中
-
搞一个线程池,每个线程处理一个jvm内存队列
-
等队列队列对应的线程完成了上一个操作的数据库修改之后,才会去执行下一个操作,也就是缓存更新的操作,此时会从数据库中读取最新的值,然后写入缓存中
这样通过异步串行化,保证了数据库和缓存双写一致,因为写请求的操作完整地完成后,才会执行读请求操作。
总结部分会出来一张完整的图(提供了阿里云oss的图片地址),来阐述整个方案,所以看到这里如果有点抽象,也不用着急。
方案的复盘
使用缓存的场景,通常是读多写少的。读请求可能会大量地传递过来,可能会阻塞在jvm内存队列中。因此我们需要针对这样的场景对读请求进行优化,以免读请求长时间阻塞在内存队列中。
读请求的优化
-
同一个读请求不需要重复入队
对于同一个读请求,只需要第一个读请求执行即可,其他读请求就hang在那边等待第一个读请求将缓存更新好,把缓存中的数据拿出来返回即可。
-
我们给读请求设置一个超时时间,如果超过了该时间,就直接从数据库中获取旧值返回
-
我们的服务是多实例部署的,所以要做压力测试,判断是否需要加机器
写请求过多,导致读请求长时间阻塞:去看看系统最繁忙的时候,内存队列可能会积压多少更新操作,可能会导致最后一个更新操作对应的读请求,会hang多少时间,如果读请求在200ms返回,如果你计算过后,哪怕是最繁忙的时候,积压10个更新操作,如果最多等待200ms,就可以接受。
思考一下:我们面向的是读多写少,因此写请求不会过多,其实还好。
方案的前提
要保证同一个业务数据,它的读请求和写请求 被路由到同一个服务实例上来,不然jvm内存队列是单机的,整个方案就裂开了。我们假设是通过nginx路由过来的,那简单,用lua脚本写一些lua代码,根据业务数据的id 做hash路由,就能保证了。
总结
本文是我学习 《亿级流量电商详情页课程》的笔记内容再进一步总结出来的,基本上就是通过JVM内存队列的形式来实现的。我是把整个电商详情页系统抛在一边,没有讲述任何业务逻辑,但个人觉得思想应该表达出来了,如果您觉得表述得还不够清楚,可以结合下图来进一步理解。
下载链接:liutianruo-2019-go-go-go.oss-cn-shanghai.aliyuncs.com/%E4%BA%BF%E…
参考内容
- 中华石杉 亿级流量电商详情页课程