Enhancing Performance in Microservice Architecture with Kitex
Bài tóm tắt ByteDance 2024 3. Quy mô lớn Độ khó: Khó
ByteDance · 2024 · 3. Quy mô lớn · Độ khó: Khó — bài gốc
Bài của team CloudWeGo (ByteDance) tổng kết các tối ưu hiệu năng framework RPC Kitex từ 2019 đến 2023. Bài trước về lịch sử framework: ByteDance 2022. Nhiều biểu đồ so sánh trong bài gốc không có số trong văn bản.
Bối cảnh
- Thời monolith, gọi hàm tốn nano giây; chuyển sang microservice, mỗi lời gọi thành RPC tốn mili giây, chênh khoảng 10^6. Theo bài, lý do tách là trần tính toán của một máy và hiệu suất phát triển không tăng tuyến tính theo kích thước repo và team; cái giá là chi phí RPC trên mọi đường gọi.
- Kitex bắt đầu năm 2019, mở mã nguồn 2021; số microservice nội bộ tăng mạnh từ 2019 đến 2023.
Tối ưu theo từng chặng của một RPC
- Codec: reflection lúc chạy, nhiều lời gọi hàm và object nhỏ, khó tái dùng bộ nhớ (tải GC). Sinh code FastThrift/FastPB: tính
Size()trước để cấp một khối nhớ cố định, ghi tuần tự, hàm con được inline. Phần CPU dành cho codec giảm từ 3,58% xuống 0,98%. - JIT (Frugal, cho Thrift): code sinh ra phình theo số trường và phụ thuộc phiên bản công cụ của từng người. JIT sinh mã lúc chạy, dùng thanh ghi và inline sâu hơn: còn 0,78%.
- Mạng (Netpoll):
netchuẩn của Go một goroutine mỗi kết nối (nặng khi nhiều instance hai phía), không tự biết kết nối đã đóng,Write([]byte)không nhận vùng nhớ rời nên phải chép. Netpoll tái dùng goroutine, đọc ghi zero-copy, tuning cho gói nhỏ đồng thời cao, có cả sửa Go runtime (ưu tiên lập lịch) và syscall theo lô trong kernel nội bộ. - Trong cùng máy với service mesh: sidecar thêm một chặng; hijack bằng iptables tốn kém. UDS thay TCP nhanh hơn chút ít (benchmark: avg 127 → 118 µs, p99 232 → 205 µs phía client). ShmIPC: bộ nhớ chia sẻ làm kênh dữ liệu, UDS làm kênh báo sự kiện.
- Biến gọi khác máy thành cùng máy (pod affinity): scheduler container đặt instance của upstream và downstream lên cùng máy vật lý khi có thể; control plane tính trọng số động để đẩy thêm traffic vào đường nội máy (vẫn giữ cân bằng tải toàn cục); framework chọn gửi thẳng tới instance cùng máy hay qua mesh proxy.
Tuning khi chạy online
- GC:
GOGC=100mặc định chạy GC mỗi khi heap gấp đôi; service dùng 100 MB trên máy 4 GB vẫn GC dày, phá tái dùng object. gctuner (mã nguồn mở): đặt ngưỡng (ví dụ 70% giới hạn bộ nhớ), dưới ngưỡng thì nâng GOGC, vượt thì hạ, kẹp trong 50–500. Lưu ý khi nhiều tiến trình chung bộ nhớ hoặc bộ nhớ có đỉnh đột ngột. - "Ảo giác" của container: cgroup giới hạn thời gian CPU chứ không phải số CPU; container 4 core thấy 4 CPU nhưng thread chạy trên nhiều core hơn, dùng hết quota trong chu kỳ 100 ms thì cả container bị throttle tới hết chu kỳ.
- Song song và timeout: gửi 4 request song song, mỗi cái cần 50 ms, timeout 100 ms; nếu bên kia lúc đó chỉ có 2 CPU (còn làm việc khác, GC) thì request thứ ba timeout. Không phải lúc nào giảm song song cũng đúng; chỉnh
GOMAXPROCS, độ song song trong code, và dùng interface theo lô khi được. Chừa CPU cho sidecar trong cùng container.
Bài học cho SWE
- Tách microservice biến lời gọi hàm thành RPC đắt hơn sáu bậc; ở quy mô lớn, framework RPC và đường tới sidecar thành chi phí hạ tầng đáng kể (Monolith và microservices — đi và quay lại, Chi phí hạ tầng và build vs buy).
- GC mặc định không được thiết kế cho service nhạy p99; tune theo giới hạn bộ nhớ thật thay vì theo kích thước heap (Đổi ngôn ngữ và runtime).
- Đọc số CPU trong container là đọc sai; giới hạn là quota thời gian, vượt thì bị throttle cả container.
- Tối ưu từ codec, mạng, IPC đến lập lịch là cùng một chuỗi; vài phần trăm CPU ở framework nhân với hàng chục nghìn service là đáng.
Liên quan
#Kitex#RPC#Go#Thrift#Protobuf#Code generation#JIT#Frugal#Netpoll#Zero-copy#Service mesh#UDS#ShmIPC#Pod affinity
Được nhắc tới trong
Chủ đề (3)
Công ty (1)
Bài tóm tắt (3)