BT

你的观点很重要! 快来参与InfoQ调研吧!

程序设计中,如何用好缓存?

| 作者 金灵杰 关注 2 他的粉丝 发布于 2017年1月16日. 估计阅读时间: 5 分钟 | ArchSummit社交架构图谱:Facebook、Snapchat、Tumblr等背后的核心技术

A note to our readers: As per your request we have developed a set of features that allow you to reduce the noise, while not losing sight of anything that is important. Get email and web notifications by choosing the topics you are interested in.

在文章开头,我们首先约定,本文说的缓存,是通过记录和保存应用程序依赖的响应慢模块返回值,在后续请求中直接使用这些数据以提高响应速度的设计。

缓存是优化系统性能最常用的方式之一,通过在耗时部件(如数据库)之前添加缓存,可以减少实际调用次数,降低响应时间。但是在引入缓存之前,务必三思而后行。本文通过一些引入缓存时的常见错误,对如何用好缓存提供了一些建议。

常见错误

启动时缓存

有时候,我们会发现应用程序启动很慢,最终发现是其中一个依赖的服务响应时间很长,这时该怎么办?

通常来说,遇到这类问题,说明这个依赖服务无法满足需求。如果这是一个第三方服务,控制权不在自己手上,这时我们可能会引入缓存。

此时引入缓存的问题,是缓存失效策略难以生效,因为缓存设计的本意就是尽可能少的请求依赖的服务。

过早缓存

这里提到“早”,不是应用程序的生命周期,而是开发的周期。有的时候我们会看见,一些开发者在开发初期就已经估算出系统瓶颈,并引入缓存。

事实上,这样的做法掩盖了可能进行性能优化的点。反正到时候这个服务的返回值会被缓存住,我干嘛还要花时间去优化这部分代码呢?

集成缓存

SOLID原则中的“S”代表——单一功能原则(Single responsibility principle)。当应用程序集成缓存模块之后,缓存模块和服务层就有了强耦合,无法在没有缓存模块的参与下单独运行。

缓存所有内容

有的时候为了降低响应延迟,可能会盲目的对外部调用都加上缓存。事实上,这样的行为很容易让开发者和维护者无法意识到缓存模块的存在,最终对底层依赖模块的可靠性做出了错误的评估。

级联缓存

缓存所有内容,或者只是缓存了大部分内容,可能会导致缓存数据中包含其他缓存数据。

如果应用程序中包含这种级联的缓存结构,可能导致的情况是缓存失效时间不可控。最上层的缓存需要等每一级缓存都失效更新之后,最终返回的数据才会彻底更新。

不可刷新缓存

通常情况下,缓存中间件会提供一个刷新缓存的工具。例如Redis,维护人员可以通过其提供的工具,删除部分数据,甚至刷新整个缓存。

但是,一些临时缓存,可能不会包含这样的工具。例如简单的将数据保存在内容中的缓存,通常不会允许外部工具来修改或者删除缓存内容。这时,如果发现缓存数据异常,维护人员只能采取重启服务的方式,这将大大增加运维成本和响应时间。更有甚者,一些缓存可能会将缓存内容写在文件系统中进行备份。此时除了重启服务,还需要确保应用程序启动之前删除文件系统上的缓存备份。

缓存带来的影响

上面提到了引入缓存可能导致的常见错误,这些问题在无缓存系统中通过不会考虑。

部署一个重度依赖缓存的系统,可能会因为等待缓存失效而花费大量时间。例如通过CDN缓存内容,系统发布之后去刷新CDN配置、CDN缓存的内容,可能需要几个小时。

另外,出现性能瓶颈优先考虑缓存,会导致性能问题被掩盖,得不到真正的解决。事实上,很多时候调优代码花费的时间,和引入缓存组件不会相差太多。

最后,对于包含缓存组件的系统,调试成本会大大增加。经常会发生追踪半天代码,结果数据来自缓存,和实际逻辑上应该依赖的组件没有任何关系。同样的问题也可能出现在执行了所有相关测试用例之后,修改到的代码实际没有被测试到。

如何用好缓存?

放弃缓存!

好吧,很多时候缓存是无法避免的。基于互联网的系统,很难完全避免使用缓存,甚至连http协议头,都包含缓存配置:Cache-Control: max-age=xxx

了解数据

如果要将数据访问缓存,首先需要了解数据更新策略。只有明确了解数据何时需要更新,才能通过If-Modified-Since头来判断客户端请求的数据是否需要更新,是简单返回304 Not Modified响应让客户端复用之前的本地缓存数据,还是返回最新数据。另外,为了更好利用http协议中的缓存,建议给数据区分版本,或者利用eTag来标记缓存数据的版本。

优化性能而不是使用缓存

前文提到过,使用缓存往往会将潜在性能问题掩盖。尽可能利用性能分析工具,找到应用程序响应缓慢的真实原因并且修复它。例如减少无效代码调用,根据SQL执行计划优化SQL等。

总结

缓存是非常有用的工具,但极易被滥用。不到最后一刻不要使用缓存,优先考虑使用其他方式优化应用程序性能。

如果读者还需要其他因为缓存引起的问题,请在下方留言,以便将这些问题添加到列表中。


感谢郭蕾对本文的审校。

给InfoQ中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家通过新浪微博(@InfoQ@丁晓昀),微信(微信号:InfoQChina)关注我们。

评价本文

专业度
风格

您好,朋友!

您需要 注册一个InfoQ账号 或者 才能进行评论。在您完成注册后还需要进行一些设置。

获得来自InfoQ的更多体验。

告诉我们您的想法

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

等于没说 by Sun Yuxing

等于没说

是笔记吗? by 齐 纳尔多

是笔记吗?

Re: 等于没说 by LIANG Terry

虽然这么说很没礼貌,但我不得不同意你的说法。。。

Re: 等于没说 by Zhang Ethan

虽然这么说很没礼貌,但我不得不同意你的说法。。。

Re: 等于没说 by Chou Kinga

虽然这么说很没礼貌,但我不得不同意你的说法。。。

Re: 等于没说 by liu dong

虽然这么说很没礼貌,但我不得不同意你的说法。。。

Re: 等于没说 by luxian lin

虽然这么说很没礼貌,但我不得不同意你的说法。。。

等于没说 by 于 立

说的挺多,有用的一句没有

Re: 等于没说 by Black Justin

虽然这么说很没礼貌,但我不得不同意你的说法。。。

Re: 等于没说 by Su XianBin

虽然这么说很没礼貌,但我不得不同意你的说法。。。

Re: 等于没说 by wh imco

说的挺好的,最后一刻用缓存,我们的系统中,就有很多半吊子程序员开始就用进程内缓存,结果在做集群的时候,发现大量缓存的对象无法序列化,就意味着无法使用分布式缓存,超级崩溃,只能做集群的进程内缓存同步处理,
如果一开始只是简单的访问数据库,也不会搞到最后,消息队列,二级缓存一堆的复杂设计

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

11 讨论

登陆InfoQ,与你最关心的话题互动。


找回密码....

Follow

关注你最喜爱的话题和作者

快速浏览网站内你所感兴趣话题的精选内容。

Like

内容自由定制

选择想要阅读的主题和喜爱的作者定制自己的新闻源。

Notifications

获取更新

设置通知机制以获取内容更新对您而言是否重要

BT