1、refresh_pattern机制分析概述
refresh_pattern指令间接的控制磁盘缓存。它帮助squid决定,是否某个给定请求是cache命中,或作为cache丢失对待。宽松的设置增加了你的cache命中率,但也增加了用户接收过时响应的机会。另一方面,保守的设置,降低了cache命中率和过时响应。refresh_pattern规则仅仅应用到没有明确过时期限的响应。原始服务器能使用Expires头部,或者Cache-Control:max-age指令来指定过时期限。
你可以在配置文件里放置任意数量的refresh_pattern行。squid按顺序查找它们以匹配正则表达式。当squid找到一个匹配时,它使用相应的值来决定,某个缓存响应是存活还是过期。refresh_pattern语法如下:
refresh_pattern [-i] regexp min percent max [options]
例如:
refresh_pattern -i \.jpg$ 30 50% 4320 reload-into-ims
refresh_pattern -i \.png$ 30 50% 4320 reload-into-ims
refresh_pattern -i \.htm$ 0 20% 1440
refresh_pattern -i \.html$ 0 20% 1440
refresh_pattern -i . 5 25% 2880
regexp参数是大小写敏感的正则表达式。你可以使用-i选项来使它们大小写不敏感。squid按顺序来检查refresh_pattern行;当正则表达式之一匹配URI时,它停止搜索。
min参数是分钟数量。它是过时响应的最低时间限制。如果某个响应驻留在cache里的时间没有超过这个最低限制,那么它不会过期。类似的,max参数是存活响应的最高时间限制。如果某个响应驻留在cache里的时间高于这个最高限制,那么它必须被刷新。
在最低和最高时间限制之间的响应,会面对squid的最后修改系数(LM-factor)算法。对这样的响应,squid计算响应的年龄和最后修改系数,然后将它作为百分比值进行比较。响应年龄简单的就是从原始服务器产生,或最后一次验证响应后,经历的时间数量。源年龄在Last-Modified和Date头部之间是不同的。LM-factor是响应年龄与源年龄的比率。
图7-2论证了LM-factor算法。squid缓存了某个目标3个小时(基于Date和Last-Modified头部)。LM-factor的值是50%,响应在接下来的1.5个小时里是存活的,在这之后,目标会过期并被当作过时处理。假如用户在存活期间请求cache目标,squid返回没有确认的cache命中。若在过时期间发生请求,squid转发确认请求到原始服务器。
图: 基于LM-factor计算过期时间
理解squid检查不同值的顺序非常重要。如下是squid的refresh_pattern算法的简单描述:
假如响应年龄超过refresh_pattern的max值,该响应过期;
假如LM-factor少于refresh_pattern百分比值,该响应存活;
假如响应年龄少于refresh_pattern的min值,该响应存活;
其他情况下,响应过期。
refresh_pattern指令也有少数选项导致squid违背HTTP协议规范。它们如下:
override-expire
该选项导致squid在检查Expires头部之前,先检查min值。这样,一个非零的min时间让squid返回一个未确认的cache命中,即使该响应准备过期。
override-lastmod
改选项导致squid在检查LM-factor百分比之前先检查min值。
reload-into-ims
该选项让squid在确认请求里,以no-cache指令传送一个请求。换句话说,squid在转发请求之前,对该请求增加一个If-Modified-Since头部。注意这点仅仅在目标有Last-Modified时间戳时才能工作。外面进来的请求保留no-cache指令,以便它到达原始服务器。
ignore-reload
该选项导致squid忽略请求里的任何no-cache指令。
2、源码分析
2.1关键数据结构
refresh_t
2009 struct _refresh_t {
2010 const char *pattern;
2011 regex_t compiled_pattern;
2012 time_t min;
2013 double pct;
2014 time_t max;
2015 refresh_t *next;
2016 struct {
2017 unsigned int icase:1;
2018 #if HTTP_VIOLATIONS
2019 unsigned int override_expire:1;
2020 unsigned int override_lastmod:1;
2021 unsigned int reload_into_ims:1;
2022 unsigned int ignore_reload:1;
2023 unsigned int ignore_no_cache:1;
2024 unsigned int ignore_private:1;
2025 unsigned int ignore_auth:1;
2026 #endif
2027 unsigned int ignore_stale_while_revalidate:1;
2028 } flags;
2029 int max_stale;
2030 int stale_while_revalidate;
2031 int negative_ttl;
2032 };
stale_flags
56 typedef struct
57 {
58 unsigned int expires: // 是否有过期时间的标记
59 1;
60 unsigned int min: // 匹配到MIN标记
61 1;
62 unsigned int lmfactor: // 匹配到百分比标记
63 1;
64 unsigned int max; // 匹配到MAX标记
65 } stale_flags;
2.2refresh_pattern检查时机
Refresh_pattern的检查时机位于clientCacheHit()函数中的refreshCheckHTTPStale()函数中。该函数的流程为在disk中找到obj,Hit后即开始检查时间,看是否可以用这个Obj进行回复。
2.3refresh_pattern检查流程
// 过期检查refresh_pattern stale==0时为Hit stale = refreshCheckHTTPStale(e, r) {
int reason = refreshCheck(entry, request, -Config.refresh_stale_window)
{
if (check_time > entry->timestamp)
age = check_time - entry->timestamp;//检查时间-时间戳
// 搜索匹配的R规则
R = uri ? refreshLimits(uri) : refreshUncompiledPattern("."); if (NULL == R) R = &DefaultRefresh; // 检查是否过期,这里描述了max\ percentage\min三个值的比较先后顺序 staleness = refreshStaleness(entry, check_time, age, R, &sf) {
if (entry->expires > -1)
{ // 数据有过期时间标识则用时间来做是否过期的判断依据 if (entry->expires > check_time)//没过期
return -1
else
return (check_time - entry->expires);//过期了
}
// 比较max的值,如果大于直接过期
if (age > R->max)
return (age - R->max);
// 比较时间戳,如果小于时间戳直接过期
if (check_time < entry->timestamp)
return (entry->timestamp - check_time);
// 比较百分比,有最后修改时间
if (entry->lastmod > -1 && entry->timestamp > entry->lastmod)
{
ime_t stale_age = (entry->timestamp - entry->lastmod) * R->pct;
//依据此时间做比较
if (age >= stale_age)
return (age - stale_age); // 直接过期
else
return -1; // 不过期
}
// 比较Min
if (age < R->min)
return -1;//不过期
else
return (age - R->min); //过期
}
if (staleness < 0)
return 不过期标志
// 得到过期的细分标志
.....
// 不过期 }
if (reason == STALE_WITHIN_DELTA)
return -1;
if (reason == STALE_ASYNC_REFRESH)
return -2;
if (reason == STALE_MAX_STALE)
return 3;
return (reason < 200) ? 0 : 1; }
// 过期处理
if (stale)
{
........
// refresh pattern 检查
clientRefreshCheck(http);
return;
}
// Hit流程
clientProcessHit(http)
refresh_pattern检查耗时的操作
refresh_pattern检查耗时的操作在函数refreshLimits(const char *url)中执行,该函数将遍历Config.Refresh进行正则匹配操作。具体代码如下:
118 const refresh_t *
119 refreshLimits(const char *url)
120 {
121 const refresh_t *R;
122 for (R = Config.Refresh; R; R = R->next)
123 {
124 if (!regexec(&(R->compiled_pattern), url, 0, 0, 0))
125 return R;
126 }
127 return NULL;
128 }
可见如果每个正则的pattern很宽松,并且正好匹配到的正则在链表的最末尾,则耗时最长。