Load Balancing & Health
ปัญหา: connection เดียว call หลายอัน
หัวข้อที่มีชื่อว่า “ปัญหา: connection เดียว call หลายอัน”ทุกอย่างที่ทำให้ gRPC เร็วก็ทำให้ load balancing ยากด้วย gRPC client เปิด connection HTTP/2 เดียวที่อยู่ยาว แล้ว multiplex ทุก call ผ่าน connection เดียวนั้น ดีต่อ latency — แต่ L4 (TCP) load balancer แบบดั้งเดิม balance ที่ connection ไม่ใช่ที่ request คือเลือก backend ครั้งเดียวตอน connection เปิด จากนั้นทุก call ก็วิ่งบน connection เดียวไปยัง backend ตัวเดียวตลอด
flowchart TB
subgraph l4["L4 (TCP) balancer — pin connection"]
c1["client"] -->|connection เดียว| p["L4 LB"]
p --> b1["backend A
(ได้ทุกอย่าง)"]
b2["backend B
(ว่าง)"]
b3["backend C
(ว่าง)"]
end
subgraph csl["Client-side LB — กระจาย call"]
c2["client"] --> r["resolver + policy"]
r --> ba["backend A"]
r --> bb["backend B"]
r --> bc["backend C"]
end ผลคือ backend ตัวเดียวโดนถล่มขณะที่ตัวอื่นนั่งว่าง และ autoscaling ทำงานผิดพลาด นี่คือเรื่องเซอร์ไพรส์เรื่อง scale ของ gRPC ที่พบบ่อยที่สุด
สองวิธี balance gRPC
หัวข้อที่มีชื่อว่า “สองวิธี balance gRPC”มีสองวิธีที่ถูกต้อง:
- L7 (application-aware) proxy — proxy ที่พูด HTTP/2 และ gRPC ได้ (Envoy, Linkerd หรือ cloud LB ที่รู้จัก gRPC) balance ทีละ call ไม่ใช่ connection client แค่คุยกับ proxy
- Client-side load balancing — ตัว client เองรู้จัก backend ทุกตัวและกระจาย call ไปให้เอง โดยใช้ name resolver (เพื่อค้นหาชุดของ backend address เช่นผ่าน DNS หรือ xDS) และ load-balancing policy (เช่น
round_robin) เพื่อเลือก subchannel — หนึ่ง connection ต่อ backend — สำหรับแต่ละ call
client-side LB เป็นแนวทาง idiomatic ใน gRPC เพราะเลี่ยง network hop ที่เพิ่มขึ้นได้ setup คลาสสิก: resolve dns:///my-service:50051 เป็นหลาย IP แล้ว apply round_robin
conn, _ := grpc.NewClient( "dns:///my-service:50051", grpc.WithDefaultServiceConfig(`{"loadBalancingConfig":[{"round_robin":{}}]}`), grpc.WithTransportCredentials(insecure.NewCredentials()),)channel = grpc.insecure_channel( "dns:///my-service:50051", options=[("grpc.lb_policy_name", "round_robin")],)const client = new MyServiceClient( 'dns:///my-service:50051', credentials.createInsecure(), { 'grpc.service_config': JSON.stringify({ loadBalancingConfig: [{ round_robin: {} }], }) },);Health checking
หัวข้อที่มีชื่อว่า “Health checking”การ balance ข้าม backend จะมีประโยชน์ก็ต่อเมื่อคุณ route หนีจากตัวที่ ไม่ healthy gRPC นิยาม Health Checking Protocol มาตรฐาน — service เล็ก ๆ ที่ทุก server เปิดเผยได้:
syntax = "proto3";package grpc.health.v1;
message HealthCheckRequest { string service = 1; }message HealthCheckResponse { enum ServingStatus { UNKNOWN = 0; SERVING = 1; NOT_SERVING = 2; } ServingStatus status = 1;}
service Health { rpc Check(HealthCheckRequest) returns (HealthCheckResponse); rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse);}load balancer และ orchestrator (Kubernetes, Envoy) เรียก Check — หรือ subscribe ด้วย Watch — แล้วหยุด route ไปยัง backend ที่รายงาน NOT_SERVING เพราะเป็น proto มาตรฐาน เครื่องมืออย่าง grpc_health_probe จึงใช้ได้กับ server ของทุกภาษา
Keepalive
หัวข้อที่มีชื่อว่า “Keepalive”connection ที่อยู่ยาวอาจตายเงียบ ๆ ได้ (NAT ที่ idle ตัดทิ้ง, backend หายไป) keepalive ping — HTTP/2 PING เล็ก ๆ ตามช่วงเวลา — ตรวจจับ connection ที่ตายได้เร็ว เพื่อให้ client reconnect และ re-resolve ใหม่ แทนที่จะส่ง call เข้าไปในหลุมดำ ปรับค่าอย่างระวัง: ตั้งถี่เกินไป server ก็อาจ reject คุณเพราะ ping รัวเกินไป