Cấu hình Server cho Site WordPress có lượng truy cập lớn

by Tu

on Sep 21/16 at 12:53

Bài viết này chủ yếu hướng đến các site dạng tin tức, nội dung trong một URL hầu như không (hoặc ít) thay đổi. Các site dạng forum vẫn có thể áp dụng phương pháp này bằng cách giảm thời gian live time trên Redis. Tuy nhiên mình mới chỉ áp dụng trên site dạng tin tức nên không chắc chắn về hiệu năng đối với các site dạng forum

Có khi nào các bạn gặp tình trạng Server WP của mình đang yên đang lành thì bị sập mà k rõ nguyên nhân?, bạn đã cố gắng chỉnh sửa, cấu hình nginx & php, thậm chí cài đặt cache các kiểu nhưng chẳng khá khẩm hơn tí nào. Bạn restart server thì một lúc sau đâu lại vào đấy. Cảm giác bất lực mà chẳng thể làm gì hơn ?

Cá nhân mình đã có lúc như bạn, sau một thời gian tìm hiểu & thử nghiệm, câu trả lời cho bài toán này chỉ có 1 từ Cache. Tuy nhiên, cách mà bạn áp dụng lại quyết định tình trạng server của bạn:

Có 2 phương pháp để Cache :

  • Cache in Local Disk
  • Cache in Server Cache (Memcache/Redis)

Mỗi loại Cache trên đều có ưu nhược điểm riêng. Chúng ta sẽ cùng phân tích từng loại cache & nguyên nhân có thể gây ra tình trạng sập server nhé

Cache using Local Disk

  • Flow: Khi user request URL của Page trên WP lần đầu tiên, Hệ thống sẽ lưu lại đoạn mã HTML (cache) lên Local Disk sau khi WP render thành công trước khi trả về cho user. Sau đó, khi user request lại URL này, server sẽ kiểm tra xem có tồn tại cache của URL đó hay không, nếu có thì sẽ trả luôn đoạn HTML đó về cho người dùng & không cần tính toán gì cả. (Nâng cao hơn thì set thêm expired cho cache)

  • WPPLugin: WP Super Cache

    • Ưu điểm:
      • Không cần tới Server Cache của bên thứ 3
      • Dễ thao tác, dễ cấu hình
      • Không lo thiếu dung lượng bộ nhớ (thường bộ nhớ Local Disk rất lớn, hơn nữa, cache sẽ tự động xóa khi expired)
    • Nhược điểm:
      • Vì phải lưu file & đọc file liên tục nên tốc độ thực thi phụ thuộc vào I/O per second của Local Disk
      • Khi có lượng truy cập lớn, server không sập vì thiếu RAM hay CPU mà sập vì I/O per second không đủ.
  • Cache using Server Cache: Tương tự như flow trên Local Disk, chỉ khác là cache sẽ được lưu trên Server Cache thay vì lưu trên Local Disk: W3 Total Cache

    • Ưu điểm:

      • Tốc độ thực thi cực kỳ nhanh (do lưu trên RAM)
      • Giảm tải cho Server vấn đề đọc & ghi cache (do đã có Server Cache lo vấn đề này)
    • Nhược điểm:
      • Cần tới Server Cache của bên thứ 3
      • Khó thao tác, cấu hình hơn Local Disk
      • Cần kiểm soát vấn đề lưu & xóa cache, tránh full bộ nhớ
      • Khi có lượng truy cập lớn, server không sập vì thiếu I/O per second mà do thiếu RAM, CPU. Các tiến trình của PHP-FPM được tạo ra để gọi tới server cache sẽ không kịp release memory. Do đó, khi có hàng loạt connection tới cùng lúc, server sẽ không cấp đủ memory cho PHP-FPM. Việc thực thi nhiều tiến trình PHP-FPM cùng lúc cũng tăng CPU usage, góp phần dẫn đến sập server.

Cả hai phương pháp Cache đều có thể dẫn đến sập server. Vậy làm sao để bảo đảm server không bị sậpkhông cần nâng cấp thêm server hiện tại ?

