Gần đây khi tôi tham gia một dự án về EC ( thương mại điện tử) tôi có gặp 1 vấn đề liên quan tới Load Balancing như sau:

  • User login vào site rồi, nhưng một lát sau lại bị logout mặc dù session đã được setting trong vòng 1 tuần.
  • Sau đó tôi kiểm tra lại session driver thì thấy  là đang lưu dạng file trên local của server => đã đổi sang lưu trên mysql or reddit nhưng đều vô dụng, tình trạng cũ vẫn tái diễn.
  • Nhờ vấn đề trên mà tôi đã tìm hiểu sâu hơn về Load Balancing một chút và đúc rút được những kinh nghiệm sẽ chia sẻ trong bài viết này.

Load Balancing là gì?

Định nghĩa: là dịch vụ có vai trò phân tải incomming request trên những server backend để tăng tải cho hệ thống. Ngoài ra còn được gọi với cái tên khác là server farm hay server pool.

Để dễ hình dung thì chúng ta hãy nhìn vào bức hình sau:

1g6xpr9NZ-wfIlH0omp3hb05ltqFAof7d

Request từ Client tới service sẽ không được gửi trực tiếp mà phải thông qua anh chàng Load Balancer, anh này sẽ dựa vào thuật toán phân tải được thiết lập để forward request tới backend server nào.

Lợi ích của sử dụng Load Balancing:

  • SCALABILITY – khả năng mở rộng: khi lượng khách hàng tăng lên thì traffic của web site cũng tăng lên , với server để nâng cấp CPU hay RAM là một vấn đề rất khó và tốn thời gian, trong khi sử dụng Load Balancer ta có thể scale theo chiều ngang là tăng số lượng server lên mà down time của hệ thống gần như là bằng 0.
  • REDUNDANCY – dư thừa: Khi sử dụng Load Balancing, nếu có 1 trong những web server tạch thì Load Balancing sẽ chuyển traffic sang những node khác còn hoạt động bình thường => đảm bảo service của chúng ta luôn khả dụng.
  • FLEXIBILITY – mềm dẻo: Khi cần main tain web server thì ta có thể tiến hành maintain trên từng node một để đảm bảo service vẫn khả dụng với người dùng.

Những vấn đề gặp phải khi sử dụng LB cho web service

Quản lý Asset

Trước hết về những static asset (css, js,...) để đảm bảo những tài nguyên này là đồng nhất thì chúng ta phải tạo những bản sao đồng nhất trên mỗi một server web backend. Ta có thể sử dụng dịch vụ CDN để cache những static asset này vừa đồng nhất mà lại tăng tốc độ truy cập cho user (Cloudflare, MaxCDN hoặc Cloudfront).

Nhưng nếu web service của bạn cho phép người dùng upload file lên thì user sẽ chỉ truy cập được file khi mà request của họ được Load Balancer trỏ vào server backend mà file được upload lên, nếu LB trỏ request vào những server còn lại thì ta sẽ nhận được error code 404 Resource not found => để giải quyết vấn đề này ta cần phải sử dụng 1 thằng storage chung cho tất cả web server: File server, Object Storage Server,...

  • Trong dự án của tôi tôi đã sử dụng Conoha (Một cloud hosting service của tập đoàn GMO ) Object Storage (thực ra là nền tảng của Open Stack) để lưu trữ file mà người dùng upload lên, bao gồm ảnh avatar, ảnh tư liệu, tài liệu doc,...
  • Ngoài ra bạn có thể sụng dụng S3 của AWS để lưu trữ file (S3 có thể tích hợp vs CDN CloudFront của Amazon luôn nên khá là tiện lợi). Dưới đây là 1 ví dụ về sử dụng S3 để lưu trữ file trên PHP.
// Send uploaded image to S3
Route::post('/upload', function()
{
    // Get Uploaded File
    $file = Input::file('file');

    // Create name for file
    $now = new DateTime;
    $hash = md5( $file->getClientOriginalName().$now->format('Y-m-d H:i:s') );
    $key = $hash.'.'.$file->getClientOriginalExtension();

    $s3 = AWS::get('s3');
    $s3->putObject(array(
        'Bucket'    => 'user_uploads_bucket',
        'Key'    => $key,
        'SourceFile'    => $file->getRealPath(),
        'ContentType'    => $file->getClientMimeType(),
    ));

    // Probably store the name of the file in a database too...

    return Redirect::to('/profile');
});

