技术开发 频道

也说缓存

【IT168技术文档】

    看了蝈蝈俊.net的《理解缓存》,觉得真的是一个对于web applcation 缓存应用的好文,难得的是覆盖了冰山海面下的部分。我现在做的应用可以说和缓存打的交道也不少(不过不是web应用),也写些东西来分享给大家。

    1.缓存是什么?
    在我看来,缓存是通过存储中间结果,缩短访问路径来减少开销,提高性能的方法。这个概括未必最科学全面,也不够具体。我们来看看一个http 动态页面访问的例子:

    访问路径是 : 数据库->应用数据集->内存对象->动态页面->HTTP服务器->用户浏览器

    一个简单的访问,中间经过了多个环节,我们称这些环节为访问路径,我们来看看哪些地方可以采用缓存:

    HTTP服务器->用户浏览器,大家都知道浏览器都有本地缓存,浏览过的页面图片脚本等都会根据http header还有html的相关指令临时保存在本地硬盘里面,假如再次访问,访问路径就变成了"本地硬盘缓存->用户浏览器", 浏览的环节大幅度减少,性能也提高了。在这个环节,经常还使用带缓存的代理服务器来提高性能。

    动态页面->HTTP服务器,这里有多种方式, 比如动态页面静态化,目前大量的大型网站使用这种方式。还有WEB服务器根据一定规则缓存整个动态页面,比如asp.net的Page Cache。这里的访问路径变成了"缓存页面->HTTP服务器->用户浏览器"