Cache using Server Cache, communicate without php-fpm

Như mình đã đề cập ở trên, nhược điểm của phương pháp Cache using Server Cache đó là khi có lượng truy cập lớn đồng thời, các tiến trình của PHP-FPM được tạo ra để kết nối tới server cache sẽ tiêu tốn hết RAM của server, tăng cpu usage dẫn tới server bị sập.

Để khắc phục vấn đề này, chúng ta sẽ cấu hình để Nginx communicate trực tiếp với server cache thông qua Lua script. Bằng phương pháp này, chúng ta có thể bỏ qua bước thực thi PHP-FPM.

Trong bài viết này, mình sẽ hướng dẫn các bạn cài đặt & cấu hình nginx & lua script để communicate tới server cache (Redis), sử dụng OS Linux - Ubuntu.

Summary

  • Ưu điểm:

    • Không cần can thiệp vào source code của WordPress, tách riêng được phần code & phần hệ thống
    • Vì tốc độ thực thi của Nginx & Lua script là cực kỳ nhanh, tiêu tốn cực kỳ ít resource (Ram/Memory) nên có thể handle được lượng truy cập lớn & đồng thời
  • Nhược điểm:
    • Chỉ thực hiện được trên OS Linux
    • Cần truy cập được SSH tới server
    • Cần quyền Root để cài đặt & cấu hình
    • Cần một chút hiểu biết về cách cấu hình trên Linux

Prepare

Để Nginx kết nối tới Redis, chúng ta cần recompile nginx với Redis2 Module, Hash MD5 Module. Đối với các bạn đã cài đặt Nginx/Apache từ trước, các bạn cần unistall nó trước khi recompile

1. Compiler environment: Dependencies to compile

sudo apt-get install build-essential zlib1g-dev libpcre3 libpcre3-dev unzip

2. Page Speed Module

Module này có thì càng tốt, không có cũng không sao. Chi tiết về module này, các bạn xem tại link Google Pagespeed Module

cd
NPS_VERSION=1.11.33.2
wget https://github.com/pagespeed/ngx_pagespeed/archive/release-${NPS_VERSION}-beta.zip -O release-${NPS_VERSION}-beta.zip
unzip release-${NPS_VERSION}-beta.zip
cd ngx_pagespeed-release-${NPS_VERSION}-beta/
wget https://dl.google.com/dl/page-speed/psol/${NPS_VERSION}.tar.gz
tar -xzvf ${NPS_VERSION}.tar.gz  # extracts to psol/

3. Redis2 Nginx Module: Module dùng để connect từ Nginx tới Redis

cd
REDIS2_VERSION=0.13
wget https://codeload.github.com/openresty/redis2-nginx-module/tar.gz/v${REDIS2_VERSION} -O redis2-nginx-module-${REDIS2_VERSION}.tar.gz
tar -xzvf redis2-nginx-module-${REDIS2_VERSION}.tar.gz

4. Lua Jit: Module LUA - Runtime Environment for LUA Language

cd
LUAJIT_VERSION=2.0.4
wget http://luajit.org/download/LuaJIT-${LUAJIT_VERSION}.tar.gz
tar -xzvf LuaJIT-${LUAJIT_VERSION}.tar.gz
cd LuaJIT-${LUAJIT_VERSION}
make && sudo make install
export LUAJIT_LIB=/usr/local/lib
export LUAJIT_INC=/usr/local/include/luajit-2.0

5. Lua Nginx Module: Module giúp Nginx communicate với Lua

cd
LUA_NGINX_VERSION=0.10.5
wget https://github.com/openresty/lua-nginx-module/archive/v${LUA_NGINX_VERSION}.tar.gz -O lua-nginx-module-${LUA_NGINX_VERSION}.tar.gz
tar -xzvf lua-nginx-module-${LUA_NGINX_VERSION}.tar.gz

6. Nginx Developer Kit Module: Module SDK for Nginx

cd
NDK_VERSION=0.3.0
wget https://codeload.github.com/simpl/ngx_devel_kit/tar.gz/v${NDK_VERSION} -O ngx_devel_kit-${NDK_VERSION}.tar.gz
tar -xzvf ngx_devel_kit-${NDK_VERSION}.tar.gz

