天天看点

你真的懂缓存使用么?一、缓存模式二、数据一致问题三、缓存穿透

在业务开发中我们经常会使用缓存来减少服务的响应rt,提升服务性能。除了先读缓存-miss后读DB-再写缓存的套路外,其实还有其他很多套路,本文将从使用模式、对数据一致性要求等方面为大家解释其中的细节。

一、缓存模式

1.1 Cache-aside

你真的懂缓存使用么?一、缓存模式二、数据一致问题三、缓存穿透

该模式就是上文中提到的,也是大家用的最多的模式

1.业务先读缓存,如果命中直接返回

      2如果未命中,业务加载db数据放入缓存,然后返回

1.2 Read-through

你真的懂缓存使用么?一、缓存模式二、数据一致问题三、缓存穿透

1.业务读缓存,如果命中直接返回

      2.如果未命中,业务等待:缓存中间件去加载db数据到缓存,然后返回给业务

      和cacheAside的区别是:在缓存失效时,由缓存自己去加载db中数据到缓存,然后再把结果返回给业务

1.2 Refresh-ahead【或者叫Read-ahead】

你真的懂缓存使用么?一、缓存模式二、数据一致问题三、缓存穿透

业务只读缓存中数据,未命中就返回空;这种模式需要另外任务把数据加载到cache,或者定时刷新任务把db中最新的数据更新到cache中。

这种模式的好处也是显而易见的:C端只是简单的从cache中读取数据,既保证了C端代码的简单性,也最大限度的提升了服务的性能。

1.3 Write-through

你真的懂缓存使用么?一、缓存模式二、数据一致问题三、缓存穿透

业务直接更新缓存,并等待缓存中间件同步把数据更新到db;

这种方式和readThrough一样都依赖cache自身的数据同步能力。

在写数据时会有2中情况:写数据没有命中缓存,写完DB数据要不要写到缓存中:流程

Write allocate :    表示需要写cache

      No-write allocate :表示不写cache

下图是:

A write-through cache with no-write allocation

流程

你真的懂缓存使用么?一、缓存模式二、数据一致问题三、缓存穿透

1.4 Write-back [或者叫Write-behind]

你真的懂缓存使用么?一、缓存模式二、数据一致问题三、缓存穿透

这种模式一般应用于高性能的处理场景,将上面write-through的同步写db改成了异步写db来提升服务性能;但是存在一个弊端就是cache中数据存在丢失的风险,另外因为缓存脏数据(需要被更新到DB中的数据)的存在,导致了实现逻辑变得更加复杂。

这个地方大家不妨回忆下InnerDB的实现,是不是似成相识。innerDB中就是将修改后的数据记录通过异步任务刷到磁盘上,同时通过redo/undo log保证数据记录丢失后可以被恢复。

A write-back cache with write allocation
你真的懂缓存使用么?一、缓存模式二、数据一致问题三、缓存穿透

1.5 Write-around

你真的懂缓存使用么?一、缓存模式二、数据一致问题三、缓存穿透

这种方式就是直接读写db,不使用cache。

二、数据一致问题

业务开发中,对数据的读操作次数一般都远远大于数据的写操作次数;也就是说我们大部分情况都是为了减少读数据的时间才引入cache的。所以这就遇到一个不可避免的问题:引入cache后多大程度能忍受cache中数据的不一致?

2.1 能够容忍数据的一段时间不一致

这种情况对于cache的使用,只要保证在写缓存时设置能够容忍的超时时间基本就不会出大问题。一般业务使用缓存模式配置如下:

CacheAside + WriteAround

这也是最常规的方式:

  • 写数据时直接写db,不操作cache;【甚至 db的写/更新可能是另外不同的团队负责操作】
  • 写cache中数据需要设置过期时间t,允许t时间的数据不一致
  • 最好:在写db成功后,invalid cache减少不一致的概率

ReadAhead

  • db数据需要全部加载到cache中
  • 依赖一个任务不断的对cache中数据进行定时刷新
  • 要求数据体量不大

2.2 不容忍数据不一致

分布式事务方式

db和cache中的数据需要时刻保持一致,要求在db和cache数据必须要同时变化,这样就可以用分布式事务式来处理这个问题,例如两阶段提交[prepare,commit/rollback]。但是这需要cache支持xa协议。

缓存一致性协议

缓存一致性协议是cpu中是用于协调多核缓存中数据的一致问题,当然也保证了缓存与主存中数据的一致性。细节大家可以参考如下地址。

MESI协议

https://en.wikipedia.org/wiki/Cache_coherency_protocols_(examples)#MESI_protocol

MOESI协议

https://en.wikipedia.org/wiki/Cache_coherency_protocols_(examples)#MOESI_protocol

2.3 分布式锁方法

通过引入一个分布式锁来保证有数据正在更新时候,只对DB进行读取。锁分为读写锁:读锁之间共享、读写锁排斥。

      读应用端:

  1. 读应用获取到读锁
    1. 读缓存 (命中则返回)
    2. 读db 同时写cache
    3. 释放读锁
  2. 读应用未获取到读锁
    1. 直接读db 返回

     写应用端:

  1. 未获取到写锁,等待重试【最好能够写抢占】
  2. 获取到写锁 【同时读应用此时一定是 进入无法获取读锁的情况】
    1. invalid cache
    2. 写db
    3. 释放锁
你真的懂缓存使用么?一、缓存模式二、数据一致问题三、缓存穿透

三、缓存穿透

如果db中记录不存在,cache因为db没数据所以也没有缓存数据;结果每次业务通过cache查询数据都要从db查一遍【但又没查到数据】,影响了业务查询的时间。

解决方法

使用布隆过滤器

思路是前置挡掉不合理的请求,不让这种请求走到上面查缓存数据的地方。将所有存在key的信息放到布隆过滤器中。业务请求前经过布隆过滤器中,如果不存在,那查询的记录一定也不存在db。

正常的业务中基本是不会使用这种方法,有一些费事的地方:

  1. db中如果存在的数据比较大,需要一个较长的初始化流程
    1. 如果数据有更新/删除时,还要维护额外维护布隆过滤器中

缓存空值

这种方式比较简单,如果db中没有查到数据时,就存一个特定值【表示没有数据】放到缓存中