Writing a Client
channel, stub, call
หัวข้อที่มีชื่อว่า “channel, stub, call”gRPC client สร้างจาก object 2 ตัว:
- channel — connection ที่อายุยาวไปยัง address ของ server (
host:port) channel จัดการ HTTP/2 connection ข้างใต้, การ reconnect และ load balancing - stub — wrapper บาง ๆ ที่มี type สร้าง จาก channel เปิด method หนึ่งตัวต่อ
rpcการเรียกstub.GetUser(req)จะส่ง request ผ่าน channel แล้วคืน response
flowchart LR stub["stub.GetUser(req)"] --> channel["channel (host:port, HTTP/2)"] stub2["stub.GetUser(req2)"] --> channel channel -->|multiplexed| server["server :50051"]
stub ทั้งสองตัวใช้ channel เดียวกัน — นั่นแหละคือประเด็น channel สร้างแพงแต่แชร์ถูก; ส่วน stub เป็นแค่ typed handle ที่วางทับอยู่ข้างบน
การยิง call
หัวข้อที่มีชื่อว่า “การยิง call”นี่คือ client ครบชุดที่สร้าง channel, สร้าง stub, เรียก GetUser แล้วจัดการทั้ง response และ error ที่อาจเกิด:
conn, err := grpc.NewClient("localhost:50051", grpc.WithTransportCredentials(insecure.NewCredentials()))if err != nil { log.Fatal(err)}defer conn.Close() // close the channel when the program ends, not per call
client := userv1.NewUserServiceClient(conn) // stub
user, err := client.GetUser(ctx, &userv1.GetUserRequest{Id: 42})if err != nil { st, _ := status.FromError(err) // inspect the gRPC status log.Fatalf("call failed: %s — %s", st.Code(), st.Message())}fmt.Println(user.Name)channel = grpc.insecure_channel("localhost:50051")client = user_pb2_grpc.UserServiceStub(channel) # stub
try: user = client.GetUser(user_pb2.GetUserRequest(id=42)) print(user.name)except grpc.RpcError as e: print(f"call failed: {e.code()} — {e.details()}") # inspect the statusconst channel = new UserServiceClient( 'localhost:50051', credentials.createInsecure(),); // channel + stub in one
channel.getUser({ id: 42 }, (err, user) => { if (err) { console.error(`call failed: ${err.code} — ${err.details}`); return; } console.log(user.name);});สังเกตว่า error ไม่ใช่ exception เรื่อง network — แต่เป็น status ที่มี code (NOT_FOUND, DEADLINE_EXCEEDED, UNAVAILABLE) และ message client จะ branch ตาม code นั้น
reuse channel — อย่าสร้างใหม่ต่อ call
หัวข้อที่มีชื่อว่า “reuse channel — อย่าสร้างใหม่ต่อ call”ความผิดพลาดของ client ที่พบบ่อยที่สุดคือสร้าง channel ใหม่ทุก request channel ต้องตั้ง HTTP/2 connection, name resolution และ state ของ load balancing — ทำแบบนั้นต่อ call คือทิ้งประสิทธิภาพที่ดีที่สุดของ gRPC และอาจทำให้ socket หมดตอน load สูง
สร้าง channel เดียวต่อ target ตอน startup แล้วแชร์ ให้ทุก call และทุก stub ปิด channel แค่ตอนโปรแกรม shutdown เท่านั้น
blocking vs async
หัวข้อที่มีชื่อว่า “blocking vs async”stub ที่ generate มามักให้ทั้งสองแบบ: call แบบ blocking/synchronous ที่คืน response (ง่ายสุดสำหรับ script และ request handler) และแบบ async/future ที่คืนทันทีแล้วเสร็จทีหลัง (มีประโยชน์สำหรับ fan-out หรือ server แบบ non-blocking) เลือกตามจุดเรียกใช้ ทั้งคู่วิ่งผ่าน channel เดียวกัน