EADDRNOTAVAIL thực sự có nghĩa là gì?
Việc thấy lỗi Error: connect EADDRNOTAVAIL trong log thường có nghĩa là ứng dụng của bạn đang xử lý nhanh hơn mức hệ điều hành có thể đáp ứng. Nói một cách dễ hiểu: hệ điều hành đã hết các cổng cục bộ (local ports) để gán cho các kết nối đi mới. Nút thắt cổ chai này về mặt kỹ thuật được gọi là ephemeral port exhaustion (cạn kiệt cổng tạm thời).
Theo mặc định, Linux dành riêng một dải cổng cụ thể cho lưu lượng truy cập đi. Nếu ứng dụng của bạn thực hiện 5.000 lệnh gọi API hoặc truy vấn cơ sở dữ liệu liên tục, các kết nối đó không biến mất ngay lập tức khi hoàn thành. Chúng sẽ duy trì ở trạng thái TIME_WAIT trong 60 giây. Nếu bạn dùng hết số lượng cổng có sẵn trước khi các cổng cũ hết hạn, ứng dụng của bạn sẽ gặp lỗi.
Bước 1: Chẩn đoán việc sử dụng cổng
Đừng bắt đầu thay đổi các biến hệ thống một cách mù quáng. Trước tiên, hãy xác thực xem các socket TIME_WAIT có thực sự là nguyên nhân hay không. Hãy sử dụng công cụ ss—nó nhanh hơn nhiều so với netstat cũ đối với các máy chủ có lưu lượng truy cập cao.
# Xem tóm tắt nhanh về thống kê socket
ss -s
# Đếm chính xác có bao nhiêu kết nối đang kẹt ở trạng thái TIME_WAIT
ss -tan | grep TIME-WAIT | wc -l
Nếu số lượng này dao động khoảng 25.000 hoặc cao hơn, bạn chắc chắn đã chạm giới hạn. Ở mức này, kernel đơn giản là không thể tái chế các cổng đủ nhanh để đáp ứng các yêu cầu mới.
Bước 2: Mở rộng dải cổng Ephemeral
Hầu hết các bản phân phối Linux đi kèm với một dải cổng hạn chế, thường là từ 32768 đến 60999. Điều này chỉ cung cấp cho bạn khoảng 28.000 cổng đồng thời. Trên một microservice bận rộn, bạn có thể dùng hết số lượng này chỉ trong vài giây.
Kiểm tra giới hạn hiện tại của bạn bằng lệnh này:
cat /proc/sys/net/ipv4/ip_local_port_range
Để khắc phục điều này, chúng ta có thể mở rộng dải cổng từ 1024 lên đến 65535. Điều này giúp tăng gấp đôi dung lượng lên khoảng 64.000 cổng. Hãy áp dụng tạm thời để kiểm tra:
sudo sysctl -w net.ipv4.ip_local_port_range="1024 65535"
Để thay đổi này có hiệu lực sau khi khởi động lại, hãy thêm dòng sau vào /etc/sysctl.conf:
net.ipv4.ip_local_port_range = 1024 65535
Bước 3: Bật tính năng tái sử dụng Socket TCP an toàn
Việc mở rộng dải cổng có giúp ích, nhưng nó không giải quyết được thời gian chờ 60 giây của các socket đã đóng. Bạn có thể yêu cầu kernel tái sử dụng một socket ở trạng thái TIME_WAIT nếu nó an toàn về mặt giao thức. Đây là một giải pháp thay đổi cục diện cho các môi trường có độ đồng thời cao.
Thêm cài đặt này vào /etc/sysctl.conf:
net.ipv4.tcp_tw_reuse = 1
Sau đó, tải lại các cài đặt:
sudo sysctl -p
Lưu ý quan trọng: Bạn có thể thấy các hướng dẫn cũ gợi ý sử dụng net.ipv4.tcp_tw_recycle. Hãy tránh nó. Nó đã thực sự bị loại bỏ khỏi các kernel Linux sau phiên bản 4.12 vì nó làm gián đoạn kết nối cho người dùng phía sau NAT (như người dùng di động hoặc mạng văn phòng). Hãy trung thành với tcp_tw_reuse.
Bước 4: Khắc phục nguyên nhân gốc rễ trong mã nguồn của bạn
Tinh chỉnh hạ tầng thường chỉ là giải pháp tạm thời. Nếu mã nguồn của bạn mở và đóng một kết nối mới cho mỗi tác vụ đơn lẻ, bạn đang lãng phí chu kỳ CPU và tài nguyên mạng. Thay vào đó, bạn nên triển khai Connection Pooling hoặc sử dụng các header Keep-Alive.
- Node.js: Đừng sử dụng agent toàn cục mặc định cho lưu lượng lớn. Hãy tạo một
http.Agenttùy chỉnh vớikeepAlive: truevàmaxSockets: Infinity. - Databases: Thay vì kết nối/ngắt kết nối trên mỗi truy vấn, hãy sử dụng một trình điều phối (pooler) như PgBouncer cho Postgres hoặc logic pooling có sẵn trong ORM của bạn.
- Nginx: Nếu bạn sử dụng Nginx làm reverse proxy, hãy đảm bảo bạn sử dụng
proxy_http_version 1.1;và xóa headerConnectionđể giữ cho các kết nối upstream luôn mở.
Cách xác minh bản sửa lỗi
Khi bạn đã tinh chỉnh kernel và cập nhật ứng dụng, hãy theo dõi số lượng socket. Bạn sẽ muốn thấy các log lỗi không còn xuất hiện trong khi số lượng socket ổn định.
# Theo dõi số lượng kết nối TIME_WAIT cập nhật mỗi giây
watch -n 1 "ss -tan | grep TIME-WAIT | wc -l"
Nếu con số này duy trì ở mức thấp hơn nhiều so với giới hạn 64.511 mới của bạn trong thời gian cao điểm, bạn đã giải quyết thành công nút thắt cổ chai.
Mẹo nâng cao để phòng ngừa
Các vấn đề mạng thường thay đổi khi bạn mở rộng quy mô. Khi logic ứng dụng của bạn nhanh hơn, kernel của hệ điều hành sẽ trở thành giới hạn mới. Nếu bạn đang xây dựng các kiến trúc phức tạp với nhiều VPC, hãy theo dõi sát sao việc lập kế hoạch IP.
Tôi thường xuyên sử dụng Subnet Calculator trên ToolCraft để lập bản đồ các khối CIDR. Nó đảm bảo các phân đoạn mạng đủ lớn để xử lý hàng nghìn máy chủ mà không bị cạn kiệt IP hoặc chồng chéo dải IP.
Cân nhắc hai tinh chỉnh cuối cùng này cho các trường hợp cực đoan:
- Giảm timeout FIN-WAIT: Giảm
net.ipv4.tcp_fin_timeouttừ 60 xuống 15. Điều này buộc hệ điều hành giải phóng các socket đã đóng nhanh hơn nhiều. - IP Aliasing: Nếu một IP là không đủ, hãy gán IP thứ hai cho giao diện mạng của bạn. Điều này cung cấp cho bạn một bộ 64.000 cổng hoàn toàn mới để sử dụng.

