乐趣区

关于golang:懂得取舍才是缓存设计的真谛

Previously

前两篇文章(缓存稳定性 和 缓存正确性)跟大家探讨了缓存的『稳定性』和『正确性』,缓存常见问题还剩下『可观测性』和『标准落地 & 工具建设』

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

上周文章发完之后,很多同学对我留的问题进行了深刻的探讨,我置信通过深度的思考,会让你对缓存一致性的了解更加粗浅!

首先,各个 Go 群和 go-zero 群里有很多的探讨,然而大家也都没有找到十分称心的答案。

让咱们来一起剖析一下这个问题的几种可能解法:

  • 利用分布式锁让每次的更新变成一个原子操作。这种办法最不可取,就相当于自废文治,放弃了高并发能力,去谋求强一致性,别忘了我之前文章强调过『这个系列文章只针对非谋求强一致性要求的高并发场景,金融领取等同学自行判断』,所以这种解法咱们首先放弃。
  • 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 系列文章见『微服务实际』公众号

退出移动版