Nginx+Varnish 实现动静分离,为服务器分流,降低服务器负载
相必大家在看加快网站响应速度方面的文章时,都提过这么一条:动静分离。那怎样实现动静分离呢,这里笔者就亲自搭建相关服务实现动静分离。
动静分离是一种架构,就是把静态文件,比如JS、CSS、图片甚至有些静态页面交给独立的服务器集群处理,从而进行分流,使服务器降低压力。
上面说把一些静态的文件分离出去,有读者就会笑了,静态文件能有多少,能消耗多少资源。
读者以实际经验告诉大家,千万不要小瞧这些静态文件,现在大部分网站都是以视频、图片为主,试想下天猫、淘宝、京东之类,文字能有多少,图片、视频又是多少,就知道这个量到底是多么的庞大,而动静分离从笔者公司实际使用经验来看,效果是杠杠的。
笔者这里的环境是: Centos6.4 Nginx Varnish , 其中Nginx响应请求,Varnish主要做缓存和反向代理(笔者这里说个题外话,很多人疑惑反向代理和正向代理,这两个概念说的都有些玄幻,笔者有个简单粗暴的解释. 反向代理:隐藏真实服务器信息, 正向代理:隐藏真实客户端信息)
笔者这里弄三台主机:192.168.138.10 192.168.138.3 192.168.138.4 (10安装Varnish,3、4安装Nginx)
第一步:先在92.168.138.3 192.168.138.4 两台机器上安装Nginx,这个安装相对容易,笔者就不做累赘了,只要你看到下面两个页面就OK了
第二步:在192.168.138.10上安装Varnish
这里笔者从官网上下载了比较新的 Varnish-3.0.7, 笔者准备使用源码编译安装,安装在/usr/local/varnish 目录下
tar -zxvf varnish-3.0.7.tar.gz
安装之前还是老话,依赖先安装上,笔者在其官方网站上找到了其依赖的包
https://www.varnish-cache.org/docs/4.0/installation/install.html
yum install -y autoconf automake jemalloc-devel libedit-devel libtool ncurses-devel pcre-devel pkgconfig python-docutils python-sphinx
然后进行编译前检查
./configure --prefix=/usr/local/varnish/ --enable-dependency-tracking --enable-developer-warnings --enable-debugging-symbols
检查无误后开始编译安装
make && make install
第三步:开始配置varnish
其配置文件位于 /usr/local/varnish/etc/varnish/default.vcl 可以看到里面已经有些范例
笔者在这里先要对其架构及一些常用函数加以说明,不然后面都看不懂在配置写什么玩意
varnish使用了一种叫VCL的语言去对其进行配置,至于这语言啥玩意,就不用过多关心了,大致了解下就OK了,其语法可以看里面的相关范例,里面有些函数,下面一一列出
vcl_recv 函数: 接收和处理请求,当请求到达并成功接收后被调用,判断请求的数据并决定如何处理请求;该函数返回值有这么些关键字-》
pass 表示进入pass模式,将请求交给vcl_pass 函数
pipe 表示进入pipe模式,将请求交个vcl_pipe函数
error code[reason] 表示返回'code'给客户端并放弃请求, 'code'是错误标示例如200 405等,'reason'是错误原因
vcl_pipe函数:该函数在进入pipe模式时被调用,用于将请求直接传递给后端主机,在请求和返回内容没有改变的情况下,将不变的内容返回给客户端,直到这个链接关闭;该函数有这么些返回值=>
error code[reason] 同vcl_recv
pipe
vcl_pass函数: 该函数进入pass模式被调用,用于直接将请求发送给后端主机,后端主机响应后发送给客户端,不进行任何缓存,每次都返回最新内容,该函数有几个返回值=>
error code[reason] 同vcl_recv
pass
lookup函数: 表示在缓存中查找到被请求的对象,并且根据查找的结果交给vcl_hit函数(命中)或vcl_miss(未命中)函数处理
vcl_hit函数:在执行lookup后,如果在缓存中找到对象,该函数将会被自动调用,该函数有以下几个返回值 =>
deliver 表示找到内容发送给客户端,把控制权交给vcl_deliver函数
error code[reason] 同vcl_recv
pass
vcl_miss函数: 在执行lookup后,在缓存中没有找到对象,该函数被调用,该函数可用于判断是否从后端请求内容,该函数有以下几个返回值 =>
fetch 表示从后端获取内容,并把控制权交给vcl_fetch函数
error code[reason] 同vcl_recv
pass
vcl_fetch函数:从后端主机更新缓存获取内容后调用该函数,接着判断获取的内容是放入缓存还是直接给客户端,该函数有以下几个返回值=》
error code[reason] 同vcl_recv
pass
deliver
vcl_deliver函数:将在缓存中找到的内容发送给客户端调用的方法,该函数有以下几个返回值=》
error code[reason] 同vcl_recv
deliver
vcl_timeout函数:在缓存内容到期前调用该函数,该函数有以下几个返回值=》
discard 表示中缓存中清除该内容
fetch
vcl_discard函数:缓存内容到期后或者缓存空间不够时自动被调用,该函数有以下几个返回值=》
keep 表示在内容中保留该缓存
discard
VCL处理流程可以用下面这张图表示
Receive状态:请求处理的入口状态,根据VCL规则判断该请求应该在Pass或Pipe,还是进入Lookup(到本地缓存中查询)
Lookup状态:进入此状态后,会在hash表中查询数据,如果找到则进入Hit状态,否则进入Miss状态
Pass状态:在此状态下会进入后端获取内容,既进入Fetch状态
Fetch状态:从后端获取请求,发送请求,获得数据,并存储到本地
Deliver状态:将获取的数据发送给客户端,然后完成本次请求
VCL里面还有些变量,可以用在VCL函数中,用于逻辑处理
请求到达后可以使用的内置公用变量:
req.backend => 指定对应的后端主机
server.ip => 表示服务器端IP
client.ip => 表示客户端IP
req.request => 指定请求的类型,如GET POST PUT DELETE
req.url => 知道请求的URL
req.proto => 表示客户端发起请求的HTTP协议版本
req.http.header => 表示请求中的头部信息
req.restarts => 表示请求重启的次数,默认最大值为4
VCL向后端主机请求时使用的内置变量
beresp.request => 指定请求的类型,如GET POST
beresp.url => 指定请求的地址
beresp.proto => 指定请求的协议版本号
beresp.http.header => 对应请求的HTTP头信息
beresp.ttl => 表示缓存的周期,单位秒
从cache或后端主机获取内容后可以使用的内置变量
obj.status => 表示返回的状态码,如200 404 500 等
obj.cacheable => 表示返回的内容是否可以缓存
obj.valid => 表示是否是有效的HTTP应答
obj.response => 表示应答的状态信息
obj.ttl => 表示返回内容的生存周期,单位秒
obj.lastuse => 表示上次请求到现在的间隔,单位秒
客户端应答时可以使用的变量
resp.status => 表示返回给客户端的状态码
resp.proto => 表示返回给客户端的HTTP协议版本
resp.http.header => 表示返回给客户端的头信息
resp.response => 表示返回给客户端的状态信息
上面可能不全,具体的可以到其官方网站上面查看
下面笔者有个配置例子,里面有注释,可以瞅瞅
#配置两台后端主机 backend myserver3 { .host = "192.168.138.3"; .port = "80"; } #配置两台后端主机 backend myserver4 { .host = "192.168.138.4"; .port = "80"; } #这里定义一个myserver的director director myserver round-robin { {.backend = myserver3;} {.backend = myserver4;} } acl purge { "localhost"; "127.0.0.1"; } #recv状态 sub vcl_recv { #如果访问 laiwojia.la 或者 www.laiwojia.la 就使用该varnish进行代理 前提是你要在你本地配置laiwojia.la这个虚拟主机奥 if (req.http.host ~ "(www.)?laiwojia.la") { set req.backend = myserver; } if (req.restarts == 0) { if (req.http.x-forwarded-for) { set req.http.X-Forwarded-For = req.http.X-Forwarded-For + ", " + client.ip; } else { set req.http.X-Forwarded-For = client.ip; } } #如果请求的类型不是GET HEAD PUT POST TRACE OPTIONS DELETE 进入pipe模式 if (req.request != "GET" && req.request != "HEAD" && req.request != "PUT" && req.request != "POST" && req.request != "TRACE" && req.request != "OPTIONS" && req.request != "DELETE") { /* Non-RFC2616 or CONNECT which is weird. */ return (pipe); } #如果不是GET或者HEAD类型的请求 进入到pass模式 if (req.request != "GET" && req.request != "HEAD") { /* We only deal with GET and HEAD by default */ return (pass); } #有认证或者cookie存在 进入到pass模式 if (req.http.Authorization || req.http.Cookie) { /* Not cacheable by default */ return (pass); } #如果URL中含有.php .jsp .do 并且以此结尾或者后面还有?或者# 进入pass模式 if (req.url ~ ".(php|jsp|do)($|?|#)") { return (pass) } if (req.request == "PURGE") { if (!client.ip ~ purge) { error 405 "Not allowed."; } return (lookup); } return (lookup); } ub vcl_pipe { # # Note that only the first request to the backend will have # # X-Forwarded-For set. If you use X-Forwarded-For and want to # # have it set for all requests, make sure to have: # # set bereq.http.connection = "close"; # # here. It is not set by default as it might break some broken web # # applications, like IIS with NTLM authentication. return (pipe); } # sub vcl_pass { return (pass); } # sub vcl_hash { hash_data(req.url); if (req.http.host) { hash_data(req.http.host); } else { hash_data(server.ip); } return (hash); } sub vcl_hit { if (req.request == "PURGE") { purge; error 200 "Purged."; } return (deliver); } # sub vcl_miss { if (req.request == "PURGE") { purge; error 200 "Purged."; } return (fetch); } sub vcl_fetch { #如果请求的类型是GET 请求url以 html png gif jpeg 等等结尾 进行缓存 缓存时间600s if (req.request == "GET" && req.url ~ ".(html|png|gif|jpeg|pdf|ppt|doc|docx|rar|zip|bmp|swf|mp3|mp4|rmvb|mov|avi|css|js|jpeg|htm)$") { set beresp.ttl = 600s; } return (deliver); } sub vcl_deliver { #给HTTP头上加上标示 用于区分是否命中缓存 if (obj.hits > 0) { set resp.http.X-Cache = "HIT from www.laiwojia.la"; } else { set resp.http.X-Cache = "MISS from www.laiwojia.la"; } return (deliver); } #当发生错误后返回给客户端的页面 sub vcl_error { set obj.http.Content-Type = "text/html; charset=utf-8"; set obj.http.Retry-After = "5"; synthetic {" <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html> <head> <title>"} + obj.status + " " + obj.response + {"</title> </head> <body> <h1>Error "} + obj.status + " " + obj.response + {"</h1> <p>"} + obj.response + {"</p> <h3>Guru Meditation:</h3> <p>XID: "} + req.xid + {"</p> <hr> <p>Varnish cache server</p> </body> </html> "}; return (deliver); } sub vcl_init { return (ok); } # sub vcl_fini { return (ok); }
好了,上面配置就是这样的,然后你要在本地加上 www.laiwojia.la laiwojia.la 的解析
vim /etc/hosts #添加 127.0.0.1 www.laiwojia.la laiwojia.la
千万不要忘记在192.168.138.3 192.168.138.4 这两台机器的nginx上配置 laiwojia.la 这个虚拟机奥,这里笔者贴出配置文件(nginx.conf 部分)
server{ listen 80; server_name laiwojia.la; root /usr/website/html/www.laiwojia.la; index index.html index.htm index.php; access_log /usr/website/logs/www.laiwojia.la/access.log; error_log /usr/website/logs/www.laiwojia.la/errors.log; }
当然上面的 /usr/website/html/www.laiwojia.la 这个目录要有 然后在里面建一个 test.html 里面写上内容,然后做好本地解析
vim /etc/hosts #添加 127.0.0.1 www.laiwojia.la laiwojia.la
好,确保两台机器上虚拟机运行没有问题
最后一步:启动varnish
/usr/local/varnish/sbin/varnishd -f /usr/local/varnish/etc/varnish/default.vcl -s malloc,1G -T 127.0.0.1:2000 -a 0.0.0.0:80
这个是笔者在其官方网站上找到的启动方法 -f 就是配置文件 -a 就是IP地址加上端口 我们这里监听80端口
OK,一切完结,我们开始测试下
curl -I www.laiwojia.la
激动人心的时刻来了
HTTP/1.1 200 OK
Server: nginx/1.6.2
Content-Type: text/html
Last-Modified: Thu, 28 Jan 2016 11:55:24 GMT
ETag: "56aa01ac-f"
Content-Length: 15
Accept-Ranges: bytes
Date: Thu, 28 Jan 2016 12:27:33 GMT
X-Varnish: 100516502
Age: 0
Via: 1.1 varnish
Connection: keep-alive
X-Cache: MISS from www.laiwojia.la
这下是不是要哭,怎么是 X-Cache: MISS from www.laiwojia.la 没有击中啊,不要担心,这是第一次,我们再来一次
HTTP/1.1 200 OK
Server: nginx/1.6.2
Content-Type: text/html
Last-Modified: Thu, 28 Jan 2016 11:55:24 GMT
ETag: "56aa01ac-f"
Content-Length: 15
Accept-Ranges: bytes
Date: Thu, 28 Jan 2016 12:27:45 GMT
X-Varnish: 100516503 100516502
Age: 12
Via: 1.1 varnish
Connection: keep-alive
X-Cache: HIT from www.laiwojia.la
这下终于是久违的 X-Cache: HIT from www.laiwojia.la
OK,这里,大体的配置就完了!
是不是有同学要问了,尼玛这怎么就叫做动静分离了?
想想上面的laiwojia.la 这个域名 ,如果是大家的,大家可以把它配置成 img.xxx.com 之类,文件都上传到这个域名下面(也可以将集群里面的静态文件目录定时复制到这个域名的主机下面),然后大家就可以通过 img.xxx.com 这样的域名访问了。笔者公司是这样做的,我们的静态文件都放在服务器 static 目录里,然后静态文件服务器集群每5分钟复制下这个目录,同时所有上传都上传到静态文件服务器,所有静态文件、图片引用都使用静态文件服务器前缀 img.xxx.com , 为了管理方便,笔者公司里面的静态文件后面都有后缀 '?v=2016012801',只要更换后面的版本号,缓存就会更新,当然了还有内部机制(有专门清理缓存的接口)。这样动态文件在规定的服务器集群,静态文件也在规定的服务器集群,相互分开,大大提升页面加载速度,提高网站吞吐量。