Bể kết nối là thứ mọi thư viện đều có và ít ai chỉnh. Bài này đo xem nó tiết kiệm được gì, và Redis chịu được bao nhiêu kết nối.
Dùng lại kết nối hay mở mới mỗi lệnh
| Không AUTH | Có AUTH | |
|---|---|---|
| Dùng lại kết nối | 20.695/s | 20.563/s |
| Mỗi lệnh một kết nối | 3.768/s | 3.235/s |
Chậm 5,5 lần, và 6,4 lần khi có mật khẩu.
Chênh lệch đến từ hai chỗ: bắt tay TCP là một lượt đi về, và AUTH là một lượt nữa. Với một lệnh SET cũng chỉ tốn một lượt, việc mở kết nối tăng chi phí lên gấp ba.
Chú ý dòng đầu: có mật khẩu hay không, dùng lại kết nối cho kết quả như nhau (20.695 so với 20.563). Mật khẩu không làm Redis chậm — nó chỉ làm việc mở kết nối chậm.
Đây là chỗ đáng nói vì người ta hay ngại bật requirepass vì sợ hiệu năng. Với bể kết nối, nỗi lo đó không có cơ sở.
Kết nối rẻ
mở 5.000 kết nối 0,9 giây (5.513 kết nối/giây)
bộ nhớ mỗi kết nối 1.928 byte
tổng bộ nhớ tiến trình
5.000 kết nối 54,65 MiB
10.000 kết nối 80,35 MiB
CPU khi 5.000 kết nối ngồi không 0,79%
Gần 2 KB mỗi kết nối theo cách Redis tự tính, và khoảng 5 KB nếu tính cả bộ đệm socket của kernel.
Kết nối ngồi không gần như không tốn CPU. Đó là nhờ mô hình vòng lặp sự kiện: 5.000 socket nhàn rỗi chỉ là 5.000 mục trong danh sách theo dõi, không phải 5.000 luồng.
Nghĩa là giữ nhiều kết nối mở là chuyện bình thường và rẻ. Cái đắt là mở đi mở lại.
Nhưng có trần cứng
kết nối thứ 10.001 -> -ERR max number of clients reached
maxclients mặc định 10.000, và Redis từ chối thẳng khi vượt.
Điều tệ hơn con số: trong lúc đó, redis-cli cũng không vào được. Tôi thử đọc INFO và nhận về chuỗi rỗng — không còn chỗ cho một kết nối nữa.
Nghĩa là khi sự cố hết kết nối xảy ra, bạn mất luôn đường để nhìn vào bên trong và tìm hiểu chuyện gì đang xảy ra.
Redis giữ lại 32 kết nối cho mục đích nội bộ, nhưng chúng dành cho bản sao và các tiến trình con, không dành cho bạn.
Hai việc nên làm:
- Đặt
maxclientscao hơn hẳn số kết nối thật. Chi phí là bộ nhớ, và 2 KB mỗi kết nối là rẻ. - Đặt trần cho bể kết nối của ứng dụng. Không giới hạn phía khách nghĩa là một sự cố chậm ở tầng ứng dụng biến thành hàng nghìn kết nối mới.
Và nhớ kiểm ulimit -n của tiến trình Redis: nó không mở được nhiều socket hơn giới hạn mô tả tệp, bất kể maxclients khai bao nhiêu. Redis tự hạ maxclients xuống nếu ulimit thấp hơn, và ghi cảnh báo lúc khởi động.
Kích cỡ bể kết nối nên đặt bao nhiêu
Từ những gì đã đo trong sê-ri:
- Một kết nối không đường ống đạt khoảng 20.000 ops/s (phần 2 và bài này).
- Thông lượng tăng gần tuyến tính tới 8 kết nối, đỉnh ở 32, rồi giảm (phần 2).
Nên bể kết nối khoảng 10 đến 50 kết nối mỗi tiến trình ứng dụng là khoảng hợp lý. Lớn hơn không tăng thông lượng — như đo ở phần 2, 128 kết nối cho ít hơn 32.
Sai lầm phổ biến là đặt bể bằng số luồng của ứng dụng. Với 200 luồng, bạn không cần 200 kết nối Redis; bạn cần 20 và một hàng chờ.
Bốn thứ nên đặt
timeout — mặc định 0, tức không bao giờ đóng kết nối nhàn rỗi. Kết nối rò rỉ từ ứng dụng đã chết sẽ ở đó mãi. Đặt 300 giây.
tcp-keepalive — mặc định 300 giây. Nó phát hiện kết nối đã chết ở phía bên kia mà không đóng đàng hoàng, chuyện thường xảy ra khi máy khách bị giết hoặc mạng đứt.
maxmemory-clients — thêm từ Redis 7, giới hạn tổng bộ nhớ dành cho khách. Đây là câu trả lời cho vấn đề đo ở phần 30: 20 khách đọc chậm một giá trị 10 MB chiếm 200 MB. Đặt nó là có trần.
Hạn chờ ở phía khách. Kết nối treo vô hạn là cách bể kết nối cạn mà không ai biết vì sao.
Kết nối cũng cần cho lệnh chặn
Nhắc lại điều đã đo ở phần 17: BLPOP chiếm trọn một kết nối trong lúc chờ. 50 worker chờ việc là 50 kết nối không làm gì khác được.
Thư viện thường không tính điều này vào kích cỡ bể — chúng lấy kết nối từ bể rồi giữ vô thời hạn. Cách đúng là dùng kết nối riêng cho lệnh chặn, tách khỏi bể dùng chung.
Thử ba mươi giây
Xem bạn đang dùng bao nhiêu kết nối và có ai rò rỉ không:
redis-cli info clients | grep -E 'connected_clients|blocked_clients|maxmemory_clients'
redis-cli config get maxclients timeout tcp-keepalive | paste - -
redis-cli client list | awk '{
for(i=1;i<=NF;i++){
if($i ~ /^addr=/) a=$i; if($i ~ /^idle=/) d=$i; if($i ~ /^cmd=/) c=$i
}
print d, c, a
}' | sort -t= -k2 -rn | head -10
Cột idle là số giây kết nối không gửi lệnh nào. Thấy hàng chục kết nối với idle hàng nghìn giây và cmd=NULL nghĩa là bạn đang rò rỉ kết nối — mỗi cái chiếm 2 KB và một chỗ trong maxclients, vĩnh viễn, vì timeout mặc định là 0.
Phần sau: ACL và TLS — đo từng lớp bảo mật và chi phí thật của mã hoá.