Suốt mười bài trước, ta xây và tối ưu service gRPC. Nhưng chạy được trong lab khác xa vận hành được trong production. Ba câu hỏi mà mọi service thật phải trả lời: Làm sao orchestrator (Kubernetes, load balancer) biết service còn sống để gửi traffic? Làm sao gọi thử/debug service khi không cầm sẵn file .proto? Và làm sao deploy (giết pod cũ, dựng pod mới) mà không rớt những request đang chạy dở? gRPC có đáp án chuẩn cho cả ba: health check, reflection, và graceful shutdown. Bài này (phần 11/12) đo thật cả ba trong go-lab.

Ba tính năng vận hành

Ảnh chụp đoạn mã nền tối minh hoạ health check reflection graceful shutdown ba thứ gRPC cần cho production, để orchestrator biết service sống để gọi thử không cần proto và để deploy không rớt request đang chạy. Một health check service chuẩn cho orchestrator thăm dò hs bằng health.NewServer healthpb.RegisterHealthServer g hs hs.SetServingStatus svc.Worker SERVING K8s load balancer gọi grpc.health.v1.Health Check biết pod sống chết đổi sang NOT_SERVING khi đang bảo trì LB ngừng gửi traffic. Hai reflection liệt kê service không cần proto reflection.Register g sau đó grpcurl hay công cụ bất kỳ hỏi server có service method gì grpcurl -plaintext host port list liệt kê mọi service grpcurl list svc.Worker liệt kê method. Ba graceful shutdown tắt mà không rớt request g.GracefulStop chờ request đang chạy xong từ chối request mới rồi tắt g.Stop cắt ngay request đang chạy bị rớt dùng khi khẩn cấp khi deploy scale-down GracefulStop để không ai nhận lỗi giữa chừng

Hình 1: Health check (service chuẩn cho orchestrator thăm dò, đổi SERVING/NOT_SERVING); reflection (reflection.Register cho grpcurl liệt kê service không cần .proto); graceful shutdown (GracefulStop chờ request đang chạy xong và từ chối request mới, khác Stop cắt ngay).

  • Health check: đăng ký một service chuẩn grpc.health.v1.Health. Orchestrator gọi Check() định kỳ để biết pod sống/chết; bạn đặt SetServingStatus(NOT_SERVING) khi bảo trì để LB ngừng gửi traffic trước khi tắt.
  • Reflection: reflection.Register(g) cho phép bất kỳ công cụ nào (grpcurl) hỏi server "có service/method gì" — gọi thử và debug không cần cầm file .proto.
  • Graceful shutdown: GracefulStop() chờ các request đang chạy hoàn thành và từ chối request mới, rồi mới tắt — khác Stop() cắt ngay (làm rớt request đang chạy).

Đo thật: cả ba trong go-lab

Ảnh chụp bảng kết quả đo thật health reflection graceful shutdown output thật go-lab grpc-go v1.67.1 grpcurl v1.8.9. Một health check đổi trạng thái được Check svc.Worker trả SERVING SetServingStatus NOT_SERVING rồi Check trả NOT_SERVING orchestrator đọc trạng thái này để quyết định gửi traffic hay không đổi sang NOT_SERVING lúc bảo trì là LB tự ngừng dồn request. Hai reflection grpcurl liệt kê service không cần proto grpcurl -plaintext 127.0.0.1 50122 list trả grpc.health.v1.Health grpc.reflection.v1.ServerReflection svc.Worker grpcurl list svc.Worker trả svc.Worker.Do grpcurl grpc.health.v1.Health Check trả status SERVING nhờ reflection công cụ hỏi thẳng server có gì gọi thử debug mà không cần cầm file proto. Ba graceful shutdown chờ request đang chạy từ chối request mới t cộng 150ms gọi GracefulStop một request 800ms đang chạy t cộng 250ms request mới sau GracefulStop bị từ chối error true t cộng 800ms request đang chạy hoàn thành bình thường xong sau 800ms GracefulStop chờ request đang bay hoàn thành 800ms rồi mới tắt nhưng từ chối request mới ngay khi deploy scale-down đây là điều giữ cho không ai nhận lỗi giữa chừng khác Stop cắt ngay làm rớt request đang chạy

Hình 2: Đo thật. (1) Health: Check trả SERVING, sau SetServingStatus(NOT_SERVING) trả NOT_SERVING. (2) Reflection: grpcurl list liệt kê các service (gồm svc.Worker) và method svc.Worker.Do mà không cần .proto; health Check qua grpcurl trả SERVING. (3) Graceful shutdown: gọi lúc request 800ms đang chạy — request mới bị từ chối, request đang chạy vẫn hoàn thành.

