HAProxy là một phần mềm load balancing thông dụng được phát triển trên ngôn ngữ C, có tốc độ xử lý và độ ổn định rất cao, các lý do thì hầu hết đã được đề cập ở phần performance ở trang chủ. Tài liệu sẽ giải thích rõ hơn (một tý) kỹ thuật đã sử dụng trong từng dòng đó.
Các kỹ thuật này không chỉ riêng riêng HAProxy sử dụng mà các ứng dụng web service khác như nginx, redis cũng sử dụng rất nhiều để tối ưu hiệu năng.

Trong bài viết sẽ sử dụng một số ví dụ trên một mô hình làm việc cơ bản của HAP như sau:
Xét một cấu hình proxy như sau: ClientProxyServer
Client kết nối tới Proxy bằng 1 kết nối in_socket.
Proxy sẽ tạo một kết nối mới tương ứng kết nối trên tới đầu Server, ta gọi đó là out_socket.

1. a single-process, event-driven model considerably reduces the cost of context switch and the memory usage
Các instance khi chạy của HAProxy là độc lập hoàn toàn về tài nguyên, mỗi process cũng chỉ sử dụng 1 thread duy nhất. Điều này giúp loại bỏ vấn đề wait giữa các thread, process để đồng bộ khi truy xuất dữ liệu (như việc phải sử dụng mutex). Điều này làm giảm việc tương tác context switch thread, proccess.
Các cơ chế select, poll, epoll (default sử dụng epoll), là dạng event-driven, giúp tối ưu việc nhận dạng IO vào ra ở các đầu (ở đây là socket).

2. O(1) event checker
Sử dụng event-driven như trên ý 1, nên độ phức tạp O(1) khi nhận dạng sự kiện của socket.
Chúng ta sẽ tìm hiểu kỹ hơn về event-driven trong một bài viết khác.

3. Delayed updates to the event checker + number of expensive system calls
Khi cần set event cho một fd, thay vì gọi ngay hàm set, HAProxy đưa vào một mảng tạm. Ngay trước khi khối poller được gọi thì thực hiện việc lấy các phần tử fd này ra và set lại.
Việc này giúp hạn chế được các thao tác add, del event trùng lặp vô nghĩa trong đoạn thời gian nghỉ giữa các lượt poller.

Ví dụ:
Poller: Lần 1, gọi vào khối code thực thi bên dưới

set_read(fd_01) set_unread(fd_01) set_read(fd_01) 

Poller: Lần 2.

-> Như vậy, việc set event thực hiện nhiều lần, như giá trị ghi nhận thực từ poll lần 1 tới poll lần 2 chỉ là 1 giá trị: set_read(fd_01)

4. Single-buffering
Sử dụng một vùng buffer duy nhất chung cho nhiều thao tác, hạn chế tối đa sử dụng thêm vùng buffer khác để xử lý vì sẽ gây ra thêm thao tác đọc, ghi bộ nhớ.
Ví dụ: Khi làm việc dữ liệu đọc được từ in_socket sẽ được lưu vào một bufferX, cũng dữ liệu tạibufferX sẽ được ghi vào out_socket, toàn bộ xử lý về gói tin cũng sẽ nằm ngay trên bufferX này mà không copy hay di chuyển đi đâu nữa cả.
Thông thường yếu tố thắt cổ chai tài nguyên xảy ra với IO hoặc CPU, nhưng nếu tốc độ truyền tải quá nhanh thì băng thông của memory cũng trở thành một yếu tố gây thắt cổ chai.

Một kỹ thuật được hiện thực cụ thể trong HAProxy cũng cho vấn đề này là memory pool, kỹ thuật này còn nhiều tác dụng khác như giúp khối bộ nhớ nằm liền mạch tránh phân mảnh về bộ nhớ, tự quản lý cấp phát lại bộ nhớ (thay vì liên tục gọi malloc).

5. Zero-copy forwarding
Sử dụng hàm splice, hàm này cho phép chép dữ liệu giữa 2 fd, kernel sẽ làm chuyện này nên tốc độ sẽ rất nhanh và không cần buffer tạm, đây là một cơ chế hỗ trợ trong kernel của Linux.
Trong HAProxy sử dụng cụ thể điều này như sau:
Khi in_socket có dữ liệu tới, thay vì dùng các API đọc dữ liệu như recv, rồi dùng API như send để gửi dữ liệu đi, chuyện này gây tốn nhiều tài nguyên. Đơn giản hơn là sử dụng một cái gì đó chuyển thẳng phần data đang cần đọc trên in_socket và đẩy nó qua out_socket, kỹ thuật này gọi là zero-copy.

Tuy nhiên zero-copy không phải một công thức thần kỳ trong mọi trường hợp. Zero-copy sử dụng đặc biệt hiệu quả với proxy không quan tâm tới dữ liệu là gì, nhưng với proxy cần xử lý thêm về gói tin (như các xử lý về http header), thì sẽ không sử dụng được, do dữ liệu nó chuyển thẳng giữa các fd dưới kernel chứ không được đọc lên trên để xử lý. Vì yếu tố này nên HAProxy có một cờ để bật tắt API zero-copy.

6. Work factoring
Nhiều process lắng nghe và có thể accept trên cùng 1 port, các fd sẽ được phân phối cho các process, theo nguyên tắc process nào nhanh tay lẹ mắt hơn sẽ được ưu tiên. Việc này giúp tài hạn chế được việc xử lý quá nặng tải ở 1 process nào đó trong khi cái khác thì rỗi.

7. CPU-affinity
Các process sẽ được chia đều trên các CPU core, thông thường số process cấu hình nên nhỏ hơn hoặc bằng số core, tất nhiên là nếu bạn cấu hình số process vượt quá số CPU core thì đó là lỗi của bạn chứ không phải lỗi của HAProxy.

8. Tree-based storage
Sử dụng Elastic Binary tree, là một loại cây có thứ tự do tác giả của HAProxy tự phát triển. Với độ phức tạp: O(logN)
Giúp tối ưu thời gian trong các thao tác:

  • Nhận dạng connection timeout
  • Nhận dạng task đã tới thời gian chưa
  • Round-robin server chọn server có lượng connection thấp

9. Optimized timer queue
Timer queue được dùng trên Elastic Binary tree, giúp các thao tác liên quan tới time queue như timeout socket … diễn ra với độ phức tạp thấp. Tác giả haproxy rất tự hào về cái cây này, nếu tò mò bạn có thể đọc tại đây.

Comments

comments