做得很糟糕的缓存会产生不良影响。尽量不要缓存数据;如果你真的需要,确保你的做法正确。
在计算机科学中只有两件艰难的事情:缓存失效和为事物命名。
– Phil Karlton
缓存?
为了确保我们在说同一件事情,当我说“缓存”,我指的是一种加快应用的实践,主要方式是通过记住之前的应答并使用它们对后续同样的请求进行应答,从而掩盖缓慢的依赖。
正如Phil Karlton在他著名的sound byte上简单提到的,缓存是一个很微妙的问题。它包含了我在过去几年遇到的一系列常见的错误,从而可能导致不必要的混乱延迟。
常见错误
以下是我所看到的一些错误,以及我们应该如何应对。
在启动时缓存
当你了解到你的依赖太慢这个事实的时候,这些依赖是难以使用的,因此你在运行时甚至不会尝试他们。在你的应用启动时预先填充缓存而不是查询你依赖的服务,就是在承认你的依赖不适用于你的目标。如果这是一个第三方服务,那你可能没什么办法,但这个技术经常可以被用于避免一定要让你的服务与目标匹配。
从那时起,他们服务太慢的事实被隐藏了。没有理由去优化或者改进解决方案,因为缓存能够保证第二次请求更快;所以为什么要担心呢,对吧?
集成缓存
在SOLID里面的S是什么意思?单一职责。如果你的缓存直接集成到你的服务层,并且你没法不用缓存运行,你绝对是违反了这条原则。这不是我赞美这条原则的美德的地方。
缓存所有事情
这是指忙目地将缓存应用到所有外部调用来保证响应性,而不考虑其影响。更严重的是,这种方法会导致开发者和运营者甚至不知道缓存在发生,而假定底层的服务的可靠性很高,这不是正确的。
无法清空的缓存
有时候缓存的实践可能会依赖像Redis这样的数据存储,它们有用于按需清空缓存的工具。
其余的实践,比如手动分配内存缓存或甚至一些主流框架提供的缓存都不会暴露任何缓存管理工具。这导致运营人员只有一个选择,即重启服务来清空内存。(更糟糕的是,查找缓存对应的文件系统的位置,然后手动清空它。)
我看到,一些发布的软件需要花费比正常来说多几个小时的时间供组员来一层一层处理缓存,将之清空或等待过期,然后处理下一层缓存。在这个过程中这个系统是离线的,因为它甚至无法保证内部一致。
对此最自然的反应是想出一个解决办法,通常是破坏缓存。我们考虑一下这个办法。为你刚刚部署的特性提供一个解决方案,想想需要花费的时间、精力和认知负荷,只是为了补偿你刚刚花费在缓存上的时间、精力和认知负荷。
调试一个缓存系统同时成为了一个挑战,因为全力投入一个困难的调试过程会导致遗漏或忘记一些不相关的事情。3个小时的混乱后,你意识到你还没有测试你做出的特性改变呢。
为性能优化,不要隐藏糟糕的性能
投资探查工具,找出你的应用程序缓慢的原因,并修复它。减少重复的执行路径。整理糟糕的查询执行计划。正确使用索引。如果你在使用S3或blob存储你的数据,你可以使用Redis建立你自己的索引。Redis不仅仅是一个缓存。你可以机智地使用它来获取很多好处,而不涉及缓存带来的问题。
结尾
缓存是一个有用的工具,但它很容易被毫无征兆地滥用。
不要使用缓存,除非不得不使用;寻找你能找到的其他方式。在你使用缓存这个钝刀之前,优化你的应用。
如果你遇到任何缓存带来的基本问题,请告诉我,我可以把问题加到列表中。