前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >聊聊细节 - 你知道缓存的正确打开方式么?(2)

聊聊细节 - 你知道缓存的正确打开方式么?(2)

作者头像
桶哥
发布2019-09-29 14:38:25
3170
发布2019-09-29 14:38:25
举报
文章被收录于专栏:PHP饭米粒PHP饭米粒
上一篇文章:聊聊细节 - 你知道缓存的正确打开方式么?(1) 中介绍了读取缓存时的一些细节,有读就有写,本篇来聊聊,当我们需要更新缓存该怎么做?

当我们通过一些方式:如后台管理系统更新了相关的数据信息,或者用户在一些操作的时候更新了一些数据信息,如果这些信息正好也在缓存里,那一般也需要在更新数据库的时候,也更新缓存.

那更新的流程是什么呢?很多人可能觉的很简单,示例如下?

代码语言:javascript
复制
public function setData($data)
{

    //更新数据库
    $db->update($data);

    //更新缓存
    $redis->set($key, $data);

    return true;
}

嗯,咋一看,没毛病,但真的是这样么?

场景一:如果更新数据库失败了?

结果可有两个

  1. 更新数据库抛异常了,中断,缓存也不影响
  2. 数据库不抛异常,缓存继续更新,结果会导致 数据库和缓存不一致
  3. 缓存更新失败,数据库和缓存不一致

如果是第二种情况,可能就会带来线上的bug了,这时同学可能会有如下的优化:

代码语言:javascript
复制
public function setData($data)
{
    try {
        //更新数据
        $ret = $db->update($data);
        
        if($ret) {
            //数据库更新成功,则更新缓存
            $redis->set($key, $data);
        }

        return true;
    }catch (Throwable $e) {
        //TODO 异常处理
    }
}

针对第三种情况,好像只能不断的重试了.

假设我们操作redis比较正常,但这样就OK了么?

场景二:并发更新的问题?

假如两个请求在并发操作相同的一条数据,由于db的update和cache的set并不是原子性的,所以存在下面的时序可能性:

  1. db 更新了 data1
  2. db 更新了 data2
  3. cache缓存了data2
  4. cache 缓存了 data1

这样就造成了缓存里的数据是老数据(data1),从而导致缓存与数据库不一致

那怎么处理呢?

有同学可能会说,我先set cache ,再 update db呢?

问题或许更严重,db操作失败的概率可能大于 cache 操作的概率,这样可能导致更多数据不一致的情况

如果要严格的要求更新数据库后,缓存能实时的一致更新 ,确实没有完美的的方案,上述场景中,第二种属于逻辑上的bug,碰到概率比较高,所以我们可以优化一下 ,让不一致的情况变的更少

优化一:set cache 变delete cache

代码语言:javascript
复制
public function setData($data)
{
    try {
        //更新数据
        $ret = $db->update($data);
        
        if($ret) {
            //数据库更新成功,则更新缓存
            $redis->delete($key, $data);
        }

        return true;
    }catch (Throwable $e) {
        //TODO 异常处理
    }
}

这样的话,并发更新的问题就不存在了,如以下时序:

  1. db 更新了 data1
  2. db 更新了 data2
  3. cache删除了data2
  4. cache 删除了 data1

都是删除cache,都是会回源到db拉到最新数据

(另一个问题:如果先delete cache再update db, 会有什么问题,欢迎留言)

那这个方式是不是就完美了呢?并不了,还存在一些极端的问题,看如下场景:

  1. 请求1读取缓存
  2. 缓存失效,回源数据库
  3. 请求2 更新db
  4. 请求2 删除cache
  5. 请求1 设置cache

这样也导致cache是老数据,但这种场景概率还是很低的(需满足缓存失效,读取db比update db时间还要长)

优化二:异步更新

可以把缓存更新的放到一个异步对列里,进行异步更新,这种方式会带来几个问题

1、逻辑变得更重

2、又引入了一个新的队列依赖

如果不用消息队列,是否可行?

也是可行的,可以直接通过db的binlog进行更新

总结:

利用缓存,本身就要做好数据不一致的预期,但我们还是可以通过细节的把握,让数据不一致的情况尽可能减少。

最后用一张图对比一下:

(思考下最后一种方式带来什么更好的改进?)

下一篇我们来聊聊用redis做锁的一些细节

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-09-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 PHP饭米粒 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云数据库 Redis
腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档