Kiến thức nền
Điều khiển tắc nghẽn TCP: Từ Nagle đến BBR
Bốn mươi năm cố gắng trả lời một câu hỏi — bạn nên gửi các gói nhanh đến mức nào khi bạn không thể thấy mạng mà bạn đang gửi chúng qua?
Mục lục
- TL;DR
- Vấn đề ban đầu: Sụp đổ tắc nghẽn
- Nagle (1984): Bài báo tắc nghẽn đầu tiên
- Jacobson (1988): Slow Start, AIMD, và cửa sổ tắc nghẽn
- Reno, NewReno, SACK: Lặp lại những năm 1990
- CUBIC (2008): Các liên kết băng thông cao, độ trễ cao
- BBR (2016): Mô hình hóa mạng thay vì phản ứng
- Tại sao điều này vẫn đang được điều chỉnh
TL;DR
TCP không biết nó đang gửi vào đâu. Người gửi không thể thấy các hàng đợi trên các router trung gian, dung lượng của liên kết cổ chai, hay có bao nhiêu luồng khác đang cạnh tranh cùng băng thông. Nó phải suy ra tất cả từ hai tín hiệu: các gói được xác nhận và các gói không được xác nhận. Suy luận đó là chủ đề nghiên cứu liên tục kể từ năm 1984, khi Nagle viết bài báo điều khiển tắc nghẽn đầu tiên, và vẫn là một vấn đề mở vào năm 2026. Mỗi phiên bản kernel Linux đều thay đổi điều gì đó về cách các kết nối của bạn chia sẻ một mạng chúng không thể thấy.
Vấn đề ban đầu: Sụp đổ tắc nghẽn
Tháng 10 năm 1986, thông lượng internet giữa LBL và UC Berkeley — vài trăm yard dây — giảm từ 32 kbit/s xuống 40 bit mỗi giây. Ba bậc độ lớn sụp đổ. Mạng chưa bị ngắt kết nối. Nó vẫn đang chuyển tiếp các gói. Nó chỉ không chuyển tiếp gì hữu ích.
Nguyên nhân là phản hồi. Các triển khai TCP sớm sẽ, khi phát hiện mất, phát lại gói bị thiếu — hung hăng, không giảm tốc. Khi một router bắt đầu rớt các gói vì hàng đợi của nó đầy, mọi người gửi có gói bị rớt phản ứng bằng cách phát lại nhiều hơn. Hàng đợi tràn mạnh hơn. Nhiều gói hơn bị rớt. Mỗi lần phát lại làm vấn đề tệ hơn.
Đây là sụp đổ tắc nghẽn, và Van Jacobson chẩn đoán nó trong một bài báo SIGCOMM năm 1988 mà có lẽ là bài báo quan trọng nhất trong lịch sử các giao thức truyền. Sửa của Jacobson — và sửa mà mọi TCP đã xuất xưởng kể từ đó — là ghép tốc độ truyền của người gửi với các tín hiệu quay lại từ mạng, và chậm lại khi mất được phát hiện, không tăng tốc.
Nagle (1984): Bài báo tắc nghẽn đầu tiên
Bốn năm trước đó, John Nagle tại Ford Aerospace đã viết RFC 896, không sử dụng từ “điều khiển tắc nghẽn” nhưng giới thiệu hầu hết các ý tưởng. Nagle đang nghĩ về một trường hợp cụ thể, xấu hổ: một phiên Telnet trên một liên kết đường dài gửi các gói một-byte.
Mỗi tải trọng một-byte trở thành một gói TCP/IP đầy đủ — 40 byte header, 1 byte dữ liệu, và một gói ACK riêng trên đường về. Mạng đang dành 98% công việc của nó cho đóng gói. Dưới bất kỳ loại tải nào, chỉ riêng tổn phí có thể làm tắc nghẽn liên kết.
Thuật toán của Nagle nói: đừng gửi các gói nhỏ trong khi có dữ liệu chưa được xác nhận đang bay. Giữ các ghi nhỏ trong giây lát, kết hợp chúng, và gửi chúng khi hoặc (a) bạn có đủ byte để đáng gửi hoặc (b) ACK đang chờ quay lại. Điều này giới thiệu ý tưởng rằng quyết định truyền của người gửi nên phụ thuộc vào trạng thái của mạng, không chỉ có byte sẵn có.
def nagle_should_send(bytes_queued, max_segment_size, has_outstanding_unacked):
if bytes_queued >= max_segment_size:
return True
if not has_outstanding_unacked:
return True
return False # giữ nó, chờ ACK hoặc thêm dữ liệu
Các ứng dụng hiện đại quan tâm đến độ trễ (SSH tương tác, các yêu cầu HTTP nhỏ) đôi khi vẫn vô hiệu hóa Nagle với TCP_NODELAY. Đánh đổi vẫn là cùng một thứ Nagle đã trình bày năm 1984.
Jacobson (1988): Slow Start, AIMD, và cửa sổ tắc nghẽn
Bài báo SIGCOMM của Jacobson giới thiệu bốn ý tưởng vẫn là cốt lõi của điều khiển tắc nghẽn TCP.
Một cửa sổ tắc nghẽn (cwnd). Ngoài cửa sổ được quảng cáo của người nhận (người nhận có thể đệm bao nhiêu), người gửi duy trì ước lượng riêng của mình về bao nhiêu mạng có thể xử lý. Cửa sổ gửi hiệu quả là tối thiểu của hai. Cwnd bắt đầu nhỏ và phát triển dựa trên phản hồi.
Slow Start. Cwnd bắt đầu ở một gói. Mỗi ACK nhân đôi nó. Điều này nghe có vẻ chậm — “slow start” là một cái tên đùa; thực sự là tăng trưởng mũ — nhưng nó cho phép người gửi dò dung lượng của mạng nhanh chóng mà không cam kết tốc độ cao trước.
Tăng Cộng, Giảm Nhân (AIMD). Một khi cwnd vượt ngưỡng, tăng trưởng chuyển từ mũ sang tuyến tính: một gói thêm mỗi RTT. Khi mất được phát hiện, cwnd bị cắt đôi (hoặc tệ hơn). Ý tưởng là dò từ từ lên là an toàn; vượt quá đắt, nên phản ứng với vượt quá là sắc.
cwnd
▲
│ ╱╲
│ ╱ ╲
│ ╱╲ ╱ ╲
│ ╱ ╲ ╱ ╲
│ ╱ ╲ ╲╱╲ ╱
│ ╱ ╲ ╱
│ ╱ ╲ ╱
│ ╱ ╲ ╱
│ ╱ ╲ ╱
│ ╱ ╳ ← mất được phát hiện: cwnd bị cắt đôi
│ ╱ (slow start — mũ)
│ ╱
│ ╱
│ ╱
└────────────────────────────────────────► thời gian
Fast Retransmit / Fast Recovery. Thay vì chờ timeout để phát hiện mất (đốt một RTT đầy không hoạt động), người gửi suy ra mất khi thấy ba ACK trùng — người nhận phàn nàn về cùng byte bị thiếu ba lần liên tiếp. Phát lại ngay lập tức; đừng rớt cwnd suốt xuống một.
Bốn ý tưởng này, kết hợp, biến TCP từ “tắc nghẽn làm sụp đổ mạng” thành “hàng trăm luồng cạnh tranh ổn định vào một thứ giống như phần chia sẻ công bằng.” Toán học không rõ ràng nhưng đứng vững: AIMD là một trong vài cơ chế mà chứng minh được hội tụ đến công bằng giữa các luồng chia sẻ một cổ chai.
Bài báo năm 1988 cũng giới thiệu ước lượng RTT — thuật toán để đo thời gian vòng quay của một kết nối nên bạn biết khi nào một gói đủ muộn để giả định nó bị mất. Đây tự nó là một vấn đề tinh vi đáng ngạc nhiên; bộ ước lượng dựa trên EWMA gốc của Jacobson vẫn đại khái là thứ TCP hiện đại sử dụng.
Reno, NewReno, SACK: Lặp lại những năm 1990
Qua những năm 1990, các biến thể trên phương án gốc của Jacobson được đặt tên theo các phòng thí nghiệm nghiên cứu và các triển khai:
- Tahoe — TCP Jacobson gốc. Trên bất kỳ mất nào, rớt cwnd xuống 1 và khởi động lại slow start. Bảo thủ.
- Reno — thêm Fast Recovery. Giữ cwnd cao hơn sau một mất duy nhất; giả định mạng không thực sự sụp đổ.
- NewReno — hành vi tốt hơn khi nhiều gói bị mất trong cùng cửa sổ.
- SACK (Selective Acknowledgment, RFC 2018) — cho phép người nhận nói với người gửi chính xác gói nào bị thiếu, thay vì chỉ “Tôi vẫn đang chờ byte X.” Đây là thứ mọi TCP hiện đại sử dụng; không có SACK, người gửi phải đoán.
Đến cuối những năm 1990, TCP đã gần như giải quyết vấn đề Jacobson đặt ra để giải: ngăn ngừa sụp đổ tắc nghẽn và chia sẻ băng thông công bằng giữa các luồng trên các mạng tương tự. Điều nó chưa giải quyết là: điều gì xảy ra khi các mạng không tương tự?
CUBIC (2008): Các liên kết băng thông cao, độ trễ cao
Khi các liên kết xương sống di chuyển từ megabit sang gigabit và độ trễ xuyên lục địa vẫn giữ nguyên, AIMD trở nên bảo thủ một cách xấu hổ. Một liên kết xuyên Thái Bình Dương 10 Gbps, 100 ms yêu cầu một cwnd khổng lồ để được tận dụng đầy đủ. +1 tuyến tính mỗi RTT của Reno mất mãi mãi để phát triển đến kích thước đó.
Ví dụ cụ thể: một luồng Reno trên một liên kết 10 Gbps với RTT 100 ms, sau một mất duy nhất, sẽ mất khoảng 80 phút để lấp đầy cửa sổ của nó. Đó không phải một mạng sử dụng dung lượng của nó — đó là một mạng với một sự kiện mất ở đầu chuyển tệp và một workstation hoàn thành chuyển tệp qua bữa trưa.
CUBIC (Ha, Rhee, Xu, 2008) thay hàm tăng trưởng tuyến tính bằng một cái bậc ba. Sau một mất, cwnd phát triển chậm ban đầu (để ở gần vùng an-toàn-đã-biết), rồi tăng tốc, rồi phẳng lại gần nơi mất trước đã xảy ra. Nó tìm điểm vận hành băng thông cao nhanh chóng và giữ ở đó cho đến tín hiệu mất tiếp theo.
CUBIC đã là mặc định Linux kể từ kernel 2.6.19 (cuối 2006). Hầu hết chuyển dữ liệu của thế giới chạy trên nó.
BBR (2016): Mô hình hóa mạng thay vì phản ứng
Mọi thuật toán trên — Reno, NewReno, CUBIC — là dựa trên mất. Nó giả định rằng nếu bạn không mất các gói, bạn có thể gửi nhanh hơn. Giả định đó vỡ khi có các buffer lớn trong đường.
Phình buffer là hiện tượng nơi một router nhà rẻ có vài trăm mili giây buffer. Một TCP dựa trên mất lấp đầy buffer đó trước khi nó thấy mất, và RTT phồng từ 20 ms thành 500 ms trong khi cwnd tiếp tục leo. Kết nối không bị tắc nghẽn theo nghĩa “sắp rớt các gói” — nó bị tắc nghẽn theo nghĩa “giao các gói với độ trễ phi lý” — và các thuật toán dựa trên mất không thể nói sự khác biệt.
BBR (Bottleneck Bandwidth and Round-trip propagation time), xuất bản bởi các kỹ sư Google Cardwell, Cheng, Gunn, Yeganeh, và Jacobson (vâng, cùng Jacobson) năm 2016, có một cách tiếp cận hoàn toàn khác: xây một mô hình của mạng và điều chỉnh nhịp với nó.
BBR đo:
- Băng thông cổ chai (BtlBw) — thông lượng tối đa quan sát trên vài giây cuối.
- Thời gian lan truyền vòng quay (RTprop) — RTT tối thiểu quan sát (đường vật lý, không bị phồng bởi xếp hàng).
Sau đó nó gửi với tốc độ sao cho cwnd = BtlBw × RTprop. Đó là tích băng thông-trễ — lượng chính xác dữ liệu đang bay để giữ cổ chai đầy mà không xây một hàng đợi. BBR định kỳ dò lên để đo lại BtlBw và xuống để đo lại RTprop, nhưng dành hầu hết thời gian chạy đều ở tốc độ đúng.
BBR hoạt động tốt hơn đáng kể so với CUBIC trên các đường bị phình buffer. Nó cũng có một vấn đề công bằng ở các phiên bản đầu (BBRv1 hung hăng với các luồng CUBIC chia sẻ một liên kết) mà BBRv2 và BBRv3 đã đang sửa dần dần. Đến năm 2026, BBR được triển khai rộng rãi trên cơ sở hạ tầng của Google, được bật như một tùy chọn không-mặc-định trong Linux, và vẫn là một lĩnh vực nghiên cứu hoạt động.
Tại sao điều này vẫn đang được điều chỉnh
Bạn có thể hợp lý hỏi: nếu Jacobson đã giải sụp đổ tắc nghẽn năm 1988, tại sao mọi người vẫn đang xuất bản bài báo về điều này?
- Các loại liên kết mới tiếp tục phá vỡ các giả định. Vệ tinh (Starlink), 5G di động, Ethernet trung tâm dữ liệu, Wi-Fi mất — mỗi cái có đặc tính độ trễ, mất, và hàng đợi khác nhau, và mỗi cái lộ các trường hợp biên mà các thuật toán cũ không được thiết kế cho.
- Các mạng trung tâm dữ liệu quan tâm đến độ trễ đuôi, không phải thông lượng. DCTCP, DCQCN, và các thuật toán tương tự giả định bạn có thể báo hiệu tắc nghẽn với các dấu ECN (thông báo một-bit từ các router) thay vì chờ các rớt. Đây là một mục tiêu tối ưu hóa khác.
- TCP internet phải chơi công bằng. Một thuật toán mới tuyệt vời cho lưu lượng riêng của bạn nhưng bỏ đói các luồng khác chia sẻ một liên kết sẽ không được chấp nhận. Hầu hết khó khăn của điều khiển tắc nghẽn hiện đại là chứng minh các tính chất công bằng, không phải thông lượng thô.
- QUIC thay đổi cảnh quan. Vì QUIC chạy trong không gian người dùng, các thuật toán điều khiển tắc nghẽn có thể được triển khai bằng nâng cấp một binary thay vì xuất xưởng một kernel. Tốc độ lặp lại đã tăng đáng kể.
Bài viết TCP/IP đề cập đến điều khiển tắc nghẽn trong một đoạn duy nhất như một thứ gì đó “vẫn đang được điều chỉnh hôm nay.” Nó là — và lý do là cơ bản. Internet là một mục tiêu di chuyển: các ứng dụng mới, các loại liên kết mới, các chế độ quy mô mới, các yêu cầu công bằng mới. Không có câu trả lời cuối cho “tôi nên gửi nhanh đến mức nào,” vì câu hỏi tiếp tục được định nghĩa lại.
Điều đã đứng vững là cấu trúc Jacobson thiết lập năm 1988: sử dụng phản hồi từ mạng để quyết định tốc độ, cẩn thận về tăng lên, cắt giảm sắc khi điều gì đó sai, và giả định rằng điều duy nhất bạn biết về mạng là những gì các ACK và mất của nó nói với bạn. Bốn mươi năm sau, mọi thuật toán mới vẫn đang trả lời cùng câu hỏi — gói tiếp theo nên làm gì? — với đo lường tốt hơn một chút và toán học thông minh hơn một chút.