本地数据集->内存对象->动态页面。常见的就是缓存数据集还有对象,这个是ASP.NET cache里面相对浓墨重彩的部分,也是Memcached发力的侧重点, 也就不多说了。
    数据库->应用数据集,不少数据库实现都有查询缓存

    这里缓存都在访问路径中的环节存储了中间结果,用来减少相应的开销

    2.缓存本身的开销
    缓存本身也是有性能开销的,一种是将内存存储到缓存中开销,一种是将内容取出来的开销。另外,缓存往往还要付出空间上的开销。另外还要付出系统复杂度的开销,这增加了开发和维护成本。

    大家也听说过IE缓存太大了或者是文件系统碎片太多以后,可能相反会拖累浏览速度,测试我倒是没有做过,但是的确是完全可能的。也就是说,缓存的开销可能会不缓存而是直接访问还要大,这就是大家不想看到的了。

    3. 缓存的目的
    其实前面已经说过缓存是为了减少开销,提高性能,这不就是缓存的目的吗?这倒是没错,但是也不尽然。

    因为开销是一个很笼统的词,具体点有CPU开销,磁盘IO开销,网络开销,数据库访问开销等,缓存对于性能的优化,除了一些大众化的优化措施以外,还得有的放矢。

    以前学习写程序,大家一定都听说过什么时间换空间,空间换时间,到底什么时候要拿空间换时间,什么时候要拿时间换空间,只能看具体应用了。前面说过缓存也有开销,其实缓存就是拿某些开销换取其他开销的下降而已。比如说动态页面静态化是一些大型网站常见的优化方法,他付出了磁盘的空间和读写开销,来换取更低的CPU消耗(不用解析动态页面)和数据库访问。有些网站每天访问量没有多少,却频繁生成和更新静态页面,同时还在服务器上做下载,本来磁盘就不堪重负,这下更加是雪上加霜,可以说是缓存优化的反面例子。或者是本来内存不大,磁盘swap很多影响性能,但是却使用大量内存做页面和对象缓存,也是反面的例子。

    所以说不能盲目的进行缓存优化,一个系统,性能出现了问题,或者将来可能出现问题,性能总会有一个或者若干个瓶颈,我们要做的就是平衡或者削弱这个瓶颈,缓存是重要的手段。

    所以缓存的目的是针对几个主要指标,兼顾若干个其他指标,来尽量实现低开销。

    比如,数据库的CPU较高,那么一般是复杂的查询或者是存储过程导致的,在前面的各个环节进行缓存优化,比如缓存数据集和内存对象,都是好的解决方法,缓存整个页面也是个好方法,但是缓存页面要付出更多的空间开销,在某些情况下,缓存数据集或者内存对象已经够了。

    假如WEB服务器的CPU较高,往往是因为动态页面处理造成的,找出开销大的处理,将处理的结果对象缓存,或者是页面静态化是不错的方法,而缓存数据库结果集往往收效不大。

    4. 啥样的缓存才是好缓存?

    蝈蝈俊认为是命中率最高的缓存最好。我做的领域是streaming server的磁盘IO缓存和CDN的网络边缘缓存,瓶颈就是磁盘或者网络IO,这种时候,命中率就是硬道理。

    但是对于web服务器来讲,影响性能的因素很多,不同的内容访问开销相差很大,什么是好缓存,虽然命中率是极为重要的指标,但是还得要综合缓存的开销,原始的访问路径/开销和性能瓶颈来综合评价。也就是说不同的应用侧重不同,跑的硬件条件和瓶颈也不一样,很难有一个简单的指标。

    比如缓存一个命中率稍低,但是原始访问开销很大的对象(比如要经过复杂查询和处理的对象) 比一个命中率较高,但是原始访问开销很小的对象要划得来。我觉得假如有一个"加权命中率"会更好,原始访问开销大的对象,要给与更高的权值,再进行命中率的计算。

    假如一个缓存策略可以减少某个方面的性能开销,但是却带来了新的性能瓶颈,那么它也不是个好的缓存实现。比如磁盘IO紧张,页面更新非常频繁的情况下,静态页面缓存往往就不是一个好方法(好像没有用静态化页面做聊天室的吧:->)。

    到底什么是好的缓存实现呢?前面也讲过了缓存的目的,我觉得充分利用了软硬件条件,消灭了性能瓶颈的就是好缓存。

    5. 如何进行缓存优化
    进行缓存优化,第一是要找到性能瓶颈,第二是找到瓶颈有关的应用部分。

    性能瓶颈一般还是好找,系统有那么多性能计数器,数据库n多的调优工具,查询优化分析器。好好对照厂商的技术资料和google大法,很容易找到是CPU 内存还是IO限制了性能提高。

    然后就是应用针对性能瓶颈的优化,有一个2/8定律,就是说80%的性能都消耗在20%的处理中,这20%也分为两种,一种是访问比例很高的,一种是开销贼大的,当然两个都占了,就更加没说的了。我们的任务就是集中火力提升这20%的性能,缓存往往是重要的方法。

    寻找开销大的操作是个细活。我们在软件设计阶段可以预见瓶颈的部位,在出现问题的时候可以猜测出现瓶颈的部位,但是除非对行业模型,相关的架构性能还有一些性能细节非常了解(也就是说你实践过n次了),否则当初的预见和猜测都可能有较大偏差的,性能优化没有银弹,实践出真知。要多多使用工具进行分析,假如在系统里本身就有一些性能计数,可以在线或者离线提取就更好了。对于成熟的运营系统,性能统计和分析往往是其必不可少的功能。

    在代码里寻找性能瓶颈太过细节,就不废话了,剩下的事情就是需要分析用户行为,根据用户对内容的访问频度实现不同的缓存策略。

    6. 用户行为分析
    用户行为主要有两种,一种是时间行为分布,就是一个内容的访问随时间变化的规律,不同的内容常常不同,比如新闻和音乐肯定是不一样的。

    另外一种是空间分布,就是用户对于不同内容的偏好程度,大家常常说80/20规律,就是指大量的访问往往集中于少量内容,80/20只是一个定性的规律,这里一般适用的规律是zipf分布,我统计过一些系统的行为,和zipf分布的吻合度还是很好的,大家可以看看我的一篇文章:http://blog.joycode.com/peon/archive/2006/08/19/80885.aspx

    大型的系统,缓存所有的内容肯定是不切实际的,缓存访问比率高的部分内容大概能达到多少缓存命中率呢?我根据zipf公式做了计算,结果可以看看下面的图:


    y轴为命中率,x轴为缓存的内容占所有内容的比率,不同类型的内容有不同的a值(什么是a值, 请看http://blog.joycode.com/peon/archive/2006/08/19/80885.aspx),上面除了一条lg函数曲线外,其余的是各个a值对应的命中率曲线。可以看到a=0.95的时候,刚好符合20/80的定律。

    光有理论的不够,进行实际统计永远是必须的,会议室里面的讨论永远代替不了实际的统计,log分析,性能计数器都是必要的技术。

    7.关于分布式缓存
    缓存会增加应用的复杂度,假如应用是分布式的而缓存不是分布式的,这个复杂度将会会平方。但是分布式缓存肯定比本机缓存效率低,所以是否选择分布式缓存实现,哪些内容使用分布式缓存哪些使用本机缓存,是第一步就得考虑的。

0
相关文章