Quay lại blog

gRPC qua proxy trong kiến trúc microservices: hướng dẫn đầy đủ về cấu hình tunneling HTTP/2

Hướng dẫn chi tiết về cách cấu hình lưu lượng gRPC qua các máy chủ proxy trong kiến trúc vi dịch vụ - từ việc tạo đường hầm HTTP/2 đến cân bằng tải và kết thúc TLS.

📅7 tháng 8, 2026
```html

gRPC là một framework RPC hiệu suất cao từ Google, hoạt động trên nền tảng HTTP/2 và đang trở thành tiêu chuẩn de facto cho việc tương tác giữa các dịch vụ. Nhưng ngay khi bạn cố gắng truyền tải lưu lượng gRPC qua proxy, sẽ ngay lập tức xuất hiện vấn đề: hầu hết các máy chủ proxy cổ điển không thể làm việc với HTTP/2 và các kết nối streaming lâu dài. Trong bài viết này, chúng ta sẽ xem xét cách cấu hình gRPC qua proxy một cách chính xác — từ việc chọn kiến trúc đến các cấu hình cụ thể cho Nginx, Envoy và HAProxy.

Tại sao gRPC hoạt động kém với proxy thông thường

Để hiểu vấn đề, cần phải tìm hiểu cách gRPC hoạt động bên trong. Giao thức sử dụng HTTP/2 làm lớp vận chuyển, và điều này hoàn toàn khác biệt so với REST thông thường trên HTTP/1.1. Hầu hết các proxy doanh nghiệp, tường lửa và bộ cân bằng tải được thiết kế trong thời đại HTTP/1.1 và đơn giản là không thể xử lý các luồng HTTP/2 đa hợp.

Dưới đây là những vấn đề cụ thể mà bạn sẽ gặp phải khi cố gắng truyền tải gRPC qua proxy HTTP cổ điển:

  • Giảm cấp xuống HTTP/1.1. Nhiều proxy tự động hạ cấp phiên bản giao thức. gRPC yêu cầu HTTP/2 — nếu không, kết nối sẽ không được thiết lập và khách hàng sẽ nhận được lỗi UNAVAILABLE.
  • Ngắt kết nối lâu dài. gRPC sử dụng tích cực streaming từ máy chủ và hai chiều. Các proxy với thời gian chờ quá nghiêm ngặt (đặc biệt là AWS ELB Classic, một số phiên bản Squid) sẽ ngắt kết nối không truyền dữ liệu lâu hơn 60 giây.
  • Vấn đề với Content-Type. gRPC sử dụng tiêu đề Content-Type: application/grpc. Proxy không biết loại này có thể từ chối yêu cầu hoặc bộ đệm nội dung không chính xác.
  • Trailers (đoạn cuối). gRPC sử dụng HTTP/2 trailers để truyền tải trạng thái hoàn thành cuộc gọi. Proxy HTTP/1.1 không hỗ trợ trailers — thông tin về trạng thái sẽ bị mất.
  • Bộ đệm nội dung. Một số proxy bộ đệm toàn bộ nội dung phản hồi trước khi chuyển đến khách hàng. Đối với các cuộc gọi gRPC streaming, điều này có nghĩa là khách hàng sẽ không nhận được bất kỳ thông điệp nào cho đến khi luồng hoàn tất.

Kết luận chính:

Để gRPC hoạt động qua proxy, cần một proxy hỗ trợ HTTP/2 end-to-end hoặc chế độ tunneling đặc biệt. Proxy HTTP cổ điển mà không có cấu hình bổ sung sẽ không phù hợp.

Tunneling HTTP/2: cách nó hoạt động

Có hai cách tiếp cận khác nhau để proxy lưu lượng gRPC, và điều quan trọng là phải hiểu sự khác biệt giữa chúng để chọn giải pháp phù hợp cho kiến trúc của bạn.

Cách tiếp cận 1: HTTP/2 end-to-end (được khuyến nghị)

Proxy hiểu HTTP/2 và thiết lập kết nối HTTP/2 với cả khách hàng và backend. Nó có thể phân tích các luồng riêng lẻ, áp dụng cân bằng tải ở cấp độ yêu cầu, thêm tiêu đề và thực hiện kết thúc TLS. Đây là tùy chọn chức năng nhất — Envoy, Nginx (bắt đầu từ phiên bản 1.13.10) và các bộ cân bằng tải nhận thức gRPC trong các nhà cung cấp đám mây hoạt động như vậy.

Cách tiếp cận 2: Tunneling TCP (CONNECT)

Proxy không phân tích lưu lượng HTTP/2 mà chỉ tạo một tunneling TCP trong phương thức CONNECT. Khách hàng thiết lập kết nối TLS trực tiếp với backend qua tunneling. Proxy chỉ thấy luồng byte đã được mã hóa. Phương pháp này dễ thiết lập hơn, nhưng sẽ khiến bạn mất đi khả năng cân bằng tải ở cấp độ yêu cầu gRPC và thêm tiêu đề.

Đặc điểm HTTP/2 end-to-end Tunneling TCP CONNECT
Cân bằng theo yêu cầu ✅ Có ❌ Không (chỉ theo TCP)
Kết thúc TLS trên proxy ✅ Có ❌ Không
Thêm tiêu đề ✅ Có ❌ Không
Độ phức tạp của cấu hình Trung bình Thấp
Hỗ trợ streaming ✅ Hoàn toàn ✅ Hoàn toàn
Khả năng quan sát (metrics) ✅ Chi tiết ❌ Chỉ TCP

Cấu hình Nginx như một gRPC-proxy

Nginx hỗ trợ proxy gRPC bắt đầu từ phiên bản 1.13.10 (tháng 2 năm 2018). Để hoạt động, cần có module ngx_http_grpc_module, được bao gồm trong bản dựng tiêu chuẩn. Quan trọng: Nginx hỗ trợ HTTP/2 ở phía khách hàng (frontend), nhưng ở phía backend chỉ sử dụng HTTP/2 cho gRPC — upstream HTTP thông thường hoạt động trên HTTP/1.1.

Cấu hình cơ bản cho gRPC-proxy trên Nginx

server {
    listen 443 ssl http2;
    server_name grpc.example.com;

    # Chứng chỉ TLS
    ssl_certificate     /etc/nginx/ssl/server.crt;
    ssl_certificate_key /etc/nginx/ssl/server.key;

    # Tham số TLS hiện đại
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    location / {
        # Chỉ thị grpc_pass thay vì proxy_pass
        grpc_pass grpc://grpc_backend;

        # Thời gian chờ cho các luồng lâu dài
        grpc_read_timeout  3600s;
        grpc_send_timeout  3600s;
        grpc_connect_timeout 5s;

        # Truyền IP thực của khách hàng
        grpc_set_header X-Real-IP $remote_addr;
        grpc_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

upstream grpc_backend {
    server backend-1:50051;
    server backend-2:50051;
    server backend-3:50051;

    # Keepalive cho các kết nối HTTP/2
    keepalive 32;
}

Lưu ý một số chi tiết quan trọng. Đầu tiên, sử dụng chỉ thị grpc_pass, không phải proxy_pass — đây là hai module khác nhau với hành vi khác nhau. Thứ hai, thời gian chờ grpc_read_timeoutgrpc_send_timeout được đặt thành 3600 giây (1 giờ) — điều này rất quan trọng cho các luồng máy chủ có thể hoạt động lâu mà không truyền dữ liệu. Thứ ba, keepalive 32 trong upstream cho phép tái sử dụng các kết nối HTTP/2 với các backend.

Cấu hình cho gRPC không mã hóa (grpc://)

server {
    listen 80 http2;
    server_name grpc-internal.example.com;

    location / {
        grpc_pass grpc://127.0.0.1:50051;

        # Xử lý lỗi gRPC
        error_page 502 = /error502grpc;
    }

    location = /error502grpc {
        internal;
        default_type application/grpc;
        add_header grpc-status 14;
        add_header content-length 0;
        return 204;
    }
}

Khối error502grpc là một chi tiết quan trọng: khi backend không khả dụng, nó trả về trạng thái gRPC chính xác UNAVAILABLE (14) thay vì mã HTTP 502, mà khách hàng gRPC sẽ không thể xử lý chính xác.

Envoy Proxy: lựa chọn tốt nhất cho gRPC trong microservices

Envoy được tạo ra bởi Lyft đặc biệt cho kiến trúc microservices, và hỗ trợ gRPC trong nó được thực hiện ở mức độ sâu nhất. Nó hiểu Protocol Buffers, có khả năng chuyển đổi gRPC thành REST, thu thập các metrics chi tiết cho mỗi phương thức RPC và là nền tảng cho các giải pháp service mesh — Istio, AWS App Mesh và nhiều hơn nữa. Nếu bạn đang xây dựng một kiến trúc microservices nghiêm túc, Envoy là tiêu chuẩn của ngành.

Cấu hình cơ bản cho Envoy cho gRPC

static_resources:
  listeners:
  - name: grpc_listener
    address:
      socket_address:
        address: 0.0.0.0
        port_value: 8080
    filter_chains:
    - filters:
      - name: envoy.filters.network.http_connection_manager
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
          stat_prefix: grpc_proxy
          codec_type: HTTP2
          route_config:
            name: grpc_routes
            virtual_hosts:
            - name: grpc_services
              domains: ["*"]
              routes:
              - match:
                  prefix: "/com.example.UserService"
                route:
                  cluster: user_service
                  timeout: 30s
                  retry_policy:
                    retry_on: "reset,connect-failure,retriable-status-codes"
                    num_retries: 3
                    retriable_status_codes: [14]
              - match:
                  prefix: "/com.example.OrderService"
                route:
                  cluster: order_service
                  timeout: 60s
          http_filters:
          - name: envoy.filters.http.grpc_stats
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.grpc_stats.v3.FilterConfig
              emit_filter_state: true
          - name: envoy.filters.http.router
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router

  clusters:
  - name: user_service
    connect_timeout: 5s
    type: STRICT_DNS
    lb_policy: ROUND_ROBIN
    http2_protocol_options: {}
    load_assignment:
      cluster_name: user_service
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: user-service
                port_value: 50051

Những lợi ích chính của cấu hình này: định tuyến theo các dịch vụ gRPC (tiền tố đường dẫn tương ứng với tên đầy đủ của dịch vụ theo định dạng package.ServiceName), tự động retry khi có trạng thái UNAVAILABLE và thu thập metrics gRPC thông qua bộ lọc grpc_stats.

Chuyển đổi gRPC-Web trong Envoy

Một trong những tính năng nổi bật của Envoy là bộ chuyển đổi gRPC-Web tích hợp sẵn. Các trình duyệt không hỗ trợ gRPC trực tiếp (do các hạn chế của Fetch API đối với HTTP/2 trailers), vì vậy giao thức gRPC-Web được sử dụng. Envoy có thể tự động chuyển đổi các yêu cầu gRPC-Web từ trình duyệt thành gRPC thông thường cho backend:

http_filters:
- name: envoy.filters.http.grpc_web
  typed_config:
    "@type": type.googleapis.com/envoy.extensions.filters.http.grpc_web.v3.GrpcWeb
- name: envoy.filters.http.cors
  typed_config:
    "@type": type.googleapis.com/envoy.extensions.filters.http.cors.v3.CorsPolicy
- name: envoy.filters.http.router
  typed_config:
    "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router

HAProxy và gRPC: cấu hình với cân bằng tải

HAProxy hỗ trợ gRPC bắt đầu từ phiên bản 1.9.2. Nó hoạt động ở cấp độ TCP hoặc HTTP/2, có khả năng cân bằng lưu lượng gRPC và thực hiện health checks. HAProxy là lựa chọn tốt nếu bạn đã sử dụng nó trong cơ sở hạ tầng và muốn thêm hỗ trợ gRPC mà không cần giới thiệu một thành phần mới.

global
    maxconn 50000
    log stdout format raw local0

defaults
    log global
    timeout connect 5s
    timeout client  3600s
    timeout server  3600s

frontend grpc_frontend
    bind *:443 ssl crt /etc/haproxy/certs/server.pem alpn h2,http/1.1
    mode http
    option http-use-htx
    default_backend grpc_servers

backend grpc_servers
    mode http
    balance leastconn
    option http-use-htx

    # Kiểm tra sức khỏe qua Giao thức Kiểm tra Sức khỏe gRPC
    option httpchk GET /grpc.health.v1.Health/Check
    http-check expect status 200

    server grpc1 10.0.0.1:50051 check ssl verify none
    server grpc2 10.0.0.2:50051 check ssl verify none
    server grpc3 10.0.0.3:50051 check ssl verify none

Lưu ý một số cài đặt quan trọng. alpn h2,http/1.1 trong chỉ thị bind chỉ ra rằng HAProxy chấp nhận cả kết nối HTTP/2 và HTTP/1.1 qua TLS ALPN negotiation. timeout client 3600stimeout server 3600s là các tham số quan trọng cho các luồng gRPC lâu dài. Thuật toán leastconn được ưu tiên hơn roundrobin cho gRPC, vì các kết nối streaming có thể kéo dài và không phân bổ đều tải cho các backend.

Kết thúc TLS và mã hóa đầu cuối cho gRPC

gRPC mặc định yêu cầu sử dụng TLS — đây là một phần của thông số kỹ thuật. Trong thực tế, trong kiến trúc microservices, câu hỏi đặt ra là: nơi nào thực hiện kết thúc TLS và làm thế nào để tổ chức mã hóa giữa các thành phần? Có ba mẫu chính.

Mẫu 1: Kết thúc TLS trên proxy (Edge TLS)

Proxy nhận lưu lượng được mã hóa từ khách hàng, giải mã nó và chuyển đến các backend qua kênh không mã hóa (hoặc với TLS riêng). Đây là cách tiếp cận phổ biến nhất trong các mạng doanh nghiệp. Các backend có thể sử dụng grpc.Insecure() để đơn giản hóa cấu hình.

Mẫu 2: Mã hóa đầu cuối (mTLS)

Mã hóa TLS tương hỗ (mTLS) là tiêu chuẩn cho service mesh. Mỗi dịch vụ có chứng chỉ riêng và trong mỗi kết nối, cả hai bên đều kiểm tra chứng chỉ của nhau. Điều này đảm bảo xác thực các dịch vụ và mã hóa lưu lượng bên trong cụm. Đây là cách mà Istio hoạt động với proxy Envoy sidecar.

# Go: gRPC-server với mTLS
import (
    "crypto/tls"
    "crypto/x509"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"
)

func createMTLSCredentials() credentials.TransportCredentials {
    cert, _ := tls.LoadX509KeyPair("server.crt", "server.key")
    
    caCert, _ := os.ReadFile("ca.crt")
    caCertPool := x509.NewCertPool()
    caCertPool.AppendCertsFromPEM(caCert)
    
    tlsConfig := &tls.Config{
        Certificates: []tls.Certificate{cert},
        ClientAuth:   tls.RequireAndVerifyClientCert,
        ClientCAs:    caCertPool,
    }
    
    return credentials.NewTLS(tlsConfig)
}

// Tạo server với mTLS
creds := createMTLSCredentials()
server := grpc.NewServer(grpc.Creds(creds))

Mẫu 3: TLS Passthrough

Proxy hoạt động ở cấp độ TCP và không giải mã lưu lượng — chỉ đơn giản là chuyển tiếp các byte đã được mã hóa đến backend. Đây là tùy chọn đơn giản nhất từ góc độ proxy, nhưng nó sẽ mất khả năng phân tích lưu lượng gRPC, thêm tiêu đề hoặc thực hiện cân bằng tải ở cấp độ yêu cầu.

Cân bằng tải cho các luồng gRPC: đặc điểm và giải pháp

Cân bằng tải cho gRPC là một nhiệm vụ không đơn giản, và đây là lý do. Trong HTTP/1.1, mỗi yêu cầu là một kết nối TCP riêng biệt (hoặc kết nối từ pool), và bộ cân bằng tải dễ dàng phân phối các yêu cầu cho các backend. Trong HTTP/2, một kết nối TCP đa hợp nhiều luồng — nếu bộ cân bằng tải hoạt động ở cấp độ TCP, tất cả các luồng của một kết nối sẽ được gửi đến một backend duy nhất.

Đối với gRPC, điều này có nghĩa là: nếu khách hàng đã thiết lập một kết nối HTTP/2 và gửi qua đó 100 yêu cầu RPC, khi cân bằng tải TCP, tất cả 100 yêu cầu sẽ được xử lý bởi một instance backend. Các backend khác sẽ không hoạt động. Giải pháp là cân bằng tải ở cấp độ luồng HTTP/2 (cân bằng tải L7).

Thuật toán cân bằng tải cho gRPC

Thuật toán Phù hợp cho gRPC Nhận xét
Round Robin ✅ Có Tốt cho các RPC đơn với thời gian xử lý tương tự nhau
Least Connection ✅ Lựa chọn tốt nhất Xem xét các luồng đang hoạt động, phân phối tải đồng đều
Random ⚠️ Có điều kiện Hoạt động tốt khi có nhiều yêu cầu, không đồng đều khi ít
IP Hash ❌ Kém Gắn kết khách hàng với một backend, không có ý nghĩa trong L7
Pick First (gRPC tích hợp sẵn) ❌ Không cho sản xuất Tất cả các yêu cầu đều đến máy chủ đầu tiên có sẵn

Cân bằng tải phía khách hàng trong gRPC

gRPC hỗ trợ cân bằng tải phía khách hàng — khách hàng tự quyết định gửi yêu cầu đến máy chủ nào. Điều này giúp vượt qua vấn đề TCP multiplexing. Để phát hiện dịch vụ, sử dụng DNS với nhiều bản ghi A hoặc các resolver đặc biệt (Consul, etcd). Ví dụ cấu hình cân bằng tải phía khách hàng trong Go:

import (
    "google.golang.org/grpc"
    "google.golang.org/grpc/balancer/roundrobin"
)

// Khách hàng với cân bằng tải round-robin qua DNS
conn, err := grpc.Dial(
    "dns:///grpc-service.internal:50051",
    grpc.WithDefaultServiceConfig(
        `{"loadBalancingConfig": [{"round_robin":{}}]}`
    ),
    grpc.WithTransportCredentials(creds),
)

// Khách hàng với least-connection (có sẵn trong gRPC >= 1.58)
conn, err := grpc.Dial(
    "dns:///grpc-service.internal:50051",
    grpc.WithDefaultServiceConfig(
        `{"loadBalancingConfig": [{"least_request":{}}]}`
    ),
    grpc.WithTransportCredentials(creds),
)

Proxy bên ngoài cho gRPC: khi nào và tại sao cần thiết trong microservices

Ngoài các thành phần proxy nội bộ (Nginx, Envoy, HAProxy), trong kiến trúc microservices đôi khi cần sử dụng các máy chủ proxy bên ngoài — ví dụ, để định tuyến lưu lượng qua các khu vực cụ thể, vượt qua các hạn chế mạng hoặc cách ly các kết nối ra ngoài. Hãy xem xét các kịch bản chính.

Kịch bản 1: Microservices phân bố địa lý

Nếu các microservices của bạn nằm ở các khu vực khác nhau (ví dụ, một phần ở Châu Âu, một phần ở Hoa Kỳ), và bạn cần kiểm soát địa chỉ IP nào được sử dụng để thiết lập các kết nối gRPC giữa các khu vực, các proxy bên ngoài có thể giúp tổ chức định tuyến dự đoán. Các proxy trung tâm dữ liệu là lựa chọn phù hợp — chúng cung cấp địa chỉ IP ổn định và tốc độ kết nối cao, điều này rất quan trọng cho gRPC với độ nhạy cảm với độ trễ.

Kịch bản 2: Cách ly lưu lượng ra ngoài

Trong một số môi trường doanh nghiệp, toàn bộ lưu lượng ra ngoài phải đi qua proxy doanh nghiệp. Đối với các khách hàng gRPC cần truy cập các dịch vụ gRPC bên ngoài (ví dụ, Google Cloud APIs sử dụng gRPC), điều này tạo ra khó khăn. Giải pháp là thiết lập tunneling CONNECT qua proxy doanh nghiệp.

Ví dụ về cấu hình khách hàng gRPC để hoạt động qua proxy HTTP CONNECT trong Go:

import (
    "net"
    "net/http"
    "golang.org/x/net/proxy"
    "google.golang.org/grpc"
)

// Sử dụng proxy SOCKS5 cho gRPC
proxyDialer, _ := proxy.SOCKS5(
    "tcp",
    "proxy.example.com:1080",
    &proxy.Auth{User: "user", Password: "pass"},
    proxy.Direct,
)

conn, err := grpc.Dial(
    "grpc-service.example.com:443",
    grpc.WithContextDialer(func(ctx context.Context, addr string) (net.Conn, error) {
        return proxyDialer.Dial("tcp", addr)
    }),
    grpc.WithTransportCredentials(creds),
)

Kịch bản 3: Kiểm tra và phát triển

Khi phát triển microservices, thường cần kiểm tra hành vi của các dịch vụ từ các môi trường mạng khác nhau — kiểm tra cách dịch vụ phản hồi khi có độ trễ cao, hoặc kiểm tra logic phụ thuộc vào địa lý. Đối với những nhiệm vụ như vậy, proxy dân cư rất tiện lợi, cho phép mô phỏng các yêu cầu từ các khu vực cụ thể với địa chỉ IP thực tế của người dùng tại nhà.

Các lỗi thường gặp khi cấu hình gRPC qua proxy và cách khắc phục

Hãy xem xét những vấn đề phổ biến nhất mà các nhà phát triển gặp phải khi cấu hình gRPC qua proxy, cùng với các cách giải quyết cụ thể.

Lỗi 1: "transport: received the unexpected content-type"

Triệu chứng:

Khách hàng nhận được lỗi transport: received the unexpected content-type "text/html; charset=utf-8"

Nguyên nhân: Proxy đã trả về một trang HTML với lỗi (ví dụ, 502 Bad Gateway) thay vì phản hồi gRPC. Khách hàng gRPC không thể xử lý HTML và đưa ra lỗi này.
Giải pháp: Đảm bảo rằng backend có sẵn. Thêm xử lý lỗi gRPC ở cấp độ proxy (như đã chỉ ra trong ví dụ Nginx ở trên với khối error502grpc).

Lỗi 2: Kết nối bị ngắt sau 60-120 giây

Triệu chứng:

Các kết nối gRPC streaming bị đóng đột ngột với lỗi UNAVAILABLE: transport is closing sau khoảng thời gian tương tự nhau.

Nguyên nhân: Proxy đóng các kết nối idle theo thời gian chờ. Các giá trị cổ điển: AWS ELB — 60 giây, Nginx mặc định — 60 giây.
Giải pháp 1: Tăng thời gian chờ trên proxy (như đã chỉ ra trong các ví dụ ở trên).
Giải pháp 2: Cấu hình keepalive ở phía khách hàng gRPC:

import "google.golang.org/grpc/keepalive"

kaParams := keepalive.ClientParameters{
    Time:                30 * time.Second, // Ping mỗi 30 giây
    Timeout:             10 * time.Second, // Chờ phản hồi 10 giây
    PermitWithoutStream: true,             // Ping ngay cả khi không có RPC hoạt động
}

conn, err := grpc.Dial(
    "grpc-service:50051",
    grpc.WithKeepaliveParams(kaParams),
    grpc.WithTransportCredentials(creds),
)

Lỗi 3: HTTP/2 không đồng bộ (ALPN failure)

Triệu chứng:

Lỗi transport: failed to dial: context deadline exceeded hoặc no application protocol

Nguyên nhân: Proxy hoặc thiết bị trung gian không hỗ trợ ALPN (Application-Layer Protocol Negotiation) hoặc không có h2 trong danh sách các giao thức được hỗ trợ.
Giải pháp: Đảm bảo rằng trong cấu hình TLS của proxy, giao thức h2 được chỉ định rõ ràng: alpn h2,http/1.1 (HAProxy) hoặc listen 443 ssl http2 (Nginx).

Lỗi 4: Streaming bị treo — dữ liệu không đến

Triệu chứng:

Streaming từ máy chủ hoạt động trên backend (thấy trong log), nhưng khách hàng không nhận được thông điệp cho đến khi luồng hoàn tất.

Nguyên nhân: Proxy bộ đệm phản hồi và chỉ gửi đến khách hàng sau khi hoàn tất. Đây là vấn đề điển hình cho các proxy được cấu hình với proxy_buffering on.
Giải pháp cho Nginx:

location / {
    grpc_pass grpc://backend;
    
    # Tắt bộ đệm cho các luồng gRPC
    grpc_buffer_size 0;
    
    # Hoặc cho proxy_pass thông thường:
    proxy_buffering off;
    proxy_cache off;
}

Danh sách kiểm tra chẩn đoán gRPC qua proxy

✅ Danh sách kiểm tra: chẩn đoán gRPC-proxy

  • Proxy hỗ trợ HTTP/2 (kiểm tra qua curl --http2 -v)
  • ALPN h2 được bật trong cấu hình TLS của proxy
  • Thời gian chờ của khách hàng và máy chủ được thiết lập ít nhất 300 giây
  • Bộ đệm phản hồi bị tắt cho các endpoint gRPC
  • Cấu hình keepalive gRPC ở phía khách hàng (Thời gian: 30s, Thời gian chờ: 10s)
  • Lỗi backend được trả về dưới dạng trạng thái gRPC, không phải mã HTTP
  • Kiểm tra sức khỏe sử dụng Giao thức Kiểm tra Sức khỏe gRPC
  • Cân bằng tải hoạt động ở L7 (luồng HTTP/2), không phải L4 (TCP)

Kết luận

Cấu hình gRPC qua proxy yêu cầu hiểu những khác biệt chính giữa HTTP/2 và HTTP/1.1: đa hợp luồng, kết nối lâu dài, HTTP/2 trailers và đồng bộ ALPN. Các proxy HTTP cổ điển mà không có cấu hình đặc biệt sẽ không hoạt động với gRPC — cần một proxy L7 hỗ trợ HTTP/2 (Nginx 1.13.10+, Envoy, HAProxy 1.9.2+) hoặc tunneling TCP CONNECT.

Đối với môi trường sản xuất, Envoy được khuyến nghị — nó được tạo ra đặc biệt cho kiến trúc microservices, có hỗ trợ gRPC bản địa, có khả năng thu thập các metrics chi tiết cho mỗi phương thức RPC và là nền tảng cho hầu hết các giải pháp service mesh. Nginx là lựa chọn tốt nếu bạn đã sử dụng nó như một API Gateway và muốn thêm hỗ trợ gRPC mà không cần giới thiệu một thành phần mới. HAProxy sẽ phù hợp nếu hiệu suất là yếu tố quan trọng và bạn đã quen thuộc với cấu hình của nó.

Bất kể proxy nào được chọn, ba quy tắc vẫn không thay đổi: tăng thời gian chờ cho các luồng lâu dài, tắt bộ đệm phản hồi và cấu hình keepalive ở phía khách hàng. Ba cài đặt này giải quyết 80% các vấn đề với gRPC qua proxy.

Nếu trong kiến trúc của bạn, các dịch vụ gRPC cần tương tác qua các mạng bên ngoài hoặc bạn cần định tuyến lưu lượng qua các khu vực cụ thể, hãy xem xét proxy trung tâm dữ liệu — chúng cung cấp địa chỉ IP ổn định, độ trễ thấp và băng thông cao, điều này đặc biệt quan trọng cho gRPC với giao thức nhị phân và độ nhạy cảm với độ trễ.

```