Quản lý Session

Đây chính là bài toán mà tôi gặp phải trong dự án của mình.

Vì HTTP là stateless (req sau không phụ thuộc vào kết quả req trước), vì vậy để lưu giữ dữ liệu giữa các page với nhau người ta sử dụng Session, ví dụ phổ biến nhất là token authentication được lưu vào Session để xác nhận người dùng đã đăng nhập vào hệ thống.

  • Khác với cookie được lưu trữ ở phía client, session được lưu trữ ở server
  • Session lưu theo dạng key-val và mỗi user sẽ có 1 dữ liệu riêng

Session được lưu trữ như thế nào?

  • PHP thuần mặc định sẽ lưu trữ trong file (khi xem thông tin phpinfo() ta sẽ biết được path lưu trữ session), path lưu trữ session ta có thể config được trong file php.ini
  • Vấn đề trong Load Balancing với Session được lưu trữ qua file
    • Thông tin authentication của user được lưu trong Session mà Session lại được lưu trữ trong file => dữ liệu Session là không đồng nhất giữa các server web => Đây là nguyên nhân khiến cho user đã login bị logout ra khi request của họ bị forward sang server web khác với server nhận request login của họ.
    • Giải quyết như thế nào?
      • Sử dụng datastore chung để lưu trữ session: database, cache (memcached, reddis)

Như đã nói ở trên, mặc dù đã thử đổi lưu trữ session qua database, cache nhưng user của tôi vẫn bị logout ra bất thình lình như trước. Thật là phiền toái, ngay lúc này tôi đã tìm đến một giải pháp tạm bợ là làm sao để cho những request của 1 user luôn luôn được redirect vào 1 web server mà thôi, đó là sử dụng thuật toán balancing của NGINX là ip hash, or sticky session

  • Ip hash
  • Sticky session

Sau khi áp dụng Ip hash thì vấn đề có vẻ đã được giải quyết, chỉ còn 1 cái là lúc user thay đổi mạng -> IP thay đổi thì sẽ có nguy cơ họ lại bị logout bất  thình lình. Lỗi này vẫn có thể chấp nhận được vì xác suất xảy ra tương đối nhỏ. Một thời gian sau, khi xem xét lại quá trình deploy tôi nhận thấy (service của chúng tôi sử dụng Laravel Framework) mỗi khi deploy version mới thì user lại bị logout ra, xem kỹ hơn nữa tôi thấy là mỗi lần deploy lại tạo ra 1 APP_KEY mới, đọc doc về APP_KEY thì thấy  APP_KEY là một chuỗi bất kỳ dùng để encrypt session => đây chính là của nợ đã khiến cho user (sau khi hệ thống chuyển qua lưu session trên database hay cache) vẫn bị logout khi nhảy request giữa các web server.

=> Bài học rút ra là chỉ sử dụng 1 APP_KEY cho các web server thôi.

Tổng hợp những phương pháp để Session đồng nhất khi sử dụng Load Balancing

  • Lưu Session vào cookie: thay vì lưu trên server, data của Session được lưu trữ trên cookie, như vậy ta không cần quan tâm là request được forward ra đâu nữa. Tuy nhiên phương pháp này gặp phải vấn đề là giới hạn dung lượng data khi lưu trên cookie + vấn đề bảo mật dữ liệu Session khi dữ liệu Session không được mã hóa.
  • Sử dụng sticky Sessions: (hay còn gọi là session affinity) mỗi 1 client sẽ có 1 sticky session, và giá trị của nó sẽ quyết định request của nó được forward tới server nào.
    • Config trong HA Proxy
    backend nodes
      # Other options above omitted for brevity
      cookie SRV_ID prefix
      server web01 127.0.0.1:9000 cookie check
      server web02 127.0.0.1:9001 cookie check
      server web03 127.0.0.1:9002 cookie check
    
    • Config trong NGINX upstream
    upstream app_example {
      ip_hash;                 
      server 127.0.0.1:9000;
      server 127.0.0.1:9001;
      server 127.0.0.1:9002;
    }
    
  • Central Session Storage: lưu trữ Session tập trung -> lưu Session vào database, cache thay vì lưu vào file.

