前言

上一篇文章里谈到了使用七牛CDN加速WordPress,但CDN无法解决部分静态资源如css以及字体地址被屏蔽的问题,于是后面直接在WordPress的主题目录下执行

1
2
grep -rn "fonts.googleapis.com" *
grep -rn "fonts.gstatic.com" *

找到所有包含fonts.gstatic.com以及fonts.googleapis.com链接的文件,下载对应的CSS与字体到服务器上,并将这些链接逐一替换,这样可以保证站点不被屏蔽的情况下,即使速度稍慢,也能完整加载,问题是不能经常变更主题,不然逐一分析替换所有无法加载的资源(通常都是谷歌提供的资源)也相当麻烦。

然后启用Nginx的静态资源缓存策略,将js、css、字体、图片等静态资源的过期时间设为永久,访问了首页后继续访问其他页面的流量消耗只包含动态资源以及未缓存的静态资源,后台页面也会更流畅。

最后配置HTTPS并启用HTTP2,首部压缩,服务端推送,多路复用等特性可以让静态资源并发加载同时减少流量消耗,而升级到PHP7或者使用HHVM也能够提升动态页面的响应速度,剩下的唯一问题就是选一个好的服务提供商了。

到现在未尝试过的优化,还剩下启用mircocache,openVZ平台启用lkl-bbr,使用固定链接静态化博客,这次会尝试下这些优化策略。

启用固定链接与缓存

Nginx官方教程里的microcache以及适配wp-super-cache都要求使用固定链接,对POST请求、带查询参数的GET请求、后台页面等不缓存,只有不带参数的GET请求结果缓存在内存或者硬盘上,后续请求命中缓存后直接返回,不需要再次查询数据库和渲染页面。由于这个博客一直没有启用固定链接,文章的链接都是查询格式的,最初microcahe使用起来效果不明显,通过Chrome调试工具发现主要时间都耗在网络传输上。

Nginx官方博客的教程链接:9-tips-for-improving-wordpress-performance-with-nginx

理论上从响应时间看,内存静态内容>硬盘静态内容>动态内容,前两者之间的差距在请求量小的情况下不是很明显,microcache直接根据请求的类型缓存,而使用wp-super-cache插件的灵活性更高,可以在后台创建更详细的缓存策略,两种都尝试之后,还是选择了使用wp-super-cache。

首先需要启用固定链接,在WordPress的后台设置中可以开启,我的固定链接格式如图:

更适合SEO的格式是链接中包含文章标题,如上面给出的Nginx官方教程链接,使用英文和横杠分割单词,有利于收录和检索,修改后注意做好访问重定向,避免旧的链接出现404的情况

然后安装wp-super-cache插件,启用缓存,最傻瓜的方式是勾选所有推荐的选项,然后测试是否可行。缓存的页面文件默认在/wp-content/cache目录下,我启用了预缓存,静态化所有文章,压缩页面以及移动设备支持,可以看到缓存目录下包含了所有的站点内容。

