目录
1、背景
2、缓存读写模式
2.1、Cache-Aside Pattern(旁路缓存模式)
2.2、Read-Through/Write-Through(读写穿透模式)
2.3、Write Behind Pattern(异步缓存写入)
3、数据不一致的几种场景
3.1、先删缓存,再更新数据库
3.2、先更新数据库,再删缓存
4、解决方案
4.1、延时双删
4.2、消息队列
4.3、进阶版消息队列(基于订阅binlog的同步机制)
4.4、数据对比
5、为什么是删除,而不是更新缓存?
1、背景
高并发场景下,如果让所有的请求都直接访问数据库,大概率数据库是扛不住这么大的并发压力的。所以,通常情况下,我们会使用Redis做一个缓冲操作,请求来时,先去查询Redis缓存,如果能在缓存中查到数据,则直接返回;如果查不到,再去查询数据库。
读场景下,Redis和数据库在数据一致性层面没什么问题,但是,在涉及到数据更新操作时:数据库和缓存更新,就会存在数据一致性问题。
2、缓存读写模式
2.1、Cache-Aside Pattern(旁路缓存模式)
适用于读多写少的场景。使用该模式的系统对缓存失效具有一定的弹性,即使缓存集群宕机,系统仍然可以通过访问数据库获取到数据,只不过这种情况下可能会降低响应时间,严重的话会搞崩数据库。
读流程:
- 读的时候,先读缓存,缓存命中的话,直接返回数据
- 缓存没有命中的话,就去读数据库,从数据库取出数据,放入缓存后,同时返回响应。
写流程:
- 更新的时候,先更新数据库,然后再删除缓存。
2.2、Read-Through/Write-Through(读写穿透模式)
Cache-Aside模式下,业务系统需要同时维护缓存和数据库,以此来保证缓存和数据库的数据一致性。
Read/Write Through模式下,提供一个缓存服务,由缓存服务维护缓存与数据库的数据一致性,业务系统只需要与缓存服务进行读写即可,不必再关心缓存与数据库的一致性问题。
读流程:
- 业务系统发读请求给缓存服务
- 缓存服务:从缓存读取数据,读到直接返回
- 缓存服务:如果读取不到的话,从数据库加载,写入缓存后,再返回响应。
写流程:
- 业务系统发写请求给缓存服务
- 缓存服务:更新数据库
- 缓存服务:更新缓存
2.3、Write Behind Pattern(异步缓存写入)
类比Read-Through/Write-Through模式,都是由一个缓存服务负责缓存和数据库的读写。不同点在于,Write Behind Pattern模式执行写操作时,只更新缓存,不直接更新数据库,通过异步批量的方式写入数据库。
数据存储的写性能很高,但数据的一致性变差,极端场景下可能会丢失数据。
适用于变更频率很高,但对一致性要求不太高的业务场景。
3、数据不一致的几种场景
由于数据库和缓存(比如Redis)是两个组件,所以不可能在一个事务中处理,那么数据的更新就会存在一个先后问题。
3.1、先删缓存,再更新数据库
原始数据:缓存中有key = value1,数据库有key = value1。
- 线程1删除缓存key成功
- 线程2读取数据,缓存key已被删除,从数据库读取key=value1,并写入缓存key=value1
- 线程1更新数据库key = value2
此时,缓存中key=value1,数据库中key=value2,数据不一致。
3.2、先更新数据库,再删缓存
原始数据:缓存中有key = value1,数据库有key = value1。
- 线程1更新数据库key = value2;
- 线程1删除缓存key,删除失败。
此时,缓存中key=value1,数据库中key=value2,数据不一致。
4、解决方案
4.1、延时双删
在写库前后都进行redis.del(key)操作,并且设定合理的超时时间。
- 删除缓存
- 更新数据库
- 休眠500毫秒(休眠时间由读业务逻辑耗时决定,确保读请求结束后,写请求可以删除读请求造成的缓存脏数据)
- 再次删除缓存
- 读请求更新缓存时,给缓存设置超时时间
4.2、消息队列
先更新数据库,成功后往消息队列发消息,消费到消息后再删除缓存,借助消息队列的重试机制来实现,达到最终一致性的效果。
但是,引入消息队列后,就需要考虑消息中间件的维护问题、消息丢失问题、消息延迟等问题。
4.3、进阶版消息队列(基于订阅binlog的同步机制)
MySQL binlog增量订阅消费+消息队列+增量数据更新到redis。
- 读Redis:热数据基本都在Redis
- 写MySQL:增删改都是操作MySQL
- 更新Redis数据:MySQ的数据操作binlog,来更新到Redis
使用canal订阅mysql的binlog,一旦MySQL中产生了新的读写、更新、删除操作,通过消息队列,就可以把binlog相关的消息推送至Redis,Redis再根据binlog中的记录,对Redis进行更新。
4.4、数据对比
通过数据对比模块,发现差异,差异报警,自动修复,定时将mysql数据同步到redis中。
5、为什么是删除,而不是更新缓存?
如果是更新的话,那就是先更新数据库,再更新缓存。
举个例子:如果数据库1小时内更新了1000次,那么缓存也要更新1000次,但是这个缓存可能在1小时内只被读取了1次,那么这1000次的更新有必要吗?
反过来,如果是删除的话,就算数据库更新了1000次,那么也只是做了1次缓存删除,只有当缓存真正被读取的时候才去数据库加载。
以上内容为个人学习理解,如有问题,欢迎在评论区指出。
部分内容截取自网络,如有侵权,联系作者删除。