7. MD5 Nginx Module

Module giúp Nginx tạo chuỗi Hash từ một chuỗi bất kỳ. Dùng để tạo Hash String cho URL

cd
sudo apt-get install libssl-dev
MD5_NGINX_VERSION=0.2.1
wget https://github.com/simpl/ngx_http_set_hash/archive/${MD5_NGINX_VERSION}.tar.gz -O ngx_http_set_hash-${MD5_NGINX_VERSION}.tar.gz
tar -xzvf ngx_http_set_hash-${MD5_NGINX_VERSION}.tar.gz

8. Nginx Echo Module: Module giúp các bạn có thể set experied time cho từng cache

cd
NGX_ECHO_VERSION=0.59
wget https://github.com/openresty/echo-nginx-module/archive/v${NGX_ECHO_VERSION}.tar.gz -O echo-nginx-module-${NGX_ECHO_VERSION}.tar.gz
tar -xzvf echo-nginx-module-${NGX_ECHO_VERSION}.tar.gz

9. Nginx: Không cần nói thêm gì về Nginx nhé

cd
# check http://nginx.org/en/download.html for the latest version
NGINX_VERSION=1.10.1
wget http://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz
tar -xvzf nginx-${NGINX_VERSION}.tar.gz

Compile: Prepare thì nhiều vậy nhưng compile chỉ cần 3 dòng thôi

cd nginx-${NGINX_VERSION}/
./configure --user=nginx --group=nginx --sbin-path=/usr/sbin/nginx --pid-path=/run/nginx.pid --with-ld-opt="-Wl,-rpath,/usr/local/lib"  --add-module=$HOME/ngx_pagespeed-release-${NPS_VERSION}-beta ${PS_NGX_EXTRA_FLAGS} --add-module=$HOME/redis2-nginx-module-${REDIS2_VERSION} --add-module=$HOME/ngx_devel_kit-${NDK_VERSION} --add-module=$HOME/ngx_http_set_hash-${MD5_NGINX_VERSION} --add-module=$HOME/echo-nginx-module-${NGX_ECHO_VERSION} --add-module=$HOME/lua-nginx-module-${LUA_NGINX_VERSION} --conf-path=/etc/nginx/nginx.conf --pid-path=/run/nginx.pid --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log
make && sudo make install

Configure

Nginx Daemon

Điều đầu tiên bạn cần quan tâm sau khi build nginx từ source chắc chắn là tập tin daemon để dễ dàng start/stop nginx đúng không. Đơn giản là bạn chỉ cần tạo tập tin /etc/init.d/nginx với nội dung như file đính kèm

Sau đó chmod nó

sudo chmod a+rx /etc/init.d/nginx

Giờ bạn có thể start/stop/restart nginx server của mình bằng lệnh:

sudo service nginx restart

Nginx - Redis

Mở tập tin cấu hình nginx server của bạn (thường nằm ở /etc/nginx/sites-available/default)

Note: Mình sẽ bỏ qua các vấn đề về thiết lập cấu hình cơ bản cho Nginx như setup server block, location, php, etc. Chi tiết các bạn có thể tham khảo tại How to install Nginx, Mysql, PHP on Ubuntu

Đầu tiên, chúng ta cần tạo một chuỗi Hash tương ứng với mỗi URL của bài viết. Trong server block, các bạn thêm đoạn code sau:

set $current_redis <redis_url>:<port>
set $current_uri $scheme://$http_host$request_uri;
set_md5 $ukey $current_uri;

Các bạn thay đổi tùy theo server redis của các bạn.

Đối với mỗi URL hiện tại $current_uri, chúng ta sẽ tạo một chuỗi MD5 tương ứng với URL đó & lưu vào biến $ukey. Chúng ta cũng cần kiểm tra chuỗi URL đó, nếu nó là file thì trả về file luôn. Nếu URL đó trỏ tới wp-admin thì forward nó tới location ~ \.php$ Còn nếu không thì forward nó tới location redis

