1. Thế nào Load Balancing

Load Balancing hay còn gọi là Cân bằng tải ?? một kỹ thuật thường được sử dụng để tối ưu hóa việc sử dụng tài nguyên, băng thông, giảm độ trễ, và tăng cường khả năng chịu lỗi.

Khi chúng ta có nhiều hơn một web server, cùng với đó là sự gia tăng lưu lượng truy cập thì việc bổ sung thêm một máy chủ để phân phối lưu lượng này một cách hợp lý là cần thiết. Máy chủ được bổ sung này được gọi là Load balancer

alt text

2. Sử dụng NGINX làm load balancer

Trong bài viết này mình sẽ sử dụng 3 server đều được cài đặt nginx, với vai trò như sau:

  • Master: Đóng vai trò là Load balancer
  • Backend1: Webserver 1
  • Backend2: Webserver 2

Cả 2 webserver 1 & 2 đều có chứa source code của một ứng dụng php và kết nối tới chung mộtMariaDB server.

Công việc của chúng ta là cấu hình sao cho khi người dùng truy cập vào vhost thì Master server sẽ điều hướng tới một trong hai Backend server, đồng thời không để mất session người dùng.

2.1. Cấu hình Upstream trên Master

Để bắt đầu sử dụng NGINX với một nhóm các máy chủ web, đầu tiên, bạn cần khai báo upstreamgroup. Directive này được đặt trong http block.

http { upstream backend { server backend1; server backend2; } } 

Ở đây backend1backend2 chính là server name của 2 máy chủ web, ta có thể thay bằng địa chỉ IP tương ứng.

Để truyền các request từ người dùng vào một group các server, tên của group được truyền vào với directive proxy_pass (hoặc fastcgi_pass, memcached_pass, uwsgi_pass, scgi_pass tùy thuộc vào giao thức). Trong bài viết này, virtual server chạy trên NGINX sẽ truyền tất cả các request tớibackend upstream server:

server { location / { proxy_pass http://backend; } } 

Kết hợp lại ta có cấu hình sau cho Master server:

http { upstream backend { server backend1; server backend2; } server { location / { proxy_pass http://backend; } } } 

2.2. Lựa chọn thuật toán cân bằng tải

Có rất nhiều thuật toán cho mọi người lựa chọn như round-robin, least_conn, least_time, ip_hash, …

  • Trong bài viết này mình lựa chọn round-robin: Các request lần lượt được đẩy về 2 server backend1 và backend2 theo tỉ lệ dựa trên server weights, ở đây là 1:1:
upstream backend { server backend1; server backend2; } 

2.3. Bảo toàn session người dùng

Hãy thử tưởng tượng bạn có một ứng dụng yêu cầu đăng nhập, nếu khi đăng nhập, session lưu trênBackend 1, sau một hồi request lại được chuyển tới Backend 2, trạng thái đăng nhập bị mất, hẳn là người dùng sẽ vô cùng nản. :rage:

Để giải quyết vấn đề này, chúng ta có thể lưu session vào memcached hoặc redis, hoặc nhanh gọn hơn NGINX có cung cấp sticky directive, giúp NGINX tracks user sessions và đưa họ tới đúng upstream server.

Tham khảo: https://www.nginx.com/products/session-persistence/

Tuy nhiên, vấn đề ở chỗ NGINX chỉ cung cấp giải pháp này cho phiên bản thương mại: NGINX Plus mà chúng ta buộc phải bỏ tiền ra mua. :trollface:

Theo một hướng khác, tại sao ta ko dùng ip_hash làm phương thức cân bằng tải ??

  • Hash được sinh từ 3 chỉ số đầu của một IP, do đó tất cả IP trong cùng C-class network sẽ đc điều hướng tới cùng một backend.
  • Tất cả user phía sau một NAT sẽ truy cập vào cùng một backend.
  • Nếu ta thêm mới một backend, toàn bộ hash sẽ thay đổi, đương nhiên session sẽ mất.

Sau khi tham khảo các bài viết trên mạng thì mình tìm được hướng giải quyết như sau:

upstream backend { server backend1; server backend2; } map $cookie_backend $sticky_backend { backend1 backend1; backend2 backend2; default backend; } server { listen 80; server_name localhost; location / { set $target http://$sticky_backend; proxy_pass $target; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; } } 
  • Step 1: Khi user lần đầu tiên truy cập vào Master server, lúc đó sẽ ko có backend cookie nào được đưa ra, và dĩ nhiên $sticky_backend NGINX variable sẽ được chuyển hướng tới upstreamgroup. Trong trường hợp này , request sẽ được chuyển tới Backend 1 hoặc Backend 2 theo phương thức round robin.
  • Step 2: Trên các webserver Backend 1Backend 2, ta cấu hình ghi các cookie tương ứng với mỗi request đến:
server { listen 80 default_server; ... location ~ ^/.+.php(/|$) { add_header Set-Cookie "backend=backend1;Max-Age=3600"; ... } } server { listen 80 default_server; ... location ~ ^/.+.php(/|$) { add_header Set-Cookie "backend=backend2;Max-Age=3600"; ... } } 

Dễ thấy nếu request được pass vào backend nào, thì trên client của user sẽ ghi một cookie có name=backend & value=backend1 or backend2 tương ứng.

  • Step 3: Mỗi khi user request lại tới Master, NGINX sẽ thực hiện map $cookie_backend với $sticky_backend tương ứng và chuyển hướng người dùng vào server đó qua proxy_pass.

Không biết cách này có tốt không, nhưng ở mức độ demo thì vẫn tạm ổn. Nếu chẳng may server tương ứng với cookie lăn ra chế thì vẫn chưa có hướng giải quyết 🙁

Hiện tại mình đang set cookie valid trong khoảng thời gian 1h. Nếu qua 1h thì cookie sẽ hết hạn và người dùng có thể bị chuyển qua server khác :joy:

3. Thực hành

Làm thế nào để kiếm được 3 con server để triển khai thử bây giờ :tired_face: Đừng lo, Docker sẽ giúp bạn tạo ra hàng chục, hàng trăm server trong 1 nốt nhạc :joy:

Chuẩn bị:

Command:

$ git clone git@github.com:euclid1990/nginx-load-balancing.git $ cd nginx-load-balancing $ docker-compose up 

Demo này mình chạy trên Ubuntu host, nếu chạy trên Mac hoặc Windows có thể phải sửa lại filedocker-compose.yml cho phù hợp. Khi làm demo này, phần mình cảm thấy rắc rối nhất là generate ra file config cho Master server, vì nếu không khai báo IP của container Backend 1 & Backend 2 thì NGINX không tự động resolve đc host name theo tên service, trong khi IP của 2 container này không cố định (Để hiểu thêm về composer file, vui lòng tham khảo https://docs.docker.com/compose/compose-file/) :

alt text

Sau khi quá trình build và run container hoàn tất. Ta sẽ có 3 cụm máy chủ có thể access theo địa chỉ sau:

Ở đây source code web có được chỉnh sửa một chút, để ta dễ thấy việc chuyển hướng tới các máy chủ khác nhau khi truy cập qua Load balancer *Master*

Trên các browser truy cập vào địa chỉ của Master:

alt text

alt text

Dễ thấy Master đã chuyển hướng ta tới 2 server khác nhau, trên các server Backend lần lượt sinh các session _token khác nhau.
Đến đây, ta thử submit dữ liệu lên Database coi sao :heart_eyes:

alt text

alt text


Trên mỗi Form submit đều có csrf _token, và source code php mình có validate lại trường này có khớp với session _token trên server hay không. Nhằm kiểm chứng việc server vẫn giữ lại session cho người dùng.

Với mỗi request tới Backend, chúng ta đều ghi cookie cho Backend tương ứng để chuyển hướng đúng người dùng về server đầu tiên họ vào, tránh việc bị mất session khi truy cập.

alt text

Như vậy mình đã thử và thành công trong phạm vi hiểu biết hạn hẹp của cá nhân :)) nếu đọc tới đây hẳn là bạn đã lãng phí 5 phút của cuộc đời :trollface:

4. Tham khảo

Comments

comments