Kết quả thật:

  • ① Health check đổi trạng thái được: Check(svc.Worker) trả SERVING; sau khi gọi SetServingStatus(NOT_SERVING), Check trả NOT_SERVING. Trong Kubernetes, bạn cấu hình gRPC health probe trỏ vào đây — pod báo NOT_SERVING sẽ bị gỡ khỏi danh sách nhận traffic mà không cần giết.
  • ② Reflection hoạt động với grpcurl: grpcurl -plaintext 127.0.0.1:50122 list trả về grpc.health.v1.Health, grpc.reflection.v1.ServerReflection, và svc.Worker. list svc.Worker trả svc.Worker.Do. Và grpcurl ... Health/Check trả {"status":"SERVING"}. Toàn bộ không cần file .proto — server tự mô tả chính nó. Đây là thứ khiến debug gRPC (vốn khó vì payload nhị phân, bài grpc-02) trở nên dễ thở.
  • ③ Graceful shutdown chờ đúng chỗ: tại t+150ms, một request 800ms đang chạy, mình gọi GracefulStop(). Tại t+250ms, một request mới bị từ chối (error). Nhưng request 800ms đang chạy vẫn hoàn thành bình thường ("xong sau 800ms") rồi server mới tắt. So với Stop() cắt ngay (request 800ms sẽ bị rớt giữa chừng), GracefulStop là thứ cho phép deploy/scale-down mà không một client nào nhận lỗi oan.

Đánh đổi cần cân nhắc

Health check nên phản ánh khả năng phục vụ thật, không chỉ "process còn sống". Trả SERVING chỉ vì tiến trình chưa chết là bẫy: nếu service mất kết nối database mà vẫn báo SERVING, orchestrator gửi traffic tới một pod không làm được việc. Health check tốt nên kiểm cả các phụ thuộc thiết yếu (DB, cache) — nhưng cẩn thận đừng để nó quá nặng (gọi liên tục) hoặc quá nhạy (một blip mạng làm cả pod bị gỡ). Cân bằng giữa "trung thực" và "ổn định".

Reflection tiện cho dev nhưng cân nhắc tắt ở production công khai. Reflection phơi bày toàn bộ API surface của server cho bất kỳ ai gọi được — tiện khi debug nội bộ, nhưng với một endpoint công khai, nó cho kẻ tấn công bản đồ đầy đủ các method. Nhiều nơi bật reflection ở môi trường dev/staging và tắt (hoặc chặn sau auth) ở production internet-facing. Đây là đánh đổi tiện-lợi vs lộ-thông-tin.

Graceful shutdown cần phối hợp với deadline và orchestrator. GracefulStop chờ request đang chạy vô thời hạn — nếu có một request treo (stream dài, bài grpc-03), nó chờ mãi. Thực tế cần: (1) đặt deadline cho mọi RPC (bài grpc-05) để không request nào treo vĩnh viễn, và (2) orchestrator cho một grace period (ví dụ terminationGracePeriodSeconds của K8s) rồi mới SIGKILL. Quy trình deploy đúng: báo NOT_SERVING → chờ LB ngừng gửi → GracefulStop → nếu quá grace period thì buộc tắt.

Ba ý mang về

  1. Health check cho orchestrator biết service sống và sẵn sàng. Đo thật: Check trả SERVING, đổi được sang NOT_SERVING bằng SetServingStatus. K8s/LB dùng nó để gửi hay ngừng traffic — và nên phản ánh khả năng phục vụ thật (gồm phụ thuộc thiết yếu), không chỉ "process còn sống".
  2. Reflection cho gọi thử/debug không cần .proto. Đo thật: grpcurl list liệt kê service và method, health Check trả SERVING — tất cả nhờ reflection.Register. Rất tiện cho dev; cân nhắc tắt ở endpoint công khai vì nó lộ toàn bộ API.
  3. Graceful shutdown giữ cho deploy không rớt request. Đo thật: GracefulStop để request 800ms đang chạy hoàn thành nhưng từ chối request mới ngay. Phối hợp với deadline (để không request nào treo) và grace period của orchestrator cho một quy trình deploy sạch.

Nguồn

Phần sau là bài tổng kết sê-ri: khi nào chọn gRPC thay REST, bảng so sánh gRPC vs REST vs GraphQL, và một checklist những gì cần có trước khi đưa service gRPC lên production.