最后配置Nginx,让符合条件的请求首先匹配硬盘上的文件,文件不存在时再转发给FastCGI

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
    set $cache_uri $request_uri;

    # POST requests and URLs with a query string should always go to PHP
    if ($request_method = POST) {
        set $cache_uri 'null cache';
    }
    if ($query_string != "") {
        set $cache_uri 'null cache';
    }   

    # Don't cache URIs containing the following segments
    if ($request_uri ~* "(/wp-admin/|/xmlrpc.php|/wp-(app|cron|login|register|mail).php
                          |wp-.*.php|/feed/|index.php|wp-comments-popup.php
                          |wp-links-opml.php|wp-locations.php |sitemap(_index)?.xml
                          |[a-z0-9_-]+-sitemap([0-9]+)?.xml)") {

        set $cache_uri 'null cache';
    }  

    # Don't use the cache for logged-in users or recent commenters
    if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+
                         |wp-postpass|wordpress_logged_in") {
        set $cache_uri 'null cache';
    }

    # Use cached or actual file if it exists, otherwise pass request to WordPress
    location / {
        try_files /wp-content/cache/supercache/$http_host/$cache_uri/index.html
                  $uri $uri/ /index.php;
    } 
    location ~ .php$ {
        try_files $uri /index.php;
        include fastcgi.conf;
        fastcgi_pass unix:/var/run/php7.2-fpm.sock;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

启用BBR

KVM虚拟化的主机可以安装版本4.10及以上的内核,为整个系统启用BBR,而在openVZ平台上,只能使用lkl-bbr为指定程序启用bbr,由于一直无法为nginx启用bbr,只能改为先为haproxy启用bbr,再将请求转发给nginx。网上的教程没有解决这种情况下nginx收到的请求源地址都是内网地址的问题,但通过在haproxy中启用send-proxy和在nginx中启用proxy_protocol后,可以获取到请求的源地址。

成功开启后,对于插图较多的文章,加载速度会有比较大提升,由于多了一层转发,在全站开启https的情况下,wp-super-cache测试缓存时会报ssl相关的错误,需要进一步了解haproxy做负载均衡情况下网络请求的转发流程,目前暂时不启用,但还是记录一下。

首先安装haproxy,然后修改service文件,让haproxy直接读取配置文件并运行,修改后/lib/systemd/system/haproxy.service文件内容如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
[Unit]
Description=HAProxy Load Balancer
Documentation=man:haproxy(1)
Documentation=file:/usr/share/doc/haproxy/configuration.txt.gz
After=network.target syslog.service
Wants=syslog.service

[Service]
Environment=CONFIG=/etc/haproxy/haproxy.cfg
EnvironmentFile=-/etc/default/haproxy
ExecStart=/usr/sbin/haproxy -f ${CONFIG} -p /run/haproxy.pid $EXTRAOPTS
ExecReload=/usr/sbin/haproxy -c -f ${CONFIG}
ExecReload=/bin/kill -USR2 $MAINPID
KillMode=mixed
Restart=always

[Install]
WantedBy=multi-user.target

其次,修改/etc/haproxy/haproxy.cfg文件,配置监听端口与转发目标,注意设置send-proxy

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
global
log 127.0.0.1 local0 notice
maxconn 2000
user haproxy
group haproxy

defaults
log global
mode tcp
balance leastconn
option dontlognull
option tcplog
option tcpka
timeout connect 5000
timeout client 10000
timeout server 10000

frontend proxy-in-80
bind *:80
default_backend proxy-out

frontend proxy-in-443
bind *:443
default_backend proxy-out

backend proxy-out
server nginx 10.2.0.1 maxconn 20480 send-proxy

再次,修改/etc/default/haproxy文件,配置lkl相关环境变量

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
LD_PRELOAD=/etc/liblkl-hijack.so
LKL_HIJACK_NET_QDISC="root|fq"
LKL_HIJACK_SYSCTL="net.ipv4.tcp_fastopen=3;net.ipv4.tcp_congestion_control=bbr;net.ipv4.tcp_wmem=4096 65536 67108864"
LKL_HIJACK_OFFLOAD="0x9983"
LKL_HIJACK_NET_IFTYPE=tap
LKL_HIJACK_NET_IFPARAMS=nginx-tap
LKL_HIJACK_NET_IP=10.2.0.3
LKL_HIJACK_NET_NETMASK_LEN=24
LKL_HIJACK_NET_GATEWAY=10.2.0.1
LKL_HIJACK_DEBUG=all

同时执行以下命令,创建一个新的tap,并设置iptables转发规则

1
2
3
4
5
6
7
ip tuntap add nginx-tap mode tap
ip addr add 10.2.0.1/24 dev nginx-tap
ip link set nginx-tap up

iptables -t nat -A PREROUTING -i venet0 -p tcp -m tcp --dport 80 -j DNAT --to-destination 10.2.0.3
iptables -t nat -A PREROUTING -i venet0 -p tcp -m tcp --dport 443 -j DNAT --to-destination 10.2.0.3
iptables -t nat<strong> </strong>-A POSTROUTING -o venet0 -j MASQUERADE

最后修改nginx配置文件,设置proxy_protocol,并从指定目标获取原始请求的IP,并在转发给后端时设置源IP

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
server {

    ......

    listen 443 ssl http2 proxy_protocol;

    listen [::]:443 ssl http2 proxy_protocol;

    ......

    set_real_ip_from 10.2.0.3;
    real_ip_header proxy_protocol;

    ....
    location / {
        ...
        proxy_set_header Host            $host;
        proxy_set_header X-Real-IP       $proxy_protocol_addr;
        proxy_set_header X-Forwarded-For $proxy_protocol_addr;
    }
}

最后重启haproxy与Nginx

Update 2018.11.26

关于proxy protocol可以在haproxy的文档中找到答案:proxy-protocol

关于nginx配置从proxy protocol获取源IP,也可以在文档中找到答案:Accepting the PROXY Protocol

使用lkl-bbr的基本思路是拦截系统调用,替换拥塞调度算法,理论上所有使用glibc的程序都可以被拦截,而像Go这样自行处理系统调用的就无法被正常拦截了,除非使用Cgo,除此之外:

(1)haproxy可以被正常拦截,而nginx不行,所以我们使用了haproxy作为nginx的前端

(2)haproxy未配置proxy protocol时,默认向后端发送的是绑定的内网IP(10.2.0.3),proxy protocol是haproxy定义的用于传递源IP的协议

(3)nginx支持proxy protocol,但需要手动配置,从haproxy转发的请求中解出源IP

(4)ngx_http_realip_module可以用来改变客户端地址,上面的配置中就是用它从proxy protocol中解出源IP,设置并转发给后端