标题:squid 优化指南 出处:沧海一粟 时间:Sat, 10 Sep 2011 15:03:11 +0000 作者:jed 地址:http://www.dzhope.com/post/840/ 内容: 很多squid 优化只限于在 squid 参数和系统参数上面的调整。但是这个实在只是细枝末节的事情,只要不是太弱智的配置导致无法缓存,squid的性能不会有太大差距,也就提高10%左右,只有实际的业务针对 squid 进行一些调整,squid 才会真正爆发出他的能量,很多时候有 100%-200% 的提升。 本文基本是一些方向性的指导,并不涉及像具体配置文件的细节,因此本文里面的内容大部分不能往配置文件里面 copy-paste。。 首先要明确一下,squid 能够用来作什么。很多人没有搞明白 squid 的工作原理,只是听说 squid 性能不错可以用来给网站提速,就直接在自己的 website 前面套了一个 squid ,这基本没有任何用处,即使你都是静态页面,后面apache上面没有开 mod_expires,一样缓存不了,squid只能起到一个连接管理的用处。 一般说来,网站用 squid 加速,目的有二 1: squid 本身具有缓存功能,可以将webserver输出的内容缓存起来,在缓存没有过期之前来的访问,都直接用缓存里面的内容,这样可以有效减少 webserver 机器上面的请求数量。这是 squid 的主要功用。 2: 网络慢的用户会长时间占用 webserver 的 TCP 连接,webserver 对每个连接占用的资源比较大,如果长时间不能释放出来服务其他请求,性能会有比较大的影响。前面放一个 squid, webserver 就可以迅速处理完逻辑以后,把数据快速发送给 squid, 然后去处理别的逻辑,而 squid 每个 TCP 连接占用的资源很少,不用担心占用太多资源。这个用途也叫做连接管理,有一些网络设备也可以做这个事情,价格都很贵。 下面针对 squid 的两种功用,来讲述如何调整业务逻辑和 squid 参数 零:预操作 在搞 squid 之前,不管你用什么编译配置,需要什么特殊选项,都请 enable-snmp ,并配置好 mrtg 之类,可以图形化的显示 squid 状态,例如 Request Hit Ratio(RHR), Byte Hit Ratio(BHR), 等等,反馈是做一切事情的基础,优化也不例外。 一:缓存 A: 使用 Expires header 来控制缓存 squid在缓存webserver内容的时候,需要后端webserver输出一些控制信息告诉他页面是不是可以被缓存,以及可以缓存多久。否则 squid 是不会自作主张给你缓存内容的。一个页面到底能不能缓存,只有开发网站的人才知道,因此开发人员有责任在动态页面里面输出 Expires 和 Cache-Control header。简单举一个 php 的例子以说明这两个 header 的值是什么含义,其中$expiretime 的单位是秒。 header("Expires: " . gmt_date_format(time()+$expiretime)); header("Cache-Control: max-age=" . "$expiretime"); 对于静态文件,有两种方式来让 squid 自动给静态文件缓存,一种是使用 apache 的 mod_expires ,可以针对路径或者针对文件类型/扩展名来自动输出 cache 头。详细的请参考 mod_expires 的说明 。另一种是用 squid 的 refresh_pattern 来指定。详细的还是请参考 squid 的配置文件。一般来说,如果后端不是配置很麻烦,建议还是在后端做,前端的配置修改大多数都是违背http协议的,如果出现问题,也比较难排查。 B 根据 squid 访问的模式,进行业务拆分  进行了 Expires Header 的处理以后,squid 就真正可以起到加速的作用了,你可能也能感觉到,网站的访问速度明显加快。但是不要满足于这点成绩,查看 squid 的 snmp 统计图,通常 hit ratio 并不会太高,有 50% 就了不起了。这就是我们需要进一步优化的,我们的目标是让大部分 squid 都达到 9X% 的命中率。 为什么 squid 命中这么低呢,这大概有两种原因。大多数的网站都是有一些页面不能够被缓存的,例如登录页面。这些页面请求也从 squid 走,成为分母的一部分,直接就降低了命中率,我们首先可以做的事情是,把这些不能够缓存的页面请求,拆分到单独一个 squid 上面,或者访问量不大的话,干脆把 apache 暴露出来。这样能够缓存的那个 squid 命中率马上上升一截。  有人可能会说,把不能缓存的页面分拆开去,就光为了让能缓存的那个数字好看,这不是掩耳盗铃么?其实这么做是有意义的,首先就是去掉了不能缓存页面的干扰,使得我们进一步优化 squid 的依据更加准确。其次是不可缓存请求和可缓存请求之间的重要性通常是有差距的,分拆了以后,它们之间不容易互相抢占资源,不会因为下载图片的连接太多把 squid 占满,影响更重要的登录请求。第三就是可缓存内容通常是图片等页面元素, 浏览器在 load 它们的时候,对每个站点的并发连接会有控制,如果分开成不同的IP,可以多一些请求同时执行。提高少许显示速度。 其实观察 sohu, sina 之类的页面,你会发现它们的页面也是分拆的,可以看到页面里面的图片都是指向 images.sohu.com 之类的地址,虽然它们可能和其他页面一样后台都指向同一个 apache。  这样做完,缓存命中率大概能上升到 70%-80% 了,运气好的时候完全可以上 90%。 另一种导致 squid 命中低的原因和这个比较类似,同样都是可缓存的内容,有的可能是软件下载站上面的大文件,有的是新闻站点上面的小图片,如果同一个 squid 对这样差别巨大的文件加速的话,会严重干扰 squid 的缓存策略,两者不能兼顾,要不就是大文件占据了 cache ,把小文件都挤出了 cache, 要不就是小文件特别多,大文件无法进入 cache, 导致大文件经常 miss 。这个比不能缓存的页面还要恶心,因此即使在服务器资源有限的情况下,也要优先拆分这两类型访问。一般来说,文件大小分界线定在 1M 左右就可以了,如果是有软件下载这样特别大的文件,可以在 4M - 10M 左右再拆分一次。对于不同访问类型的 squid, 其系统优化参数也会有所不同,这个我们后面还会讲到。  只要悉心按照访问模式来拆分业务,大部分起缓存作用的 squid 都可以达到很高的命中率,至少都可以到达 9X%。 C 根据不同的需求,调整参数优化缓存 完成 A 和 B 两步优化以后, squid 的命中率经常可以达到 9x%, 可以说我们已经给 squid 创造了非常优秀的外部环境,下面我们就要从 squid 本身入手,通过调整它的缓存参数和缓存策略,甚至系统的参数,来让 squid 发挥出更好的性能。 在 B 步骤中,我们把 squid 划分成了三种用途,缓存大文件,缓存小文件,不缓存文件,这其中最后一种用途情况下面 squid 不起到缓存效果,只用来做连接管理,因此我们把它放到后面的连接管理里面叙述,这里只讨论和缓存相关的 squid 参数。 squid 有内存缓存和磁盘缓存两级缓存, 通常来说, 只要是专门给 squid 用的机器, 内存缓存都建议开得比较大, 大内存缓存总是有好处的嘛, 但是注意不要使得系统开始吃 swap ,像Linux这样一开始吃 swap 性能就下降比较严重的系统尤其要注意. 这个程度需要自己试验确定. 通常 1G 内存的Linux机器用来跑 squid ,内存缓存可以开到 512M. 有些libc比较差的平台, 例如比较老的 freebsd 系统, 其 malloc 函数的质量不高,可能会造成比较多的内存碎片,导致 squid 运行一段时间以后分配不出来内存挂掉. 这时候推荐在编译时候使用 dlmalloc package. 即使如此, 仍然要再缩小 squid 的内存缓存,以防不幸发生. 磁盘缓存的情况比较复杂, squid 有 ufs, aufs, coss, diskd, null 五种存储后端, 其中 ufs, aufs, diskd 都是在文件系统上面保存很多小文件, coss 是 squid 自己实现了一个简单的文件系统,可以使用一个大文件或者一个磁盘设备来存储. null 则是给不想要磁盘缓存的情况准备的. coss 看起来好像比较拽, 但是以前试验并不足够稳定,因此并不推荐使用. 剩下的三种存储方式,具体选择哪种需要根据操作系统的特性来进行. ufs 是最传统的存储方式, 我们知道, squid 是一个单进程的程序, 它使用 ufs 存储后端时, 直接在进程里面读写文件. 这是一种很简单的方式, 缺点是当读写磁盘被阻塞的时候, squid 不能够处理请求, 会造成服务质量波动比较大. 因此出现了 aufs 和 diskd 两种存储后端, 原理都是 squid 主服务循环不负责读写文件, 而是通过消息队列或者tcp/pipe连接将数据传送给其他的线程(aufs)/进程(diskd), 然后其他线程/进程进行读写. 很显然,这两种存储方式有一定的通信开销, 因此不一定就比 ufs 好, 需要具体问题具体分析 前面说到, ufs/aufs/diskd都是在文件系统上面存储很多小文件,因此文件系统本身的特性严重影响了squid缓存的性能,对于 Linux ,强烈推荐用 reiserfs 等适合处理小文件的文件系统, bsd 则至少要打开 softupdate, 以及 dirhash 等一切对很多小文件有好处的选项. 在比较新的系统上面, reiserfs 等文件系统的性能已经足够优越, 通常 ufs 就已经可以应付需要. 对于一些老系统,使用 aufs 或者 diskd 是比较好的选择,如果系统的线程库比较好(如Linux,Solaris),那么使用 aufs, 否则 diskd. 也有一些例外情况, 比如多 cpu 的 Linux 2.6 系统, 线程库很优秀, 虽然 ufs 本身已经比较快了,但是 squid 单进程无法利用另外的 cpu , 不如使用 aufs , 让另外的 cpu 也可以起到一些作用, aufs 在编译的时候可以选择使用几个读写线程. 这个个人觉得稍微超过 cpu 个数就可以了.但是并没有实际测试过. 磁盘缓存开多大? 这个问题没有固定答案. 需要经过试验来确定, 一般来说开大一些没有太大问题. 只要你的硬盘足够 (待续) 最后致谢一下 windtear ,这位是 squid 之王, Lord of Squid, 业务分拆是从他那里学来的。致敬。(假如有人转载请不要删除这个,否则你也太没良心了) ---------------以下是a总分析 A. 数据反馈 康神教导我们:反馈是做一切事情的基础,优化也不例外。那么具体看一些什么反馈数据呢?很遗憾,这个问题没有固定的答案,基本上需要具体问题具体分析。具体来说,用 cacti 之类的看 squid snmp 数据是第一步。主要的数据有: * BHR (Byte Hit Rate)/RHR (Request Hit Rate),这两个分别表示有多少数据量/请求数是被 squid hit cache 的。要特别注意的是,squid hit cache 并不等于 squid 不往主服务器发请求。具体情况在 squid 2.5/2.6 里面参看 isTcpHit() 函数。举个例子,如果 RHR 显示 90%,那么可能有 20% 的请求 squid 还是往主服务器发送了请求询问它过期的 cache 内容是否有变化的,只不过主服务器的回应是没有变化。主服务器的判断一般需要一次数据库查询或者文件系统的 stat 调用。高负荷服务器到最后如果瓶颈在主服务器磁盘 I/O,这些貌似 HIT 的请求也会对主服务器造成一定量的冲击。单纯盲目追求高 BHR/RHR 是不可靠的。 * Service Time,这个是各类请求的回应时间,但是这个时间受到外部网络速度的影响,所以也不一定代表真实的性能。另外,如果出现 Miss Service Time 比别的几个 Service Time 指标高出很多,表示去主服务器的请求耗时比较长,一般是代表瓶颈在主服务器那里,但这也不是绝对的。如果 squid 服务器磁盘 I/O 性能跟不上或者 CPU 不够强劲(squid 2 核心是可怜的单进程),那么也可能是 squid 本身的调度出了问题。 * Storage,在特定情况下可以估算 squid 每天缓存多少东西,在配置缓存大小等问题的时候会有帮助。 总的来说,squid snmp 数据是需要根据具体情况分析的,而且 squid snmp 的数据也不多,有时候往往需要配合别的监控来综合分析问题,比方网络出入流量,服务器的各类性能分析,主服务器和 squid access log。有一次我在某站 squid 调整了一个参数,结果那天 squid 的反应奇好,BHR 更是上了前所未有的 98%。但是后来反复推敲发现这个参数并没有作用,结合各类 log 分析发现是因为那天 frjj 贴了新照片,而且被盗链,导致巨大比例的 frjj 照片的请求被 squid 有效的缓存,squid 网络出流量激增也支持这个结论。说了半天,中心意思是高负荷服务器的 squid 优化是一个长期而艰巨的任务,某些参数的优化需要几天的数据才能得出有意义的结论,所以需要有足够的耐心和针对实际情况的系统化的优化步骤。 B. 缓存策略 康神教导我们:一般来说,(缓存策略)如果后端不是配置很麻烦,建议还是在后端做,前端的配置修改大多数都是违背 http 协议的,如果出现问题,也比较难排查。HTTP 缓存协议比较权威的可以参考 RF2616 第十三章,特别是 13.2 和 13.3 节。具体实现可以参考比方 Firefox 代码 nsHttpResponseHead.cpp 的 ComputeCurrentAge() 和 ComputeFreshnessLifetime() 函数看看各类情况的处理方式。mod_expires 的配置就需要深刻理解这些基本概念,否则可能反而会增加请求数。如果没有特别的理由,静态文件的过期时间一般是设置为 access time 加上一定量的时间。这个一定量的时间由具体情况决定。比如网站建设初期,各类静态文件可能需要比较短的过期时间以方便网站更新;而一旦美工敲定图片,图片的过期时间可以大胆的设置为几个月。在配置完成以后如果没有很大的把握也可以实际浏览一下分析请求序列看是否浏览器端和 squid 服务器都做到了有效的缓存,特别注意 cache 相关的请求和回复头,包括 squid 提供的 X-Cache 头。 另外,虽然违反 HTTP 协议的 squid 配置一般都不推荐,但是具体到细节上,这也不是绝对的原则。下面举例说说必须违反 HTTP 协议的情况。 * 使用 javascript 做镜像网站测速,一般实现方式是从各个镜像站下载一个图片看哪一个最快。最理想的情况是图片在浏览器端不要缓存(以便下次准确测速),但是这个请求又完全没必要打到主服务器上。那么可以在 squid 里针对这个图片 url 配置强制缓存 refresh_pattern reload-into-ims ignore-reload。当然这个例子很土鳖,只是举个例子。 * reload_into_ims (这里说的是 squid 的配置参数,不是 refresh_pattern 里面的 option)。这个参数虽然违反 HTTP 协议但是对大部分网站来说是可以设置为 on 的,只要后端服务器对 If-Modified-Since 头的判断正确并且没有潜在安全问题即可。 * 浏览器 F5 刷新和 javascript 的 location.reload() 刷新可能会重新请求所有的网页内嵌元素并且可能带 no-cache 请求头,一般来说 reload_into_ims 设置成 on 已经足够保证对主服务器不造成冲击,但是如果有必要可能还是需要在 squid 配置强制缓存。 * 针对土鳖客户端的优化。比如早期的 fterm 预览图片会发送 Pragma: no-cache 的请求头,这势必导致所有 fterm 预览图片的请求如数全部打在后端服务器上,所以解决方法是 squid 这里做手脚针对这类 url 配置强制缓存。一个细节问题是如果不能缓存的图片(比方有察看权限限制的)和能缓存的图片的 url 结构完全一样,那么在 squid 强制缓存这类 url 的话又会有潜在的安全问题,这里涉及到后面会讲到的网站结构优化,针对这个问题需要修改网站的代码以明确区分这两类 url。还有另外一个例子是早期的 firefox 发送 XMLHttpRequest 请求也会发送 no-cache 的头,后来的版本改了。当年这一类 ajax 请求的 url 也是需要配置强制缓存的。 * 最后一个问题是,如果在特殊情况下必须同时在后端服务器发送 Expires 头,并且又在 squid 中配置这类 url 的 refresh_pattern,那么需要特别小心。比如,如果 squid 强制缓存时间比 mod_expires 配置的过期时间长,那么可能造成 squid 发送已经过期的内容,导致浏览器本来可以有效缓存的内容却需要不断的向服务器检查更新。 最后,有些后端服务器没办法配置 mod_expires。这可能是因为没有配置权限,也可能是因为后端服务器软件太土鳖,总之这样的情况下就必须用 squid 配置 refresh_pattern 了。 C. 网站代码及结构优化 康神教导我们:很多 squid 优化(的文章)只限于在 squid 参数和系统参数上面的调整。但是这个实在只是细枝末节的事情,只要不是太弱智的配置导致无法缓存,squid 的性能不会有太大差距。网站优化一般来说也是属于这种类型的优化,对于主服务器负荷瓶颈在磁盘 I/O,或者网络瓶颈是大量大图片文件的情况,优化网站 html 结构可能对性能提升没有半点作用。不过即便如此,有一个为 squid 考虑的网站结构,可以使得 squid 服务器的配置比较容易,也可以比较容易的实现多 squid 业务分拆,有的时候业务分拆并不是为了性能,而是为了更好的分析问题以便进一步优化网站。下面简要说说有可能提高性能的网站代码优化。 * 减少页面大小。这个问题实在是到处都有好文章,我就不详细说了。常见技巧是分离 css/js 到单独文件减少动态主页面大小同时保证静态内容有效缓存;页面 layout 设计尽量使用 div+css;有大量冗余 html 元素的部分使用 javascript 来输出。最后这个页面 javascript 化可能需要考虑搜索引擎优化 (SEO) 的问题,总的来说需要在减少流量和 SEO 之间寻找一个好的平衡点,这个只有做网站的人自己最清楚。 * 减少同一份数据的不同表现形式。大量使用 ajax 的站点有时候考虑 SEO 往往要重写一套给搜索引擎看的页面,这势必导致 squid 这里要存两套页面。但是如果功力足够还是可以做到大部分页面重用。举例来说,网站可能希望用户读文章不切换页面而使用 XMLHttpRequest 载入,这个就可以在