数据库|如何保证缓存和数据库的一致性?( 三 )


当然我们前面已经分析过了 , 尽量先操作数据库再操作缓存 , 但是即使这样也还是有可能存在问题 , 解决问题的办法就是延迟双删 。
延迟双删是这样:先执行缓存清除操作 , 再执行数据库更新操作 , 延迟 N 秒之后再执行一次缓存清除操作 , 这样就不用担心缓存中的数据和数据库中的数据不一致了 。
那么这个延迟 N 秒 , N 是多大比较合适呢?一般来说 , N 要大于一次写操作的时间 , 如果延迟时间小于写入缓存的时间 , 会导致请求 A 已经延迟清除了缓存 , 但是此时请求 B 缓存还未写入 , 具体是多少 , 就要结合自己的业务来统计这个数值了 。
2.4 如何确保原子性但是更新数据库和删除缓存毕竟不是一个原子操作 , 要是数据库更新完毕后 , 删除缓存失败了咋办?
对于这种情况 , 一种常见的解决方案就是使用消息中间件来实现删除的重试 。 大家知道 , MQ 一般都自带消费失败重试的机制 , 当我们要删除缓存的时候 , 就往 MQ 中扔一条消息 , 缓存服务读取该消息并尝试删除缓存 , 删除失败了就会自动重试 。 如果小伙伴们还不懂 RabbitMQ 的使用 , 可以在公众号江南一点雨后台回复 rabbitmq , 有免费的视频+文档 。
3. Read-Through/Write-Through这种缓存操作模式 , 松哥印象最深的是在 Oracle Coherence 中有应用 , 不知道小伙伴们有没有用过 Oracle Coherence , 这是一个内存数据网格 , 通过这个 , 应用开发人员和管理人员可快速访问键值数据 , Coherence 可提供集群式低延迟数据存储、多语言网格计算和异步事件流处理 , 从而为客户企业应用赋予超高水平的可扩展性和性能 。
Oracle Coherence 我们就不讨论了 , 我们就来说说 Read-Through 。
3.1 Read-Through这里为了省事 , 我就不自己画图了 , 网上找了一张图片 , 如下:

乍一看 , 很多人感觉这和 Cache-Aside 一样呀 , 没啥区别!是的 , 单看流程是不太容易看到区别 。
Read-Through 是一种类似于 Cache-Aside 的缓存方法 , 区别在于 , 在 Cache-Aside 中 , 由应用程序决定去读取缓存还是读取数据库 , 这样就会导致应用程序中出现了很多业务无关的代码;而在 Read-Through 中 , 相当于多出来了一个中间层 Cache Middleware , 由它去读取缓存或者数据库 , 应用层的代码得到了简化 , 松哥之前写过 Spring Cache 的用法 , 大家回忆下 Spring Cache 中的 @Cacheable 注解 , 感觉像不像 Read-Through?
我画一个简单的流程图大家来看下:

可以看到 , 和 Cache-Aside 相比 , 其实就相当于是多了一个 Cache Middleware , 这样我们在应用程序中就只需要正常的读写数据就行了 , 并不用管底层的具体逻辑 , 相当于把缓存相关的代码从应用程序中剥离出来了 , 应用程序只需要专注于业务就行了 。
3.2 Write-ThroughWrite-Through 其实也是差不多 , 所有的操作都交给 Cache Middleware 来完成 , 应用程序中就是一句简单的更新就行了 , 我们来看看流程:

在 Write-Through 策略中 , 所有的写操作都经过 Cache Middleware , 每次写入时 , Cache Middleware 会将数据存储在 DB 和 Cache 中 , 这两个操作发生在一个事务中 , 因此 , 只有两个都写入成功 , 一切才会成功 。
这种写数据的优势在于 , 应用程序只与 Cache Middleware 对话 , 所以它的代码更加干净和简单 。
4. Write BehindWrite-Behind 缓存策略类似于 Write-Through 缓存 , 应用程序仅与 Cache Middleware 通信 , Cache Middleware 会预留一个与应用程序通信的接口 。
Write-Behind 与 Write-Through 最大的区别在于 , 前者是数据首先写入缓存 , 一段时间后(或通过其他触发器)再将数据写入 Database , 并且这里涉及到的写入是一个异步操作 。 这种方式下 , Cache 和 DB 数据的一致性不强 , 对一致性要求高的系统要谨慎使用 , 如果有人在数据尚未写入数据源的情况下直接从数据源获取数据 , 则可能导致获取过期数据 , 不过对于频繁写入的场景 , 这个其实非常适用 。