Bài này hoặc loạt bài này (nếu mình có thời gian) mình thực hiện bằng cách vừa học vừa viết lại, nguồn từ internet, chủ yếu mình lược dịch lại từ các trang web nước ngoài. Mục đích vừa để chia sẻ vừa để lại lưu trữ (sau này mình quên có thể vào để xem lại).
Xem thêm:
Hướng dẫn Nginx – Phần 1: Các khái niệm cơ bản
Tiếp tục phần 2:
tcp_nodelay
, tcp_nopush
, and sendfile
tcp_nodelay
Trong những ngày đầu của TCP, các kỹ sư phải đối mặt với nguy cơ sụp đổ do tắc ngẽn. Có một vài giải pháp nổi lên để ngăn chặn điều này, và một trong số đó là một thuật toán được John Nagle đề xuất.
Thuật toán của Nagle nhằm tránh bị tắt nghẽn bởi một số lượng lớn các gói nhỏ. Nó không can thiệp vào các gói TCP kích thước đầy đủ (Maximum Segment Size, hoặc MSS trong ngắn hạn), chỉ can thiệp vào các gói có kích thước nhỏ hơn MSS. Những gói đó sẽ được truyền chỉ khi người nhận gửi thành công tất cả các xác nhận của các gói trước đó (ACKs). Và trong thời gian chờ đợi, người gửi có thể lưu nhiều dữ liệu đệm hơn (buffer more data).
1 2 3 4 5 6 7 |
if package.size >= MSS.size send(package) elsif acks.all_received? send(package) else # accumulate data end |
Trong thời gian đó, một đề xuất khác xuất hiện: Delayed ACK.
Trong giao tiếp TCP, chúng ta gửi dữ liệu và nhận được các xác nhận (acknowledgements : ACKs) cho chúng tác biết rằng những dữ liệu đó đã được gửi thành công.
Delayed ACK cố gắng giải quyết vấn đề tắt ngẽn bởi một số lượng lớn các gói ACK. Để giảm thiểu nó, người nhận sẽ đợi một số dữ liệu sẽ được gửi tiếp để cộng chung các ACK với các dữ liệu đó. Nếu không có dữ liệu được gửi lại, chúng ta phải gửi các ACK ít nhất 2*MSS, hoặc từ 200 dến 500 ms (trong trường hợp chúng ta không còn nhận gói)
1 2 3 4 5 6 7 |
if packages.any? send elsif last_ack_send_more_than_2MSS_ago? || 200_ms_timer.finished? send else # wait end |
- “Congestion window” (cửa sổ nghẽn) ban đầu bằng 2. Congestion window là một phần của cơ chế TCP khác gọi là Slow-Start. Các chi tiết khác không quan trong,bầy giờ chỉ cần ghi nhớ rằng nó hạn chế bao nhiêu gói có thể được gửi cùng một lúc. Trong round-trip (nôm na là trọn một vòng gửi và nhận dữ liệu) đầu tiên, chúng ta được phép gửi 2 gói MSS; trong round-trip thứ hai, 4 MSS; thứ ba, 8 MSS, v.v.
- 4 gói đệm chờ để được gửi: A, B, C, D.
- A, B, C là các gói MSS
- D là một gói nhỏ.
Kịch bản:
- Do “Congestion window” (cửa sổ nghẽn) ban đầu bằng 2, người gửi được phép gửi 2 gói: A và B.
- Người nhận gửi ACK khi nhận cả 2 gói.
- Người gửi truyền gói C. Tuy nhiên, Nagle chặn chúng ta gửi gói D (gói quá nhỏ, vì vậy chúng ta cần phải chờ ACK từ C)
- Về phía người nhận, “Delayed ACK” ngăn cản anh ta gửi ACK (được gửi mỗi 2 gói hoặc mỗi 200 ms)
- Sau 200ms, người nhận gửi ACK cho gói C.
- Người gửi nhận ACK và gửi gói D.
1 |
tcp_nodelay on; # Bật TCP_NODELAY , sử dụng trên những kết nối keepalive |
Ok, hãy tận hưởng lợi ích từ 200ms.
Nếu bạn giỏi tiếng anh và muốn tìm hiều thêm về phần này thì tải file này về xem: Rethinking the TCP Nagle Algorithm
sendfile
Thông thường, khi một file cần được gửi, bắt buộc phải có các bước sau đây:
malloc(3)
: Cấp phát bộ đệm cục bộ (local buffer) để lưu trữ dữ liệu đối tượng (object data)read(2)
: Lấy và sao chép đối tượng vào bộ đệm cục bộ (local buffer) đã được cấp phát.write(2)
: Sao chép đối tượng từ bộ đệm cục (local buffer) bộ vào bộ đệm ổ cắm (socket buffer)
- không hoạt động mới UNIX sockets (Vd: khi phân phối file tĩnh thông qua máy chủ upstream)
- hiệu suất có thể khác nhau tùy thuộc vào hệ điều hành (xem thêm ở đây)
Để bật thứ hay ho này trong nginx, gõ:
1 |
sendfile on; |
tcp_nopush
1 2 |
sendfile on; tcp_nopush on; |
- đảm bảo các gói đã đầy đủ kích thước trước khi gửi chúng cho máy khách.
- đối với gói cuối cùng,
tcp_nopush
sẽ bị xóa, cho phép TCP gửi nó ngay lập tức mà không bị delay 200ms
Nên có bao nhiêu process?
Worker processes
1 |
worker_process auto; |
Worker connections
1 |
worker_connections 1024; |
Open files limit
1 2 |
ulimit -Sn # soft limit ulimit -Hn # hard limit |
Giới hạn hệ thống này phải được tinh chỉnh theo worker_connections. Mọi kết nối đến sẽ mở ít nhất một tệp (thường là hai kết nối socket và kết nối phụ trợ khác hoặc file tĩnh trên ỗ đĩa). Vì vậy, tốt nhất là để giá trị này bằng với worker_connections X 2. May mắn thay, Nginx cung cấp tùy chọn tăng giá trị hệ thống này trong cấu hình nginx. Để cấu hình, hãy thêm directive worker_rlimit_nofile với giá trị thích hợp và reload lại nginx.
1 |
worker_rlimit_nofile 2048; |
Config
Nói dài dòng để hiểu hơn thôi, cuối cùng chúng ta cấu hình lại đơn giản như sau:
1 2 3 |
worker_process auto; worker_rlimit_nofile 2048; # Changes the limit on the maximum number of open files (RLIMIT_NOFILE) for worker processes. worker_connections 1024; # Sets the maximum number of simultaneous connections that can be opened by a worker process. |
Số lượng kết nối tối đa:
1 2 3 4 5 |
max no of connections = worker_processes * worker_connections ---------------------------------------------- (keep_alive_timeout + avg_response_time) * 2 |
Gzip
Compression level
1 |
curl -I -H 'Accept-Encoding: gzip,deflate' https://sofsog.com/ |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
❯ du -sh ./* 64K ./0_gzip 16K ./1_gzip 12K ./2_gzip 12K ./3_gzip 12K ./4_gzip 12K ./5_gzip 12K ./6_gzip 12K ./7_gzip 12K ./8_gzip 12K ./9_gzip ❯ ls -al -rw-r--r-- 1 matDobek staff 61711 3 Nov 08:46 0_gzip -rw-r--r-- 1 matDobek staff 12331 3 Nov 08:48 1_gzip -rw-r--r-- 1 matDobek staff 12123 3 Nov 08:48 2_gzip -rw-r--r-- 1 matDobek staff 12003 3 Nov 08:48 3_gzip -rw-r--r-- 1 matDobek staff 11264 3 Nov 08:49 4_gzip -rw-r--r-- 1 matDobek staff 11111 3 Nov 08:50 5_gzip -rw-r--r-- 1 matDobek staff 11097 3 Nov 08:50 6_gzip -rw-r--r-- 1 matDobek staff 11080 3 Nov 08:50 7_gzip -rw-r--r-- 1 matDobek staff 11071 3 Nov 08:51 8_gzip -rw-r--r-- 1 matDobek staff 11005 3 Nov 08:51 9_gzip |
gzip_http_version 1.1;
Config
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
gzip on; # bật gzip gzip_http_version 1.1; # chỉ bật gzip cho http 1.1 và cao hơn gzip_disable "msie6"; # IE 6 gặp sự cố với gzip gzip_comp_level 5; # inc compresion level và mức sử dung CPU gzip_min_length 100; # trọng lượng tối thiếu cho tiệp gzip gzip_proxied any; # Bật gzip cho proxied requests (vd: CDN) gzip_buffers 16 8k; # bộ đệm nén (nếu chúng ta vượt quá giá trị này, đĩa cứng sẽ được sử dụng thay cho RAM) gzip_vary on; # add header Vary Accept-Encoding (sẽ nói thêm bên dưới phần Cache) # Xác định file cần được nén: gzip_types text/plain; gzip_types text/css; gzip_types application/javascript; gzip_types application/json; gzip_types application/vnd.ms-fontobject; gzip_types application/x-font-ttf; gzip_types font/opentype; gzip_types image/svg+xml; gzip_types image/x-icon; |
Caching
Cache-Control
để quản lý cache trong HTTP/1.1Pragma
để tương thích ngược với các máy khách HTTP / 1.0
Cache có thể được chia thành hai loại: bộ nhớ cache công khai (public cache) và riêng tư (private cache). Public Cache lưu trữ những phản hồi (responses) để sử dụng lại cho nhiều người dùng. Private cache được dành riêng cho một người dùng.
1 2 |
add_header Cache-Control public; add_header Pragma public; |
1 2 3 4 5 |
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ { expires 1M; add_header Cache-Control public; add_header Pragma public; } |
- một cái dúng phiên bản cũ, không hỗ trợ gzip
- một cái trình duyệt mới có hỗ trợ gzip
Trình duyệt phiên bản cũ gửi yêu cầu sofsog.com/style.css đến CDN của chúng ta. Vì CDN chưa có tài nguyên này, nó sẽ truy vấn máy chủ của chúng ta và trả về phản hồi (response) không nén. CDN lưu trữ tập tin đã được băm (hash) (để sử dụng sau này):
1 2 3 4 5 |
{ ... sofsog.com/styles.css => FILE("/sites/sofsog/style.css") ... } |
Cuối cùng, file sẽ được trả lại cho máy khách.
Bây giờ, trình duyệt mới gửi cùng một yêu cầu tới CDN, yêu cầu sofsog.com/style.css, mong đợi một tài nguyên gzipped. Vì CDN chỉ xác định tài nguyên bởi URI, nó sẽ trả về cùng một tài nguyên không nén cho trình duyệt mới. Trình duyệt mới sẽ cố gắng trích xuất một tệp không được nén và sẽ nhận được rác.
1 2 3 4 5 6 7 |
{ ... (sofsog.com/styles.css, gzip) => FILE("/sites/sofsog/style.css.gzip") (sofsog.como/styles.css, text/css) => FILE("/sites/sofsog/style.css") ... } `` |
1 2 3 4 5 6 |
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ { expires 1M; add_header Cache-Control public; add_header Pragma public; add_header Vary Accept-Encoding; } |
Timeouts
Hãy cẩn thận khi thiết lập các giá trị bên trên, vì thời gian chờ đợi quá lâu có thể làm bạn dễ bị tấn công, trong khi thời gian quá ngắn không đủ để phản hồi các máy khách chậm.
1 2 3 4 |
# Configure timeouts client_body_timeout 12; client_header_timeout 12; send_timeout 10; |
Buffers
client_body_buffer_size
client_header_buffer_size
và large_client_header_buffers
client_max_body_size
Config
1 2 3 4 |
client_body_buffer_size 16K; client_header_buffer_size 1k; large_client_header_buffers 2 1k; client_max_body_size 8m; |
Keep-Alive
1 2 3 4 5 6 7 8 9 10 11 12 |
Open connection TCP Handshake: Warsaw ->------------------ synchronise packet (SYN) ----------------->- Berlin Warsaw -<--------- synchronise-acknowledgement packet (SYN-ACK) ------<- Berlin Warsaw ->------------------- acknowledgement (ACK) ------------------->- Berlin Data transfer: Warsaw ->---------------------- /image.jpg --------------------------->- Berlin Warsaw -<--------------------- (image data) --------------------------<- Berlin Close connection |
- keepalive giữa khách hàng và nginx
1 2 3 4 5 6 7 8 9 10 |
keepalive_disable msie6; # tắt keepalive trên IE6 #Số lượng yêu cầu mà khách hàng có thể thực hiện qua một kết nối duy nhất. #Giá trị mặc định là 100, nhưng giá trị cao hơn nhiều có thể đặc biệt hữu ích #khi thử nghiệm với load‑generation tool, thường gửi một số lượng lớn các yêu cầu từ một máy khách đơn. keepalive_requests 100000; # Thời gian idle keepalive connection mở. keepalive_timeout 60; |
- keepalive giữa khách hàng và upstream
1 2 3 4 5 6 7 8 9 10 11 12 |
upstream backend { # Số lượng các kết nối "idle keepalive" đến "upstream server" được mở cho mỗi worker process keepalive 16; } server { location /http/ { proxy_pass http://http_backend; proxy_http_version 1.1; proxy_set_header Connection ""; } } |
OK, được 1 phần nữa
Kết luận
Cảm ơn các bạn đã đọc đến đây. Loạt bài này sẽ không thể có được nếu không có số lượng lớn tài nguyên được tìm thấy trên internet. Dưới đây là một số trang web tuyệt vời mà mình thấy đặc biệt hữu ích khi viết loạt bài này:
- nginx docs
- nginx blog
- nginx fundamentals on udemy
- Ilya Grigorik blog, và cuốn sánh của anh ta: High Performance Browser Networking
- Martin Fjordvald blog