由於Wordpress通常用來建構以內容為主的網站,當網站流量變高時,如何加速自然是個重要的課題。加速的方法不少,跟Wordpress的執行環境息息相關。本文說明如何在VPS或者dedicate環境安裝Varnish來加速。
Varnish 是個網站加速軟體,透過將常用的檔案內容快取,減低網頁服務器、PHP以及資料庫存取的次數,以達到加速的目的。除了作為網站加速外,Varnish本身提供基本的覆載均衡(load balancing)的功能。
前提假設
作業系統:Ubuntu 10.04 64-bit
Varnish: 2.1.0-2ubuntu0.1
Varnish: 2.1.0-2ubuntu0.1
目標
- 使用Varnish來達到網站加速以及高可用性的目的。
- 當網站伺服器不再回應,Varnish就不應該再將用戶請求導到那個故障的伺服器。
- 對於登入的會員不使用快取的頁面,因為可能需要顯示個性化的內容。
安裝Varnish
使用Ubuntu的套件庫,Varnish的安裝相當容易,執行下列指令即可:
# apt-get -y install varnish
設定Varnish
編輯 /etc/default/varnish, 假設我們希望客製化的Varnish設定檔位置為/srv/www/webhost/varnish/wordpress.vcl。
# Configuration file for varnish
#
# /etc/init.d/varnish expects the variables $DAEMON_OPTS, $NFILES and $MEMLOCK
# to be set from this shell script fragment.
#
# Maximum number of open files (for ulimit -n)
NFILES=8192
# Maximum locked memory size (for ulimit -l)
# Used for locking the shared memory log in memory. If you increase log size,
# you need to increase this number as well
MEMLOCK=82000
# Default varnish instance name is the local nodename. Can be overridden with
# the -n switch, to have more instances on a single server.
INSTANCE=$(uname -n)
#
# See varnishd(1) for more information.
#
# # Main configuration file. You probably want to change it :)
VARNISH_VCL_CONF=/srv/www/webhost/etc/varnish/wordpress.vcl
#
# # Default address and port to bind to
# # Blank address means all IPv4 and IPv6 interfaces, otherwise specify
# # a host name, an IPv4 dotted quad, or an IPv6 address in brackets.
VARNISH_LISTEN_ADDRESS=
VARNISH_LISTEN_PORT=80
#
# # Telnet admin interface listen address and port
VARNISH_ADMIN_LISTEN_ADDRESS=127.0.0.1
VARNISH_ADMIN_LISTEN_PORT=6082
#
# # The minimum number of worker threads to start
VARNISH_MIN_THREADS=4
#
# # The Maximum number of worker threads to start
VARNISH_MAX_THREADS=1000
#
# # Idle timeout for worker threads
VARNISH_THREAD_TIMEOUT=120
#
# # Cache file location
VARNISH_STORAGE_FILE=/var/lib/varnish/$INSTANCE/varnish_storage.bin
#
# # Cache file size: in bytes, optionally using k / M / G / T suffix,
# # or in percentage of available disk space using the % suffix.
VARNISH_STORAGE_SIZE=100M
#
# # File containing administration secret
VARNISH_SECRET_FILE=/etc/varnish/secret
#
# # Backend storage specification
VARNISH_STORAGE="file,${VARNISH_STORAGE_FILE},${VARNISH_STORAGE_SIZE}"
#
# # Default TTL used when the backend does not specify one
VARNISH_TTL=120
#
# # DAEMON_OPTS is used by the init script. If you add or remove options, make
# # sure you update this section, too.
DAEMON_OPTS="-a ${VARNISH_LISTEN_ADDRESS}:${VARNISH_LISTEN_PORT} \
-f ${VARNISH_VCL_CONF} \
-T ${VARNISH_ADMIN_LISTEN_ADDRESS}:${VARNISH_ADMIN_LISTEN_PORT} \
-t ${VARNISH_TTL} \
-w ${VARNISH_MIN_THREADS},${VARNISH_MAX_THREADS},${VARNISH_THREAD_TIMEOUT} \
-S ${VARNISH_SECRET_FILE} \
-s ${VARNISH_STORAGE}"
## Alternative 4, Do It Yourself
#
# DAEMON_OPTS=""
編輯Varnish設定檔(wordpress.vcl)
編輯/srv/www/webhost/varnish/wordpress.vcl檔案,下面為其參考內容。
// Defining our backends
backend node1 {
.host = "node1.local";
.port = "8080";
.probe = {
.url = "/status.html";
.interval = 5s;
.timeout = 1 s;
.window = 5;
.threshold = 3;
}
.connect_timeout = 600s;
.first_byte_timeout = 600s;
.between_bytes_timeout = 600s;
}
backend node2 {
.host = "node2.local";
.port = "8080";
.probe = {
.url = "/status.html";
.interval = 5s;
.timeout = 1 s;
.window = 5;
.threshold = 3;
}
.connect_timeout = 600s;
.first_byte_timeout = 600s;
.between_bytes_timeout = 600s;
}
// Defining our cluster including end points for purge
director cluster round-robin {
{.backend = node1;}
{.backend = node2;}
}
acl purge {
"localhost";
"node1";
"node2";
}
sub vcl_recv {
## determine real client IP
if (req.http.cf-connecting-ip) {
set req.http.X-Forwarded-For = req.http.cf-connecting-ip;
} else 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;
}
## set the backend to the cluster
set req.backend = cluster;
## check for a mobile device
call device_detection;
## enable grace period.
set req.grace = 30s;
if (req.request == "PURGE") {
if (!client.ip ~ purge) {
error 405 "Not allowed.";
}
purge("req.url == " req.url " && req.http.host == " req.http.host);
error 200 "Purged.";
}
## normalize Accept-Encoding header.
if (req.http.Accept-Encoding) {
if (req.url ~ "\.(jpg|png|gif|gz|tgz|bz2|tbz|mp3|ogg)$") {
# No point in compressing these
remove req.http.Accept-Encoding;
} elsif (req.http.Accept-Encoding ~ "gzip") {
set req.http.Accept-Encoding = "gzip";
} elsif (req.http.Accept-Encoding ~ "deflate") {
set req.http.Accept-Encoding = "deflate";
} else {
# unkown algorithm
remove req.http.Accept-Encoding;
}
}
## remove cookies for static resources.
if (req.url ~ "^/[^?]+\.(jpeg|jpg|png|gif|ico|js|css|txt|gz|zip|lzma|bz2|tgz|tbz|html|htm)(\?.*|)$") {
unset req.http.cookie;
set req.url = regsub(req.url, "\?.*$", "");
return(lookup);
}
## rename wordpress_test_cookie to something else to simplfy cookie removal afterwards.
if (req.http.Cookie && req.http.Cookie ~ "wordpress_") {
set req.http.Cookie = regsuball(req.http.Cookie, "wordpress_test_cookie=", "; wpjunk=");
}
### do not cache these files:
##never cache the admin pages, or the server-status page
if (req.request == "GET" && (req.url ~ "(wp-admin|bb-admin|server-status)"))
{
return(pipe);
}
##dont cache ajax requests
if(req.http.X-Requested-With == "XMLHttpRequest" || req.url ~ "nocache" || req.url ~ "(control.php|wp-comments-post.php|wp-login.php|bb-login.php|bb-reset-password.php|register.php)")
{
return (pass);
}
### don't cache authenticated sessions
if (req.http.Cookie && req.http.Cookie ~ "(wordpress_|PHPSESSID)") {
return(pass);
}
## Cache any dynamic content
if (req.url !~ "wp-(login|admin|signup)" && req.url !~ "preview" || req.url ~ "admin-ajax.php"){
## "Request is not for login, admin, preview, sign up or admin-ajax so don't cache it";
if (req.http.Cookie !~ "wordpress_logged_in "){
//log "User is not logged in";
if (req.http.Cookie !~ "wp-postpass"){
//log "Post is not password protected";
unset req.http.cookie;
return(lookup);
}
}
}
}
sub vcl_hit {
if (req.request == "PURGE") {
set obj.ttl = 0s;
error 200 "Purged.";
}
}
sub vcl_miss {
if (req.request == "PURGE") {
error 404 "Not in cache.";
}
if (!(req.url ~ "wp-(login|admin)")) {
unset req.http.cookie;
}
if (req.url ~ "^/[^?]+.(jpeg|jpg|png|gif|ico|js|css|txt|gz|zip|lzma|bz2|tgz|tbz|html|htm)(\?.|)$") {
unset req.http.cookie;
set req.url = regsub(req.url, "\?.*$", "");
}
if (req.url ~ "^/$") {
unset req.http.cookie;
}
}
sub vcl_fetch {
// When fetching images we can set a long caching marker that we can access later
if (req.request == "GET" && req.url ~ "\.(jpg|jpeg|gif|ico|css|js|png)$") {
set beresp.http.magicmarker = "1";
}
// Don't cache mobile requests
if (req.http.X-Device == "mobile"){
set beresp.ttl = 0s;
//log "Not caching mobile requests";
}
// Don't cache error pages
if (beresp.status == 404 || beresp.status == 503 || beresp.status >= 500){
set beresp.ttl = 0s;
}
## enable grace period.
set req.grace = 30s;
// Some debug code for why objects are/aren't cachable
// Varnish determined the object was not cacheable
if (!beresp.cacheable) {
set beresp.http.X-Cacheable = "NO:Not Cacheable";
// You don't wish to cache content for logged in users
} elsif (req.http.Cookie ~ "(UserID|_session)") {
set beresp.http.X-Cacheable = "NO:Got Session";
//log "It appears a session is in process so we have returned pass";
return(pass);
// You are respecting the Cache-Control=private header from the backend
} elsif (beresp.http.Cache-Control ~ "private") {
set beresp.http.X-Cacheable = "NO:Cache-Control=private";
//log "It appears this is private so we have returned pass";
return(pass);
// You are extending the lifetime of the object artificially
} elsif (beresp.ttl < 1s) {
set beresp.ttl = 5s;
set beresp.grace = 5s;
set beresp.http.X-Cacheable = "YES:FORCED";
// Varnish determined the object was cacheable
} else {
set beresp.http.X-Cacheable = "YES";
}
}
sub vcl_hash {
# Each cached page has to be identified by a key that unlocks it.
# Add the browser cookie only if a WordPress cookie found.
if ( req.http.Cookie ~"(wp-postpass|wordpress_logged_in|comment_author_)" ) {
set req.hash += req.http.Cookie;
}
}
## Deliver
sub vcl_deliver {
## set header for indicating hit or miss.
if (obj.hits > 0) {
set resp.http.X-Cache = "HIT";
set resp.http.X-Cache-Hits = obj.hits;
} else {
set resp.http.X-Cache = "MISS";
}
## We'll be hiding some headers added by Varnish. We want to make sure people are not seeing we're using Varnish.
## Since we're not caching (yet), why bother telling people we use it?
remove resp.http.X-Varnish;
remove resp.http.Via;
remove resp.http.Age;
remove resp.http.X-Pingback;
## We'd like to hide the X-Powered-By headers. Nobody has to know we can run PHP and have version xyz of it.
remove resp.http.X-Powered-By;
## fake the server signature
set resp.http.Server = "i-me.tw/1.0";
if (resp.http.magicmarker) {
//log "Magicmarker set so setting our own client side caching";
unset resp.http.magicmarker;
set resp.http.Cache-Control = "max-age=648000";
set resp.http.Expires = "Thu, 01 May 2014 00:10:22 GMT";
set resp.http.Last-Modified = "Mon, 25 Apr 2011 01:00:00 GMT";
set resp.http.Age = "647";
}
}
sub device_detection {
// Default to thinking it's a PC
set req.http.X-Device = "pc";
// Add all possible agent strings - These are the most popular agent strings
//log "Checking for mobile device";
if (req.http.User-Agent ~ "iP(hone|od)" || req.http.User-Agent ~ "Android" || req.http.User-Agent ~ "Symbian" || req.http.User-Agent ~ "^BlackBerry" || req.http.User-Agent ~ "^SonyEricsson"
|| req.http.User-Agent ~ "^Nokia" || req.http.User-Agent ~ "^SAMSUNG" || req.http.User-Agent ~ "^LG" || req.http.User-Agent ~ "webOS") {
//log "Mobile device detected";
//log req.http.cookie;
//log "Following req.url";
//log req.url;
if (req.url !~ "wptouch_view=normal"){
//log "wptouch_switch_toggle is not set";
set req.http.X-Device = "mobile";
}
else{
//log "this should not be redirecting to mobile";
//log "wp touch view is set to normal so we shouldn't be setting a device type other thna PC";
set req.http.X-Device = "pc";
error 750 req.http.host;
}
}
// These are some more obscure agent strings
if (req.http.User-Agent ~ "^PalmSource"){
set req.http.X-Device = "mobile";
}
}
結論
筆者不保證本文提供的Varnish設定正確或已經是最佳化,讀者要根據自己需要自行調整。
參考來源
- Optimizing WordPress with Nginx, Varnish, APC, W3 Total Cache, and Amazon S3 (With Benchmarks)
- Speed up WordPress with Apache and Varnish





尚未有迴響。