🔗 使用Adaptation缓存动态内容
此页面正在持续开发中。至少需要跟上youtube.com的更新。如果您在使用这些配置时遇到任何问题,请首先回到这里查看更新的配置。
🔗 问题概述
Squid从早期至今 Squid-3.2 都使用URL作为资源键。这一直是并且仍然是HTTP的基本设计属性。这种方法基于这样的假设,即每个URL的GET请求都应该标识一个且仅一个资源。动态内容应该基于POST请求中的用户数据发送。正如 rfc2616 GET的9.3节 和 POST的9.5节 中所定义的。
9.3 “GET方法表示检索Request-URI标识的任何信息(以实体形式)。”
9.5 “POST方法用于请求源服务器接受请求中包含的实体,作为Request-Line中Request-URI标识的资源的新的下级。”
rfc规定了协议的规范,但执行它取决于开发人员/网页设计师。
🔗 什么是动态内容
一个URL可能导致多个资源(一对多)。
- 动态内容是指在请求时生成实体资源。其通常结果是每次请求都会变化并包含特定于请求信息的实体。例如,包含登录用户姓名并请求它的网页。
其中一些原因
- 基于或不基于最终用户提供的参数的实时内容流的结果。
- CMS(内容管理系统)脚本设计。
- 糟糕的编程。
- 隐私政策。
🔗 文件去重/重复
- 两个URL导致相同相同的资源(多对一)。其中一些原因
- 基于凭证的内容访问的临时URL
- 糟糕的编程或对缓存的恐惧
- 隐私政策
网上内容复制的问题也存在。例如:有多少网站包含自己的“jQuery.js”副本?图片、图标、脚本、模板、样式表、小部件。所有这些东西都有大量的重复,从而降低了缓存效率。
🔗 URL中的动态内容标记
Squid默认使用刷新模式ACL对URL中的动态内容标记进行匹配,例如“?”和“cgi-bin”。refresh_pattern
refresh_pattern -i (/cgi-bin/|\?) 0 0% 0
这些不应被缓存的唯一原因是旧的CGI系统通常不发送缓存控制或过期信息以允许安全缓存。对于带有“?”的查询字符串脚本也是如此。refresh_pattern指令专门用于缓存响应中确实包含足够缓存控制头的动态内容响应。
🔗 ”?”
附加到URL的问号用于向脚本传递参数,并且可以代表“动态内容”页面,该页面将根据参数而变化。URL:“https://wiki.squid.org.cn/index.html?action=login”将参数“action=login”传递给wiki服务器,并将生成登录页面。如果您将参数发送到静态HTML文件,例如:“https://squid.org.cn/index.html?action=login”,结果只是一个更长的URL。许多CMS如WordPress使用问号来标识系统中存储的特定页面/文章。(“/wordpress/?p=941”)
但是,脚本作者可以选择利用这种约定,或者直接添加特定于缓存的头信息来允许或禁止缓存资源。
🔗 HTTP和缓存
Mark Nottingham撰写了一篇关于缓存的非常详细的文档“Caching Tutorial for Web Authors and Webmasters”,我推荐您阅读。他还编写了一个很棒的工具来分析网站的缓存头信息RedBot
🔗 HTTP头
除了URL本身之外,还有几个HTTP头会影响请求的结果,从而影响缓存。HTTP响应可以根据“User-Agent”、“Cookie”或其他请求头在客户端之间有所不同。
“User-Agent”非常普遍地用于识别客户端软件并做出不同的响应。它可以区分移动手机和桌面设备,或者客户端的HTML格式兼容性。这些头信息会影响响应的语言、内容和压缩。
客户端可以使用缓存特定头信息来识别当前缓存资源的有效性。“Expires”和“Etag”可以识别过期缓存资源的信号。
为了提高缓存效率,HTTP头信息和状态码提供了帮助。缓存可以使用带有“If-Modified-Since”头的请求,服务器可以通过“304”响应码验证文件自“Since”以来没有更改。头的变化可以在这种情况下提供帮助。
常见的请求头
User-Agent:
Accept-Language:
Accept-Encoding:
Cookie:
If-Modified-Since:
If-None-Match:
常见的响应头
Cache-Control:
Expires:
Accept-Ranges:
Transfer-Encoding:
Vary:
Etag:
Pragma:
🔗 HTTP 206/部分内容
Squid尚未缓存范围响应。但也有其他软件提供此功能。
🔗 动态内容|带宽消耗者
如果您查看一些ISP/办公室的图形,您会发现使用图表中存在某种模式。软件更新和视频内容是众所周知的带宽消耗者。其中一些对缓存友好,而另一些则不友好。
Squid开发人员曾尝试让YouTube更友好于缓存,但最终由于YouTube方面的原因而陷入僵局。
🔗 特定缓存案例分析
文件去重
- Microsoft更新
- Youtube视频/图片
- CDN/DNS负载均衡
真正的动态内容
🔗 Microsoft更新缓存
Microsoft更新的主要问题是它们使用Squid无法缓存的206部分内容响应。有时更新文件大小达几十MB,会导致很大的负载。Amos Jeffries在:SquidFaq/WindowsUpdate 提出了一种解决方案,为了节省最大带宽,迫使Squid下载整个文件而不是部分内容,使用
range_offset_limit -1
quick_abort_min -1
range_offset_limit quick_abort_min
问题在于这些ACL适用于整个服务器,并可能导致某些软件响应不正确,因为它期望的是部分响应。此外,90MB文件中的1KB数据块将导致90MB带宽浪费。因此,代理管理员需要妥善设置缓存。
🔗 Youtube视频/图片
页面和URL是动态创建的,但它们会去重到静态视频位置。YouTube为用户提供视频内容请求,以应用“仅允许特定用户/组/朋友”等策略。在几秒钟内,同一视频可能会以不同的URL提供给同一客户端。大多数视频URL的参数形式有一些共同的标识,因此可以使用特定的“键”进行缓存。由于Squid主要使用URL来标识缓存资源,这使得缓存管理员的生活更加困难。视频URL的随机模式也加剧了这个问题。
过去,曾有过几次尝试使用旧的“store_url_rewrite”在Squid2.X中缓存它们。另一种解决方案是使用“url_rewrite”结合Web服务器,如ConfigExamples/DynamicContent/YouTube所述。
🔗 CDN/DNS负载均衡
许多网站使用CDN(内容分发网络)来扩展其网站。其中一些使用其他域上的相同URL。我可以用SourceForge进行演示,它是主要的开源玩家之一。它们在世界各地都有镜像,并使用前缀域来选择镜像,如
http://iweb.dl.sourceforge.net/project/assp/ASSP%20Installation/README.txt
http://cdnetworks-kr-2.dl.sourceforge.net/project/assp/ASSP%20Installation/README.txt
所以这是一个简单的URL去重案例。这种情况可以通过将所有子域存储在一个“键”下来轻松解决。伪代码如下:dl.sourceforge.net的每个子域都应该存储为:“dl.sourceforge.net.some_internal_key”。以及一个Ruby示例代码来演示
url = "http://iweb.dl.sourceforge.net/project/assp/ASSP%20Installation/README.txt"
key = "http://dl.sourceforge.net.squid.internal/" + url.match(/.*\.dl\.sourceforge\.net\/(.*)/)[1]
类似的情况是,AV更新会使用多个域或将IP地址作为冗余情况,此时没有DNS可用。
Facebook是另一个带宽滥用的主题,但需要认真思考。作为缓存管理员,您可以看到Facebook在日志和报告中是排名前列的URL之一。如果您在一个域上看到很多URL,并不意味着它消耗带宽。Facebook像所有社交网络一样,有“暴力史”,而不仅仅是在带宽方面。
社交网络的一个问题是“隐私”。这些网络产生大量包含私人数据的响应,当ISP缓存这些数据时,可能导致“隐私侵犯”。
- 我遇到过一个案例,在缓存配置错误时,用户开始获取其他用户的Facebook和Gmail页面。
隐私是缓存操作员在配置服务器ACL(refresh_pattern)时需要非常深入考虑的问题。自从Facebook在全球范围内普及以来,他们确实付出了很多努力来使用“Cache-Control”等头信息来使其对缓存友好。他们使用XML进行更新,具有以下头信息:
Cache-Control: private, no-store, no-cache, must-revalidate
他们使用一个CDN来处理视频和图片内容,地址为
http://video.ak.fbcdn.net/...
- ** > 在此处添加视频URL重写代码片段**
但是您必须有一个键参数才能访问视频。对于图片,他们使用“多对一CDN”,例如:
http://a6.sphotos.ak.fbcdn.net/...
您可以将“a6”替换为多对一的“键”。
- ** > 在此处添加图片URL重写代码片段**
🔗 缓存动态内容|去重内容
正如我之前描述的问题,对于每种情况,我们都可以提供一个解决方案。
🔗 旧方法
Youtube/CDN等网站造成了需要快速解决的问题。这些网站提供了大量对缓存不友好的API数据。这就是为什么旧方法被迅速开发出来的原因。
Amos Jeffries:这项工作以多种方式继续进行(头信息 Content-MD5、Digest:、Link: 等)。
🔗 Store URL Rewrite
在Squid-2.7中,集成了store_url_rewrite接口来解决资源去重问题。例如SourceForge,它也可以用于YouTube等。
#!/usr/bin/ruby
def main
while request = gets
request = request.split
if request[0]
case request[1]
when /^http:\/\/[a-zA-Z0-9\-\_\.]+\.dl\.sourceforge\.net\/.*/
puts request[0] + "http://dl.sourceforge.net.squid.internal/" + request[1].match(/^http:\/\/[a-zA-Z0-9\-\_\.]+\.dl\.sourceforge\.net\/(.*)/)[1]
else
puts request[0] + ""
end
else
puts ""
end
end
end
STDOUT.sync = true
main
这个辅助程序支持并发,无论如何都比没有并发的普通重写器要好。
该辅助程序的性能约为100,000个请求2.6秒。这意味着每分钟约2,000,000个请求。您可以通过以下方式自行测试:
-
创建重定向文件
head -100000 access.log | awk '{ print $7 " " $3"/-" " " $8 " " $6}' >/tmp/testurls -
进行测试
time ./rewritter.rb < /tmp/testurls >/dev/null
优点
- 实现简单。
缺点
- 仅适用于squid2分支。
- 检查仅基于请求的URL。在300状态码响应的情况下,URL将被缓存并可能导致无限循环。
- 无法与Squid的任何缓存接口(如ICP/HTCP/Cache Manager)进行交互,资源是“幽灵”。
(我编写了一个ICP客户端,并正在开发一个HTCP Switch/Hub来监控和控制实时缓存对象)
- 为了解决300状态码问题,提出了一个特定的补丁但未集成到Squid中。
- 300状态码问题可以通过ICAP RESPMOD重写来解决。
🔗 Web服务器和URL重写
简而言之,该想法是使用url_rewrite接口将请求静默重定向到一个本地Web服务器脚本。
- 脚本将为Squid获取URL并将其存储在HDD上,或者从HDD获取缓存的文件。
另一个类似风格的解决方案是youtube-cache使用的,后来在yt-cache中扩展。
优点
- 适用于任何Squid版本。
- 易于适配其他CDN。
缺点
- 不支持keep-alive,因此无法缓存带有“range”参数请求的YouTube(会导致YouTube播放器一直停止)。
- 完全不支持POST请求,它们将被视为GET(可以通过一些编码进行更改)。
- 如果两个人同时观看一个未缓存的视频,它将被两个人下载。
- 需要始终运行一个Web服务器。
- 缓存目录将由管理员手动管理,而不是由Squid的智能替换算法管理。
- 不能与tproxy一起使用(Web服务器将使用自己的IP获取请求,而不是Squid模拟客户端的方式)。
🔗 NGINX作为缓存对等体
在youtube-cache中,作者使用NGINX Web服务器作为cache_peer和反向代理。其想法是利用NGINX的“proxy_store”作为缓存存储以及“resolver”选项,使NGINX能够进行“Forward proxy”。NGINX具有一些允许其轻松使用请求参数作为“缓存存储键”一部分的有用功能。
对于YouTube,可以使用
proxy_store "/usr/local/www/nginx_cache/files/id=$arg_id.itag=$arg_itag.range=$arg_range";
优点
- 适用于任何Squid版本。
- 易于适配其他CDN。
缺点
- 不支持keep-alive,因此无法缓存带有“range”参数请求的YouTube(会导致YouTube播放器一直停止)。
- 一个请求将导致完整文件下载,并可能导致缓存Web服务器的DDOS或大量带宽消耗。
- 需要始终运行一个Web服务器。
- 缓存目录将由管理员手动管理,而不是由Squid的智能替换算法管理。
- 不能与tproxy一起使用。
🔗 ICAP解决方案总结
更新的Squid版本(2+)的“问题”在于store_url_rewrite接口未集成,结果大多数用户使用了旧版本的Squid。其他人则使用了url_rewrite和Web服务器的方式。许多人使用了videocache,它基于相同的想法,因为它有更新、支持和其他功能。
这导致Squid服务器从本地NGINX/APACHE/LIGHTHTTPD提供文件,从而带来了非常糟糕的缓存可维护性问题。
许多缓存管理员获得了YouTube视频缓存,但失去了Squid的大多数优势。
其思想是让Squid(2个实例)处理所有缓存获取等工作,而不是使用第三方缓存解决方案和Web服务器。因此,在我长期进行动态内容分析工作的基础上,我长期以来都有这个想法,但直到最近才进行了测试和实现。
我实现的解决方案适用于较新的Squid版本3+,可以使用ICAP服务器或url_rewrite这两种选项之一来实现,而ICAP具有许多优势。它需要
- 2个Squid实例
- ICAP服务器/url_rewrite脚本
- 非常快速的数据库引擎(MYSQL/PGSQL/REDIS/其他)
它会做什么?欺骗系统中的所有人!ICAP和url_rewrite能够透明地重写URL,对客户端来说,当客户端请求一个文件时,Squid 1会根据ACL通过ICAP服务器发出ICAP REQMOD(请求修改)。ICAP代码伪代码
分析请求。如果请求符合标准:从请求中提取所需数据(来自URL和其他头信息),创建一个内部“地址”,如“http://ytvideo.squid.internal/somekey”,在数据库中存储原始URL和修改后URL的键值对,将修改后的请求发送给Squid。
在Squid 1中,我们为.internal的所有目标域预先配置了cache_peer,因此重写的URL必须通过Squid 2获取。
Squid 2然后接收到对“http://ytvideo.squid.internal/somekey”的请求,并将请求传递给ICAP服务器。ICAP服务器在适当的时候从数据库中获取原始URL,并将请求重写到原始源服务器。
状态是:客户端认为它正在获取原始文件。Squid 1认为它正在获取“http://ytvideo.squid.internal/somekey”文件。Squid 2只是一个简单的代理(无缓存)。ICAP服务器协调工作流程。
结果是:Squid 1将使用唯一的键存储视频,该键可以通过ICP/HTCP/CACHEMGR/LOGS等进行验证。Squid 2只是一个简单的代理(无缓存)。ICAP服务器协调工作流程。
优点
- 缓存由Squid算法管理。
- 应该适用于任何支持ICAP/url_rewrite的Squid版本。(已在squid 3.1.19上测试)
- 可以基于URL和所有请求头构建键。
缺点
- 取决于数据库和ICAP服务器。
🔗 实现ICAP解决方案
需要
- 带有ICAP支持的Squid
- mysql数据库
- ICAP服务器(我编写了echelon-mod,专为项目需求设计)我也使用GreasySpoon ICAP服务器实现了这一点,可以在github上找到。
Squid 1
acl ytcdoms dstdomain .c.youtube.com
acl internaldoms dstdomain .squid.internal
acl ytcblcok urlpath_regex (begin\=)
acl ytcblockdoms dstdomain redirector.c.youtube.com
acl ytimg dstdomain .ytimg.com
refresh_pattern ^http://(youtube|ytimg)\.squid\.internal/.* 10080 80% 28800 override-lastmod override-expire override-lastmod ignore-no-cache ignore-private ignore-reload
maximum_object_size_in_memory 4 MB
#cache_peers section
cache_peer 127.0.0.1 parent 13128 0 no-query no-digest no-tproxy default name=internal
cache_peer_access internal allow internaldoms
cache_peer_access internal deny all
never_direct allow internaldoms
never_direct deny all
cache deny ytcblockdoms
cache deny ytcdoms ytcblcok
cache allow all
icap_enable on
icap_service_revival_delay 30
icap_service service_req reqmod_precache bypass=1 icap://127.0.0.2:1344/reqmod?ytvideoexternal
adaptation_access service_req deny internaldoms
adaptation_access service_req deny ytcblockdoms
adaptation_access service_req allow ytcdoms
adaptation_access service_req deny all
icap_service service_ytimg reqmod_precache bypass=1 icap://127.0.0.2:1344/reqmod?ytimgexternal
adaptation_access service_ytimg allow ytimg img
adaptation_access service_ytimg deny all
Squid 2
acl internalyt dstdomain youtube.squid.internal
acl intytimg dstdomain ytimg.squid.internal
cache deny all
icap_enable on
icap_service_revival_delay 30
icap_service service_req reqmod_precache bypass=0 icap://127.0.0.2:1344/reqmod?ytvideointernal
adaptation_access service_req allow internalyt
adaptation_access service_req deny all
icap_service service_ytimg reqmod_precache bypass=0 icap://127.0.0.2:1344/reqmod?ytimginternal
adaptation_access service_ytimg allow intytimg
adaptation_access service_ytimg deny all
MYSQL数据库
#i have used mysql db 'ytcache' table 'temp' with user and password as 'ytcache' with full rights for localhost and ip 127.0.0.1
create a memory table in DB with two very long varchar(2000) fields.
create give a user full rights to the db.
# it's recommended to truncate the temp memory table at least once a day because it has limited size.
ICAP服务器
我的ICAP服务器可以从:我的github下载。服务器用Ruby编写,并在1.9版本上进行了测试。服务器需要:
"rubygems"
gem "bundler"
gem "eventmachine"
gem "settingslogic"
gem "mysql"
gem "dbi"
有一个settings.yml配置文件。
注意在配置文件中设置服务器的本地IP地址。
我使用了IP 127.0.0.2来允许进行非常高强度的压力测试,并开放了大量端口。
🔗 使用url_rewrite的ICAP服务器的替代方案
我通过ICAP实现的相同逻辑也可以通过url_rewrite机制来实现。
我编写了一个特定的URL重写器,后端使用redis数据库/缓存服务器。我们可以使用与ICAP服务器相同的逻辑来重写每个Squid实例上的URL。您需要安装“redis”和Ruby的redis gem。
squid1.conf
acl internaldoms dstdomain .squid.internal
acl rewritedoms dstdomain .c.youtube.com av.vimeo.com .dl.sourceforge.net .ytimg.com
url_rewrite_program /opt/coordinator.rb
url_rewrite_children 5
url_rewrite_concurrency 50
url_rewrite_access deny internaldoms
url_rewrite_access allow all
squid2.conf
cache deny all
acl internaldoms dstdomain .squid.internal
url_rewrite_program /opt/coordinator.rb
url_rewrite_children 5
url_rewrite_concurrency 50
url_rewrite_access allow internaldoms
url_rewrite_access deny all
记住chmod +x coordinator.rb
coordinator.rb
#!/usr/bin/ruby
require 'syslog'
require 'redis'
class Cache
def initialize
@host = "localhost"
@db = "0"
@port = 6379
@redis = Redis.new(:host => @host, :port => @port)
@redis.select @db
end
def setvid(url,vid)
return @redis.setex "md5(" + vid+ ")",1200 ,url
end
def geturl(vid)
return @redis.get "md5(" + vid + ")"
end
def sfdlid(url)
m = url.match(/^http:\/\/.*\.dl\.sourceforge\.net\/(.*)/)
if m[1]
return m[1]
else
return nil
end
end
def vimid(url)
m = url.match(/.*\.com\/(.*)(\?.*)/)
if m[1]
return m[1]
else
return nil
end
end
def ytimg(url)
m = url.match(/.*\.ytimg.com\/(.*)/)
if m[1]
return m[1]
else
return nil
end
end
def ytvid(url)
id = getytid(url)
itag = getytitag(url)
range = getytrange(url)
redirect = getytredirect(url)
if id == nil
return nil
else
vid = id
end
if itag != nil
vid = vid + "&" + itag
end
if range != nil
vid = vid + "&" + range
end
if redirect != nil
vid = vid + "&" + redirect
end
return vid
end
private
def getytid(url)
m = url.match(/(id\=[a-zA-Z0-9\-\_]+)/)
return m.to_s if m != nil
end
def getytitag(url)
m = url.match(/(itag\=[0-9\-\_]+)/)
return m.to_s if m != nil
end
def getytrange(url)
m = url.match(/(range\=[0-9\-]+)/)
return m.to_s if m != nil
end
def getytredirect(url)
m = url.match(/(redirect\=)([a-zA-Z0-9\-\_]+)/)
return (m.to_s + Time.now.to_i.to_s) if m != nil
end
end
def rewriter(request)
case request
when /^http:\/\/[a-zA-Z0-9\-\_\.]+\.squid\.internal\/.*/
url = $cache.geturl(request)
if url != nil
return url
else
return ""
return ""
end
when /^http:\/\/[a-zA-Z0-9\-\_\.]+\.dl\.sourceforge\.net\/.*/
vid = $cache.sfdlid(request)
$cache.setvid(request, "http://dl.sourceforge.net.squid.internal/" + vid) if vid != nil
url = "http://dl.sourceforge.net.squid.internal/" + vid if vid != nil
return url
when /^http:\/\/av\.vimeo\.com\/.*/
vid = $cache.vimid(request)
$cache.setvid(request, "http://vimeo.squid.internal/" + vid) if vid != nil
url = "http://vimeo.squid.internal/" + vid if vid != nil
return url
when /^http:\/\/[a-zA-Z0-9\-\_\.]+\.c\.youtube\.com\/videoplayback\?.*id\=.*/
vid = $cache.ytvid(request)
$cache.setvid(request, "http://youtube.squid.internal/" + vid) if vid != nil
url = "http://youtube.squid.internal/" + vid if vid != nil
return url
when /^http:\/\/[a-zA-Z0-9\-\_\.]+\.ytimg\.com\.*/
vid = $cache.ytimg(request)
$cache.setvid(request, "http://ytimg.squid.internal/" + vid) if vid != nil
url = "http://ytimg.squid.internal/" + vid if vid != nil
return url
when /^quit.*/
exit 0
else
return ""
end
end
def log(msg)
Syslog.log(Syslog::LOG_ERR, "%s", msg)
end
def main
Syslog.open('cordinator.rb', Syslog::LOG_PID)
log("Started")
#read_requests do |request|
while request = gets.split
if request[0] && request[1]
log("original request [#{request.join(" ")}].") if $debug
url = request[0] +" " + rewriter(request[1])
log("modified response [#{url}].") if $debug
puts url
else
puts ""
end
end
end
$debug = false
$cache = Cache.new
STDOUT.sync = true
main
⚠️ Disclaimer: Any example presented here is provided "as-is" with no support
or guarantee of suitability. If you have any further questions about
these examples please email the squid-users mailing list.
类别: ConfigExample
导航:网站搜索,站点页面,类别,🔼向上