懂得取舍才是缓存设计的真谛

Previously

前两篇文章(缓存稳定性缓存正确性)跟你们讨论了缓存的『稳定性』和『正确性』,缓存常见问题还剩下『可观测性』和『规范落地&工具建设』git

  • 稳定性
  • 正确性
  • 可观测性
  • 规范落地和工具建设

上周文章发完以后,不少同窗对我留的问题进行了深刻的讨论,我相信通过深度的思考,会让你对缓存一致性的理解更加深入!github

首先,各个 Go 群和 go-zero 群里有不少的讨论,可是你们也都没有找到很是满意的答案。redis

让咱们来一块儿分析一下这个问题的几种可能解法:sql

  • 利用分布式锁让每次的更新变成一个原子操做。这种方法最不可取,就至关于自废武功,放弃了高并发能力,去追求强一致性,别忘了我以前文章强调过『这个系列文章只针对非追求强一致性要求的高并发场景,金融支付等同窗自行判断』,因此这种解法咱们首先放弃。
  • A删除缓存 加上延迟,好比过1秒再执行此操做。这样的坏处是为了解决这种几率极低的状况,而让全部的更新在1秒内都只能获取旧数据。这种方法也不是很理想,咱们也不但愿使用。
  • A删除缓存 这里改为设置一个特殊占位符,并让 B设置缓存 用 redis 的 setnx 指令,而后后续请求遇到这个特殊占位符时从新请求缓存。这个方法至关于在删除缓存时加了一种新的状态,咱们来看下图的状况缓存

    是否是又绕回来了,由于A请求在遇到占位符时必须强行设置缓存或者判断是否是内容为占位符。因此这也解决不了问题。服务器

那咱们看看 go-zero 是怎么应对这种状况的,咱们选择对这种状况不作处理,是否是很吃惊?那么咱们回到原点来分析这种状况是怎么发生的:微信

  • 对读请求的数据没有缓存(压根没加载到缓存或者缓存已失效),触发了DB读取
  • 此时来了一个对该数据的更新操做
  • 须要知足这样的顺序:B请求读DB -> A请求写DB -> A请求删除缓存 -> B请求设置缓存

咱们都知道DB的写操做须要锁行记录,是个慢操做,而读操做不须要,因此此类状况相对发生的几率比较低。并且咱们有设置过时时间,现实场景遇到此类状况几率极低,要真正解决这类问题,咱们就须要经过 2PC 或是 Paxos 协议保证一致性,我想这都不是你们想用的方法,太复杂了!架构

作架构最难的我认为是懂得取舍(trade-off),寻找最佳收益的平衡点是很是考验综合能力的。固然,若是你们有什么好的想法,能够经过群或者公众号联系我,感谢!并发

本文做为系列文章第三篇,主要跟你们探讨『缓存监控和代码自动化』框架

缓存可观测性

前面两篇文章咱们解决了缓存的稳定性和数据一致性问题,此时咱们的系统已经充分享受到了缓存带来的价值,解决了从零到一的问题,那么咱们接下来要考虑的是如何进一步下降使用成本,判断哪些缓存带来了实际的业务价值,哪些能够去掉,从而下降服务器成本,哪些缓存我须要增长服务器资源,各个缓存的 qps 是多少,命中率多少,有没有须要进一步调优等。

上图是一个服务的缓存监控日志,能够看出这个缓存服务的每分钟有5057个请求,其中99.7%的请求都命中了缓存,只有13个落到DB了,DB都成功返回了。从这个监控能够看到这个缓存服务把DB压力下降了三个数量级(90%命中是一个数量级,99%命中是两个数量级,99.7%差很少三个数量级了),能够看出这个缓存的收益是至关能够的。

但若是反过来,缓存命中率只有0.3%的话就没什么收益了,那么咱们就应该把这个缓存去掉,一是能够下降系统复杂度(如非必要,勿增实体嘛),二是能够下降服务器成本。

若是这个服务的 qps 特别高(足以对DB形成较大压力),那么若是缓存命中率只有50%,就是说咱们下降了一半的压力,咱们应该根据业务状况考虑增长过时时间来增长缓存命中率。

若是这个服务的 qps 特别高(足以对缓存形成较大压力),缓存命中率也很高,那么咱们能够考虑增长缓存可以承载的 qps 或者加上进程内缓存来下降缓存的压力。

全部这些都是基于缓存监控的,只有可观测了,咱们才能作进一步有针对性的调优和简化,我也一直强调『没有度量,就没有优化』。

如何让缓存被规范使用?

了解 go-zero 设计思路或者看过个人分享视频的同窗可能对我常常讲的『工具大于约定和文档』有印象。

对于缓存来讲,知识点是很是繁多的,每一个人写出的缓存代码必定会风格迥异,并且全部知识点都写对是很是难的,就像我这种写了那么多年程序的老鸟来讲,一次让我把全部知识点都写对,依然是很是困难的。那么 go-zero 是怎么解决这个问题的呢?

  • 尽量把抽象出来的通用解决方法封装到框架里。这样整个缓存的控制流程就不须要你们来操心了,只要你调用正确的方法,就没有出错的可能性。
  • 把从建表 sql 到 CRUD + Cache 的代码都经过工具一键生成。避免了你们去根据表结构写一堆结构和控制逻辑。

这是从 go-zero 的官方示例 bookstore 里截的一个 CRUD + Cache 的生成说明。咱们能够经过指定的建表 sql 文件或者 datasource 来提供给 goctl 所需的 schema,而后 goctlmodel 子命令能够一键生成所需的 CRUD + Cache 代码。

这样就确保了全部人写的缓存代码都是同样的,工具生成能不同吗?:P

未完待续

本文跟你们一块儿讨论了缓存的可观测性和代码自动化,下一篇我来跟你们分享一下咱们是怎么提炼和抽象缓存的通用解决方法的,你们能够预先了解一下聚族索引的设计,本身先思考一下缓存该如何作,毕竟通过深度思考,你的理解会更加深入嘛!

全部这些问题的解决方法都已包含在 go-zero 微服务框架里,若是你想要更好的了解 go-zero 项目,欢迎前往官方网站上学习具体的示例。

视频回放地址

ArchSummit架构师峰会-海量并发下的缓存架构设计

项目地址

https://github.com/tal-tech/go-zero

欢迎使用 go-zero 并 star 支持咱们!

微信交流群

关注『微服务实践』公众号并点击 交流群 获取社区群二维码。

go-zero 系列文章见『微服务实践』公众号
相关文章
相关标签/搜索