location / {
        try_files $uri $uri/ /redis;
}

location = / {
        try_files $uri /redis;
}

location redis

Thực hiện các tính toán logic như kiểm tra cache tồn tại hay không. Nếu có thì trả về cache, nếu không thì gọi tới php xử lý URL & cache output trước khi trả về cho người dùng.

location = /redis {
    default_type text/html;
    add_header 'X-URI' "$current_uri";
    content_by_lua_block {
        local res = ngx.location.capture("/redis_check")
        if string.match(res.body, "1") then
            local data = ngx.location.capture("/redis_get")
            local inx = string.find(data.body, "<")
            if inx ~= nil then
                local html = string.sub(data.body, inx)
                ngx.print(html)
            else
                ngx.print(data.body)
            end
            ngx.print("<!--Service by nginx w redis-->")
        else
            local ori = ngx.location.capture("/original")
            local res
            if string.find(ngx.var.request_uri, "feed") then
                res = ngx.location.capture("/redis_set_date",
                    {
                        method = ngx.HTTP_PUT,
                        body = ori.body
                    }
                )
            else
                res = ngx.location.capture("/redis_set",
                    {
                        method = ngx.HTTP_PUT,
                        body = ori.body
                    }
                )
            end
            ngx.print(ori.body)
            if string.match(res.body, "OK") then
                ngx.print("<!--Original-->")
            else
                ngx.location.capture("/redis_flush")
                ngx.print("<!--Flushed-->")
            end
        end
    }
}

Như các bạn thấy trong đoạn script ở trên, chúng ta có 1 số location:

location redis_check

Kiểm tra xem URL hiện tại đã tồn tại trong redis hay chưa.

location = /redis_check {
        internal;
        redis2_query exists $ukey;
        redis2_pass $current_redis;
}

location redis_get

Lấy nội dung cache từ server redis

location = /redis_get {
        internal;
        redis2_query get $ukey;
        redis2_pass $current_redis;
}

location original

Gọi PHP thực thi URL & trả về code HTML

location = /original {
        try_files $uri /index.php?$args;
}

location redis_set_date

Custom behavior của Redis đối với mỗi dạng của URL. Như location redis bên trên, mình cấu hình cho các URL có chứa từ khóa feed thì sẽ có thời gian expired ngắn hơn so với URL bình thường

location = /redis_set_date {
        internal;
        redis2_query set $ukey $echo_request_body "EX" 2700;
        redis2_pass $current_redis;
}

location redis_set

Set cache cho một URL

location = /redis_set {
        internal;
        redis2_query set $ukey $echo_request_body "EX" 604800;
        redis2_pass $current_redis;
}

Sau khi đã cấu hình Nginx xong, các bạn restart lại Nginx Daemon

sudo service nginx restart

Tới đây, site của các bạn đã có thể hoạt động bình thường với sự hỗ trợ từ nginx & redis cache. Tuy nhiên, các bạn cũng cần quản lý cache trên redis của mình. Ví dụ như nếu bạn viết một bài mới và muốn nó nằm trên trang chủ. Nhưng trang chủ đã có cache & cache chưa hết timeout thì phải làm thế nào ?

Mình có viết sẵn cho các bạn một plugin cho phép các bạn quản lý các vấn đề như vậy. Tuy nhiên mới chỉ ở mức cơ bản. Các bạn có thể xem hướng dẫn & tải về tại Github Predis Cache

Lời kết

Tất cả những hướng dẫn mình viết trên đây đều dựa trên kinh nghiệm thực tiễn của mình. Kết quả khi mình áp dụng trên site của cty (xin lỗi mình không được phép dẫn link) thì không còn thấy tình trạng sập server & tốc độ thực thi cực kỳ nhanh. Do mình là dân lập trình, không phải dân hệ thống nên có thể có nhiều vấn đề mình chưa lường hết. Nếu các bạn gặp vấn đề gì khó khăn trong quá trình cài đặt hay phát hiện vấn đề gì chưa ổn thì vui lòng comment bên dưới hoặc gửi mail về địa chỉ glmanhtu@gmail.com