如何保障数据库和Redis缓存的一致性

前言
随着互联网的高速发展,使用互联网产品的人也越来越多,服务不可避免得也会面对越来越复杂的高并发业务场景(如下图),比如热点视频/文章的观看(读场景),热点视频/文章的评论,点赞等(写场景) 。
众所周知,数据库是把数据存储在磁盘上,访问时需要进行IO操作,在请求量小的情况下,耗时还比较低 。但随着数据量的增大,访问量的集中,整个数据库负担加重,响应就会变慢,请求延时上升 。进而导致用户侧的等待时间变长,很大程度上影响了用户体验 。
为了解决这一问题,在整个请求链路上就引入了缓存的策略 。使用 Redis 用来实现应用和数据库之间读写操作的缓冲 。既可以减少数据库 IO,还可以提升数据的 IO 性能 。我们只需要将数据库中的数据copy一份到Redis缓存(毕竟可是号称单机抗10w qps) 。由于Redis的数据是直接存在内存中,因此读写数据速度提升,整体请求响应速度也得到了提高 。
当应用程序需要去读取某个数据的时候,首先会先尝试去 Redis 里面加载,如果命中就直接返回 。如果没有命中,就从数据库查询,查询到数据后再把这个数据缓存到 Redis 里面 。
在这样一个架构中,会出现一个问题,就是一份数据,同时保存在数据库和 Redis 里面,当数据发生变化的时候,需要同时更新 Redis 和 数据库,由于更新是有先后顺序的,并且它不像 数据库 中的多表事务操作,可以满足 ACID 特性 。所以就会出现数据一致性问题 。
在高并发的业务场景下,用户的的操作无非就是读场景和写场景 。
针对读场景 如果Redis中存在用户所需数据,直接返回即可;如果不存在,则试图从db中读取数据,读取完成后,将该数据更新到Redis中,避免下次再次查询或其他用户查询时,仍从db读取 。在高并发场景下,两次请求同时请求Redis,发现数据并不存在后,都会从db中加载数据,然后再重新将数据写入Redis,无论两次请求的写操作谁先谁后,数据本身是一样的,只不过额外进行了一次数据写覆盖 。因此不会产生数据不一致的问题 。
针对写场景 如果Redis中本身不存在缓存数据,则直接修改db中的数据即可,不会产生数据不一致问题 。如果Redis中已存在缓存数据,则需要同时修改db和Redis中的数据,但是二者修改操作的执行必然存在先后顺序 。在高并发的场景下,就有可能产生数据不一致的情况 。
那么针对此中数据不一致问题,就产生了以下两个疑问:
由于Redis中的数据可有可无,那么当数据发生变化时,是对Redis中的数据进行修改,还是直接删除对应的Redis,然后通过后续的读请求再回源db将数据重新写入Redis呢?Redis和db的数据写操作的顺序问题,是先更新Redis,还是先更新db? 问题1,缓存数据淘汰 or 更新 方案1: 淘汰缓存策略方案2: 更新缓存策略综合分析
其实业界一般采用的都是缓存淘汰策略,而非缓存更新策略 。原因有三:
大多数情况下,Redis缓存中的数据并不是完全copy db中的数据,而是将db中多张表的数据进行了重新计算,筛选后更新到Redis 。如果在db某一张表的数据发生了变化的情况下,需要同步重新计算Redis中的值,成本过高 。缓存更新后的新值,无法保证一定会有读请求命中,如果一直没有请求命中该部分冷数据,其实是产生了一定的资源浪费(计算成本+存储成本) 。相较于淘汰缓存策略中,仅有一次读请求cache miss的结果来说,淘汰缓存策略的缺点完全可以容忍 。
举个例子来说,a表中的字段,1分钟更改了100次,如果采用更新缓存策略,则需要计算100次,哪怕1分钟内只有1次读请求;如果采用淘汰缓存策略,如果1分钟内只有1次请求,则只需要计算1次即可,开销大幅度降低 。