Mất thông tin của Client

Khi triển khai Load Balancing, mọi request mà các web server nhận được đều là từ Load Balancer node => khi trên server web ta cần thông tin của client như là IP, hostname mà client access, schema (http/https) mà client sử dụng.

Thật may là hầu như mọi Load Balancing đều hỗ trợ add những thông tin trên vào header để các web server có thể lấy được thông tin của Client. Ví dụ các header

  • X-Forwarded-For: có giá trị là IP của Client, chỉ ra request là thông qua proxy or load balancing
  • X-Forwarded-Host: hostname mà Client truy cập vào ( là hostname của Load Balancer)
  • X-Forwarded-Proto / X-Forwarded-Scheme: HTTP/HTTPS
  • X-Forwarded-Port: port mà Client truy cập

Khi có những thông tin trên trong header của request, web server có thể lấy được những thông tin cần thiết để audit request của client.

SSL Traffic

SSL Termination

  • Khi sử dụng SSL, thông thường Load Balancer sẽ decrypt các https request trước khi forward nó cho các web service. Còn gọi là SSL Termination (chấm dứt SSL) . Kỹ thuật này giúp cho web server giảm tải CPU vì ko phải decrypt SSL package.
  • Kỹ thuật này có 1 vấn đề là giữa Load Balancer và web server dữ liệu ko được mã hóa, dễ trở thành nạn nhân của  man-in-the-middle attacks (ngồi giữa mót của). Tuy nhiên đa phần Load Balancer và các web server là ở trên cùng cụm data center nên ta có thể hạn chế hoàn toàn khả năng bị tấn công bơi man-in-the-middle attacks.
  • Config trong HA Proxy
frontend localhost
    bind *:80
    bind *:443 ssl crt /etc/ssl/xip.io/xip.io.pem
    mode http
    default_backend nodes

backend nodes
    mode http
    balance roundrobin
    option forwardfor
    option httpchk HEAD / HTTP/1.1\r\nHost:localhost
    server web01 172.17.0.3:9000 check
    server web02 172.17.0.3:9001 check
    server web03 172.17.0.3:9002 check
    http-request set-header X-Forwarded-Port %[dst_port]
    http-request add-header X-Forwarded-Proto https if { ssl_fc }

SSL Pass-Through

  • Load Balancer lúc này ko decrypt các request nữa mà pass thẳng luôn cho Web server
  • Kỹ thuật này sẽ khiến cho CPU của các web server phải chịu tải nhiều hơn vì phải decypt các request. Tuy nhiên nó cũng có hữu ích là giúp phòng chống DDos hiệu quả hơn (thiết lập chống DDos trên Load Balancer khi Load Balancer ko cần phải decrypt request sẽ hiệu quả hơn).
  • Kỹ thuật này chỉ sử dụng được khi công cụ Load Balancing hỗ trợ xủ lý với package ở tần TCP (ở tầng HTTP ta sẽ ko làm được).
  • Config cho HAProxy
frontend localhost
    bind *:80
    bind *:443
    option tcplog
    mode tcp
    default_backend nodes

backend nodes
    mode tcp
    balance roundrobin
    option ssl-hello-chk
    server web01 172.17.0.3:443 check
    server web02 172.17.0.4:443 check

Logging

Khi có nhiều web server , mỗi web server lại có log riêng => ta phải tổng hợp lại để phân tích chung.

Ta có thể sử dụng các tool Centralize Logging như là

  • Logstash
  • Flume
  • Fluend
  • Graylog2
  • Splunk
  • Syslog-ng
  • Rsyslog

Hoặc các SaaS logger (log sẽ được gửi lên cloud của bên cung cấp và hỗ trợ quản lý, thao tác với log trên đó)

  • Loggly
  • Splunk Storm
  • Paper Trail
  • Bugsnag

Kết luận

Ngày nay kiến trúc của service đã nhảy lên một level mới là migrate lên cloud so với trước đây, vì vậy đi kèm với nó là những vấn đề hóc búa hơn về mặt hệ thống mà chúng ta phải đối mặt, Load Balancing cũng là một ví dụ.

Tôi hy vọng bài viết của tôi phần nào cung cấp được một kiến thức đầy đủ để giúp các bạn tự tin xử lý những vấn đề khi ta làm việc với Load Balancing.

Tham khảo