缓存架构设计

缓存架构设计

需求分析

    缓存是一种提高系统读性能的常见技术,对于读多写少的应用场景,我们经常使用缓存来进行优化。

    例如对于用户的余额信息表account(uid, money),业务上的需求是:

  1. 查询用户的余额,SELECT money FROM account WHERE uid=XXX,占99%的请求
  2. 更改用户余额,UPDATE account SET money=XXX WHERE uid=XXX,占1%的请求

缓存架构设计
    由于大部分的请求是查询,我们在缓存中建立uid到money的键值对,能够极大降低数据库的压力。

    读操作流程

    有了数据库和缓存两个地方存放数据之后(uid->money),每当需要读取相关数据时(money),操作流程一般是这样的:

  1. 读取缓存中是否有相关数据,uid->money
  2. 如果缓存中有相关数据money,则返回【这就是所谓的数据命中“hit”】
  3. 如果缓存中没有相关数据money,则从数据库读取相关数据money【这就是所谓的数据未命中“miss”】,放入缓存中uid->money,再返回

    缓存的命中率 = 命中缓存请求个数/总缓存访问请求个数 = hit/(hit+miss)

    上面举例的余额场景,99%的读,1%的写,这个缓存的命中率是非常高的,会在95%以上。

    问题

    当数据money发生变化的时候:

  1. 是更新缓存中的数据,还是淘汰缓存中的数据呢?
  2. 是先操纵数据库中的数据再操纵缓存中的数据,还是先操纵缓存中的数据再操纵数据库中的数据呢?
  3. 缓存与数据库的操作,在架构上是否有优化的空间呢?

更新缓存 VS 淘汰缓存

  • 什么是更新缓存:数据不但写入数据库,还会写入缓存
  • 什么是淘汰缓存:数据只会写入数据库,不会写入缓存,只会把数据淘汰掉
  • 更新缓存的优点:缓存不会增加一次miss,命中率高
  • 淘汰缓存的优点:简单

选择的关键:更新缓存的复杂度

    情况一:只是简单的把余额money设置成一个值

  • 淘汰缓存的操作为deleteCache(uid)
  • 更新缓存的操作为setCache(uid, money)

    更新缓存的代价很小,此时我们应该更倾向于更新缓存,以保证更高的缓存命中率。

    情况二:如果余额是通过很复杂的数据计算得出来的,例如业务上除了账户表account,还有商品表

        product,折扣表discount

        account(uid, money)

        product(pid, type, price, pinfo)

        discount(type, zhekou)

    业务场景是用户买了一个商品product,这个商品的价格是price,这个商品从属于type类商品,type类商品在做促销活动要打折扣zhekou,购买了商品过后,这个余额的计算就复杂了,需要:

  1. 先把商品的品类,价格取出来:SELECT type, price FROM product WHERE pid=XXX
  2. 再把这个品类的折扣取出来:SELECT zhekou FROM discount WHERE type=XXX
  3. 再把原有余额从缓存中查询出来money = getCache(uid)
  4. 再把新的余额写入到缓存中去setCache(uid, money-price*zhekou)

    更新缓存的代价很大,此时我们应该更倾向于淘汰缓存。

建议

    淘汰缓存操作简单,并且带来的副作用只是增加了一次cache miss,建议作为通用的处理方式。

先操作数据库 vs 先操作缓存

       这个比较主要是针对写操作的,最根本性的问题就是保证数据的一致性,因为操作数据库和缓存不是原子性的,所以一旦中间出现什么错,有可能会导致数据不一致的情况,就要从下面情况来细说:

  1. add数据时,应该先写数据库,还是先写缓存?
  2. update数据时,如果选择淘汰cache,应该先更新数据库还是缓存?
  3. update数据时,如果选择更新cache,应该先更细数据库还是缓存?

add数据时,怎么选择?

       很显然,这是啥先写数据库,然后再写缓存,即使后面写缓存失败,顶多出现一次cache miss,最起码数据库已经持久化了;如果反过来,一旦写数据失败,那么缓存将会是脏数据,除非自己加上写缓存失败时,delete掉数据库的数据,这样太麻烦了。

update数据时,选择淘汰cache,那应该先更新哪个?

       记住这个准则,如果出现不一致,谁先做对业务的影响较小,就谁先执行。下面对两种情况进行业务上的比较:

       假设先写数据库,再淘汰缓存:第一步写数据库操作成功,第二步淘汰缓存失败,则会出现DB中是新数据,Cache中是旧数据,数据不一致。


缓存架构设计
 

       假设先淘汰缓存,再写数据库:第一步淘汰缓存成功,第二步写数据库失败,则只会引发一次Cache miss。

缓存架构设计
    结论

    先淘汰缓存,再写数据库。

update数据时,选择更新cache,那应该先更新哪个?

       细想,无论是先更新数据库再更新缓存,还是先更新缓存再更新数据库,一旦后面的操作失败,都有可能出现数据不一致。所以,根据上面的解决方法,进行了改进:

    情况一:更新或新增缓存代价比较大

  1. 先删除缓存
  2. 然后更新数据库
  3. 等下次查询时,首先cache miss,查询出数据后再增加缓存

    情况二:更新或新增缓存代价小

  1.  先删除缓存
  2. 然后更新数据库
  3. 再将新数据新增到缓存

    情况三:如果缓存服务器很稳定,基本上能够保证修改或查询不会出问题

  1. 更新数据库
  2. 然后更新缓存

       

缓存架构优化

       前面提到的缓存架构有一个缺点:业务方需要同时关注缓存与DB,有没有进一步的优化空间呢?有两种常见的方案,一种主流方案,一种非主流方案。

主流优化方案

       服务化:加入一个服务层,向上游提供帅气的数据访问接口,向上游屏蔽底层数据存储的细节,这样业务线不需要关注数据是来自于cache还是DB。


缓存架构设计
 

    

非主流方案

       异步缓存更新:业务线所有的写操作都走数据库,所有的读操作都总缓存,由一个异步的工具来做数据库与缓存之间数据的同步,具体细节是:

缓存架构设计
 

  1. 要有一个init cache的过程,将需要缓存的数据全量写入cache
  2. 如果DB有写操作,异步更新程序读取binlog,更新cache

在(1)和(2)的合作下,cache中有全部的数据,这样:

  • 业务线读cache,一定能够hit(很短的时间内,可能有脏数据),无需关注数据库
  • 业务线写DB,cache中能得到异步更新,无需关注缓存

缺点

       这样将大大简化业务线的调用逻辑,存在的缺点是,如果缓存的数据业务逻辑比较复杂,async-update异步更新的逻辑可能也会比较复杂。

相关推荐