缓存一定要用redis吗

缓存一定要用redis吗

「缓存」,写程序的肯定都用过。

使用缓存的目的只有2个:提升速度、减轻数据库压力。

使用缓存虽然也很简单的,但还是有很多细节可以推敲。

经典的缓存更新策略

我就先摆一些经典的缓存模式,其实我们有时候都不知道它是一个模式的时候,都已经在用了。

一般有以下几种:

  • Cache Aside
  • Read/Write Through
  • Write Behind

名字看上去很专业,但其实原理都比较简单,大家有时间可以去网上搜一下,我很多也是搜回来的,下面我只挑里面重点讲扼要。

Cache Aside

Cache-Aside-Design-Pattern-Flow-Diagram-e1470471723210.png
Updating-Data-using-the-Cache-Aside-Pattern-Flow-Diagram-1-e1470471761402.png

上面就是整个更新缓存的流程图,重点我们可以看下面的图更新缓存的方式:

  • 先更新数据库
  • 再把缓存的数据失效

缓存失效了后,等下次Application再请求数据的时候,就会回到第一张图,如此循环。

其实这个就是我们经常使用的更新缓存逻辑,但这里存在一个高并发情况下的问题。

  • 缓存初始化为空,A请求数据,就去数据库查询到了数据,并准备放到缓存。即执行了上图的1、2,3还没执行。
  • 此时B请求并发,更新数据库,并使缓存为空。也就是下图的1、2。
  • 最后A请求执行步骤3,设置缓存并返回结果。这时候其实缓存里面的数据是「脏数据」,因为B的更新并没有读到缓存里。

然而高并发情况触发条件比较苛刻,所以如果对缓存再加上一个适当的TTL(Time To Live,过期时间),缓存数据的准确性要求不高,那问题就不大。

Read/Write Through

Read/Write Through 更新逻辑如下所示

dcc0b5c52ce691ac9fbd591f0d5a4a82_1440w.jpg

咋看其实跟上面Cache Aside没什么区别,但架构上多了一个Cache的服务,Cache服务层充当写入、更新、读取缓存的服务。

整体数据流就是:应用 > Cache 服务 > 数据库。所以跟Cache Aside最大的区别在于:

  • Cache Aside是直接应用跟数据库打交道,缓存的更新逻辑有应用自己完成。
  • Read/Write Through 在应用和数据库之间多了一个“Cache 服务”,从而应用和数据库解耦。
  • 解耦的好处在于,应用再也不需要关注缓存的更新逻辑,每次读取、更新数据都是跟Cache 服务交互;Cache 服务专注于缓存的更新以及数据库的同步即可。

Write Behind

Write Behind 更新逻辑如下所示

293f4fd60aa7cc24c7b233d2f543ba21_1440w.jpg

截取《缓存更新的套路》的一段话解释

Write Back套路,一句说就是,在更新数据的时候,只更新缓存,不更新数据库,而我们的缓存会异步地批量更新数据库。这个设计的好处就是让数据的I/O操作飞快无比(因为直接操作内存嘛 ),因为异步,write back还可以合并对同一个数据的多次操作,所以性能的提高是相当可观的。

但是,其带来的问题是,数据不是强一致性的,而且可能会丢失,但软件设计没有完美,都是权衡取舍。

另外,Write Back实现逻辑比较复杂,因为他需要track有哪数据是被更新了的,需要刷到持久层上。操作系统的write back会在仅当这个cache需要失效的时候,才会被真正持久起来,比如,内存不够了,或是进程退出了等情况,这又叫lazy write。

如果中间件服务挂掉呢

其实可以看出,上面几个缓存使用策略,逻辑都是 Cache Aside 基础上强化改造。那我们回到 Cache Aside 的逻辑看,除了上面说的 “高并发” 问题,其实还有其他问题。

回忆一下,在Cache Aside的模式中,我们在更新缓存:

  • 是先更新完数据库。
  • 再直接把缓存失效;
  • 待下次读取缓存数据的时候,再从数据库加载新数据;
  • 加载到缓存中,再返回数据。
image-20210125194734959.png

一般来说,程序部署标准都是用各种中间件,比如一个程序用了缓存,就用redis;数据库,就用mysql。那么一个完整的程序,就有程序应用实例、redis、数据库,当三个服务都正常时,就会提供正常响应,但如果redis或者数据库访问异常呢。

redis 异常

如果redis异常的话,一般程序会有2种表现:

  • 程序没cache到redis异常,或者没有做处理,程序不返回数据直接报错。redis 属于数据缓存,缓存崩溃导致整体程序不可用,对于某些高可用场景来说未必是可以接受的;没了缓存性能、效率、吞吐量会降低,可以限流提供有限服务,但不能完全不提供。
  • 程序cache到redis异常,并且跳过了redis,每一次的抓取数据都要访问DB,这个比上面的表现要好,因为起码对外仍然提供了服务;但如果没有有效的限流,高并发下就会造成「缓存击穿」,大量查询可能直接把数据库搞挂。

数据库异常

数据库异常本身就是比较大的故障,整体程序应该大部分都不能用。

但有时偶尔的网络抖动导致访问失败也是有可能的,那如果刚好缓存被设置为失效,又请求数据库失败的这种情况下,就会返回失败。

这种情况下,如果redis缓存还有旧数据,就可以暂时返回给用户。

真的要用中间件吗

理论上来说,redis和数据库作为中间件而言,一般都是具有较高可用性,比如腾讯云上的redis就达到99.5%。

image-20210210091338201.png

但中间件终归是一种共享服务,除了本身的稳定性,还需要依赖网络的稳定性,相互请求也需要带宽。

回到「缓存」这个主题上来,要增加使用缓存的稳定性,还有一个可以考虑的点,就是是否真的需要使用redis。因为少一个外部依赖点,程序性能相对稳定性可控。

假如程序里面的缓存符合以下特性:

  • 缓存数据不需要锁,多个实例不需要共享、统计。
  • 缓存数据的量是可以统计到并预计到内存的使用量,要避免本地内存使用影响程序本身的服务。
  • 缓存不需要考虑持久化,进程重启后可以随时重新获取。

符合以上特性,其实建议还是使用本地缓存,缺点就是增加了代码复杂度,要考虑过期、线程安全等;但对应也是有措施,可以用一些框架来缓解代码复杂度的问题。

本地缓存另外一个问题就是缓存更新,因为在分布式系统下,同一个应用的缓存就散落到各个实例上,如何主动统一刷新多态进程实例的缓存也是一个问题。

你可能感兴趣的:(缓存一定要用redis吗)