缓存由于其高性能,支持高并发的特性,在高并发的项目中不可或缺。被大家广泛使用的有Redis,Memcached等。本文主要探讨几种常见的缓存的读写模式,以及如何来保证缓存和数据库的数据一致性。

1. Cache-Aside模式

Cache-Aside可能是项目中最常见的一种模式。它是一种控制逻辑都实现在应用程序中的模式。缓存不和数据库直接进行交互,而是由应用程序来同时和缓存以及数据库打交道。Cache-Aside的名字正体现了这个模式,Cache在应用的一旁(aside)。

读数据时:
第1步:程序需要判断缓存中是否已经存在数据。
第2步:当缓存中已经存在数据(也就是缓存命中,cache hit),则直接从缓存中返回数据
第3步:当缓存中不存在数据(也就是缓存未命中,cache miss),则先从数据库里读取数据,并且存入缓存,然后返回数据
图片.png
写数据时,我们可以有以下两种策略:

第一种写数据策略:
第1步:更新数据库
第2步:更新缓存

但这种策略有线程安全的问题,可能出现缓存和数据库不一致。试想有两个写的线程,线程A和线程B

  1. 线程A写数据库
  2. 线程B后于线程A写数据库
  3. 线程B先写缓存
  4. 线程A后于线程B写缓存
  5. 数据库中四线程B的数据,但是缓存中是线程A的数据,所以缓存中的是脏数据。

要解决线程安全的问题,我们可以加锁,不过实现起来比较麻烦,因此我们不考虑这种写策略,而使用第二种策略。

第二种写数据策略:
第1步:更新数据库
第2步:删除缓存中对应的数据
高并发下如何保证缓存和数据库的数据一致性 - 图2
那么这种写策略会有线程安全的问题吗?有,试想一下有两个线程,线程A读,线程B写

  1. A读数据,由于未命中那么从数据库中取数据
  2. B写数据库
  3. B删除缓存
  4. A由于网络延迟比较慢,将脏数据写入缓存

但是这种情况可能性非常的小,需要同时满足很多条件,近乎不太可能发生,所以我们一般都采用这种写策略。另外可以对缓存中的数据设置合适的过期时间,即使发生的脏数据的情况,也不会发生很长时间。

Cache-Aside模式优点:

  • 缓存仅仅保存被请求的数据,属于懒加载模式(Lazy Loading),和下文的Write-Through模式相比,避免了任何数据都被写入缓存造成缓存频繁的更新。

Cache-Aside模式缺点:

  • 当发生缓存未命中的情况时,则会比较慢,因为要经过三个步骤:查询缓存,从数据库读取,写入缓存。
  • 复杂的逻辑都在应用程序中,如果实现微服务,多个微服务中会有重复的逻辑代码

2. Read-Through/Write-Through模式

2.1 介绍

这种模式中,应用程序将缓存作为主要的数据源,而数据库对于应用程序是透明的,更新数据库和读取数据库的的任务都交给缓存来代理了,所以对于应用程序来说,简单很多。

Read-Through:由缓存配置一个读模块,它知道如何将数据库中的数据写入缓存。在数据被请求的时候,如果未命中,则将数据从数据库载入缓存。
Write-Through:缓存配置一个写模块,它知道如何将数据写入数据库。当应用要写入数据时,缓存会先存储数据,并调用写模块将数据写入数据库。

2.2 优点和缺点

优点:

  • 缓存不存在脏数据
  • 相比较Cache-Aside懒加载模式,读取速度更高,因为较少因为缓存未命中而从数据库中查找
  • 应用程序的逻辑相对简单

缺点

  • 对于总是写入却很少被读取的应用,那么Write-Through会非常浪费性能,因为数据可能更改了很多次,却没有被读取,白白的每次都写入缓存造成写入延迟。

除了Write-Through以外,我们还有另外的两种写模式可以和Read-Through一起来配合使用,分别是Write-Back和Write-Around。

2.3 Write的两种策略

2.3.1 Write-Back策略

又叫做Write-Behind。和Write-Through写入的时机不同,Write-Back将缓存作为可靠的数据源,每次都只写入缓存,而写入数据库则采用异步的方式,比如当数据要被移除出缓存的时候再存储到数据库或者一段时间之后批量更新数据库。
图片.png
优点

  • 写入和读取数据都非常的快,因为都是从缓存中直接读取和写入。
  • 对于数据库不可用的情况有一定的容忍度,即使数据库暂时不可用,系统也整体可用,当数据库之后恢复的时候,再将数据写入数据库。

缺点

  • 有数据丢失的风险,如果缓存挂掉而数据没有及时写到数据库中,那么缓存中的有些数据将永久的丢失了

2.3.2 Write-Around策略

和Write-Through不同,更新的时候只写入数据库,不写入缓存,结合Read-Through或者Cache-Aside使用,只在缓存未命中的情况下写缓存。
图片.png
优点

  • 相比较Write-Through写入的时候的效率较高,如果数据写入后很少被读取,缓存也不会被没用到的数据占满。

缺点

  • 如果数据会写入多次,那么可能存在缓存和数据库不一致

高并发下如何保证缓存和数据库的数据一致性 - 图5