代码评审-如何保证缓存与数据库的读写一致性?

我们从近期代码评审过程中的一段代码,开始探讨缓存和数据库的一致性问题。

探讨前置

一般来说,使用缓存主要为了提升应用性能和降低DB的直接负载,从场景上来说可以接受最终一致性方案,
如果业务场景要求 “缓存+数据库” 必须保持强一致性的话,那么需要使用同步方案,比如排它锁或者队列机制+数据库事务处理
这样的话影响系统可用性,简单情况下可以使用....还是另选方案吧

业务场景

  1. 商品系统的Cache和DB数据(或者RPC操作库存应用),大致包含商品基本属性、库存、价格,
  2. 业务特性:读多写少,同一商品id,基本属性修改并发少,库存修改并发多(下单量大的时候库存操作比较频繁)
  3. 缓存操作模式上,采用Cache Aside Pattern(图片来源
    Cache Aside Pattern
    Cache Aside Pattern
  4. 具体使用情况的伪代码
public Ware getById(long id) {
        Ware ware = Cache.get(id);
        if (ware != null) {
            return ware;
        }
        ware = Db.get(id);
        if(ware != null){
            //缓存时间12小时,根据具体业务调整
            Cache.put(ware,60*60*12);
        }
        return ware;
}

public void update(Param param) {
        Db.update(param);
        Cache.del(param.getId());
}
//Cache
public void del(long id) {
        //异常 静默
        CacheClient.expire("key1"+id, 0);
        CacheClient.expire("key2"+ id, 0);
}

问题

先说说这段代码已经考虑到的问题,也是使用Cache Aside Pattern的好处

  1. Q: update 先执行了更新DB,然后删除cache,为什么不能先删cache,再更新DB?
    A: 考虑到 getById 和 update并发执行,更新先删,但是查询并发放入,导致cache里为脏数据(读多写少使这种情况更容易出现)
  2. Q: update 先执行了更新DB,然后删除cache,为什么不能直接更新cache,这样也可以避免热点数据导致缓存击穿问题?
    A: 1. 考虑到 update 方法本身并发执行,Db.update和Cache.update不是原子操作,会出现先更新DB的后更新cache 时序不一致问题(库存修改存在并发情况,并要求时序一致性)
    A: 2. 商品的缓存数据可能包含多维度比如库存和价格,这儿更新了库存一个字段,如果更新缓存需要查询多表数据聚合放置缓存
    A: 3. 此次更新的商品可能不被查询使用,比如冷数据,采用查的时候缓存起到了懒加载效果
    A: 4. 缓存击穿问题,这儿的更新是基于单个商品,一般情况可忽略,请求量如果特别高,比如秒杀商品需要更改缓存结构和特殊的处理方式(比如版本替换机制,队列扣减机制等等,后续有机会bob再详解...)

未考虑到的问题

  1. 缓存操作使用异常静默,这是查询时候异常降级的思路,但是在更新或者删除的时候使用,因为Db.update和Cache.update不是原子操作, 如果发生异常静默,会导致缓存脏数据的出现,如果出现了脏数据,除了等待过期,还能怎么办?
  2. 缓存删除中我们看到会操作多个key(伪代码中2个),如果其中1个成功,1个失败,partial failures,用户看到的数据一半对,一半错...怎么办?
  3. 在update方法并发执行时,多个请求依然有可能出现时序不一致导致的问题,比如先更新的后放置缓存。
  4. 在update和查询并行的情况下,查询接口从DB中查询出数据准备放置缓存,但是GC暂停,接着update删除缓存,查询恢复放置缓存,极端情况也可能出现脏数据

解决思路

  1. 根据场景设置合适的缓存过期时间,即使不一致,也只是缓存过期时间内的不一致,过期时间越短,数据一致性越高,但是查数据库就会越频繁
  2. 为了保持时序一致性可以采用版本化或者加锁机制(影响吞吐量)
  3. 为了达到最终一致性我们可以引入消息队列来作补偿,在更新后我们不删缓存而是发送消息来异步更新(技术复杂性提高)


    消息补偿
  4. 采用binlog+消息队列(项目目前使用方案),按照时序解析binlog,发送到消息队列中(使用顺序队列,延迟一定时间消费),然后业务系统顺序消费删除缓存,这样能起到最终一致性,顺序一致性
    因为binlog顺序解析并且发送到顺序队列中,所以业务上可以保证顺序一致性,如果删除缓存失败可以继续重试,
    为什么要延迟一定时间消费呢,这是为了保证查询和删除缓存并发会出现脏数据,因为延迟了一定时间,这段时间内查询方法应完成,然后再删除,就提高了一致性的可能
  5. 从业务上将缓存动静隔离(比如将库存作为单独缓存key和基础属性分开处理)、热点隔离(比如秒杀商品采用特殊处理方式)

后续

看到最后我们可以发现与其说是保证了一致性,不如说我们是在 提高缓存一致性

从上面的业务使用场景结合问题分析,我们也可以看出在不同的场景下为了达到不同的效果(一致性要求、吞吐、并发)我们有不同的方案,这些方案的选择离不开场景,但同时我们也要结合技术复杂度和团队技术水平、开发维护成本综合考虑来选择适合团队的方案。

参考

Redis使用总结(一、几点使用心得)
高并发架构系列:Redis缓存和MySQL数据一致性方案详解
Facebook use delete to remove the key
Improving cache consistency

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,657评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,662评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,143评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,732评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,837评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,036评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,126评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,868评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,315评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,641评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,773评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,859评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,584评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,676评论 2 351

推荐阅读更多精彩内容