作者:孤独烟
1、先更新数据库,再更新缓存
不推荐,面试答这个稳挂
2、先删缓存,再更新数据库,下次查询再更新缓存
会出现如下问题:
- 请求A想要更新数据库,先删除了缓存
- 请求B过来了,查询缓存没有,于是查询数据库并更新了缓存
- 请求A更新了数据库
- 此时缓存里面存储的还是之前的旧值
这个情况发生的概率还是很大的,因为写操作肯定是比读操作耗时的,所以完全有可能在写的过程中完成了读操作
解决办法:延时双删:在删除缓存并更新数据库之后,等待一定时间后再次删除缓存
那么具体设置多久呢:一般是在读请求耗费时间的基础上加上几百ms,如果数据库配置了主从同步的话,还得再加上同步的时间
这个方案也不是很好,也不建议面试的时候回答,因为延时在高并发情况下是不可接受的
3、先更新数据库,再删缓存,下次查询再更新缓存
当然他也存在问题:
- 假设现在缓存中没有,请求A想要查询于是查询数据库
- 请求B更新了数据库并删除了缓存(因为缓存没有,所以删除啥也没做,只是表达删除这一步走完了)
- 请求A去更新缓存,此时缓存中为旧值
那么这种情况发生的概率大吗,当然不大,因为上述情况想要发生,就要求请求B的写请求执行完了,请求A的读请求还没有执行完。我们都知道,写请求是远慢于读请求的,所以这种情况发生的概率很小
那么更重要的问题来了,删除缓存失败了怎么办
方案一:将需要删除的key发送至消息队列,然后取出来消费,直到删除成功。但是这样会对业务代码造成大量的侵入
方案二:启动一个订阅程序去订阅数据库的binlog,获得需要操作的数据。在应用程序中,另起一段程序,获得这个订阅程序传来的信息,进行删除缓存操作。
- 更新数据库数据;
- 数据库会将操作信息写入binlog日志当中;
- 订阅程序提取出所需要的数据以及key;
- 另起一段非业务代码,获得该信息;
- 尝试删除缓存操作,发现删除失败;
- 将这些信息发送至消息队列;
- 重新从消息队列中获得该数据,重试操作;
备注说明:上述的订阅binlog程序在mysql中有现成的中间件叫canal,可以完成订阅binlog日志的功能。另外,重试机制,这里采用的是消息队列的方式。如果对一致性要求不是很高,直接在程序中另起一个线程,每隔一段时间去重试。