BT

您是否属于早期采用者或者创新人士?InfoQ正在努力为您设计更多新功能。了解更多

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

| 作者 金灵杰 关注 1 他的粉丝 发布于 2017年1月16日. 估计阅读时间: 不到一分钟 | 硅谷人工智能、机器学习、互联网金融、未来移动技术架构 ,尽在QCon上海2017

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

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

常见错误

启动时缓存

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

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

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

过早缓存

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

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

集成缓存

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