一、为什么gRPC放在ALB/Nginx后面会出问题?

1.1 先搞懂gRPC和HTTP/2的关系

可以把gRPC想象成只能走专用高铁线路的快递,而HTTP/2就是这条高铁线路的轨道——没有专用轨道,高铁根本跑不起来。普通的HTTP/1.1就像国道,适合慢件运输,但对高性能要求的微服务通信来说太慢了。gRPC天生绑定HTTP/2,不管是后端服务还是中间转发层,都必须支持这条“高铁轨道”,否则整个链路就会卡住。

1.2 AWS ALB和Nginx的坑点

很多人第一次配置gRPC时,容易忽略中间件的HTTP/2支持:比如AWS ALB默认可能开的是HTTP/1.1,或者配置时没选“HTTP/2协议版本”;Nginx更是常犯的错——监听端口没加http2参数,SSL配置里没加ALPN的h2标识,这些都会导致gRPC和中间件协商失败,明明看起来配置了,却连不上。

二、正确部署的完整步骤(带示例)

2.1 选用的技术栈

技术栈:Go + gRPC后端 + Nginx + AWS Application Load Balancer 选择Go是因为它的gRPC实现极简,示例代码量少,注释清晰,适合不同基础的开发者理解。

2.2 后端gRPC服务代码

首先写gRPC的核心定义和服务实现,先放Proto文件:

// 示例Proto文件,定义gRPC服务的结构
syntax = "proto3";

// 定义包名,对应Go生成代码的包路径
package pb;

// 定义服务类:Greeter,提供问候服务
service Greeter {
  // 定义服务方法:SayHello,接收请求返回问候
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// 请求消息:包含用户名字段
message HelloRequest {
  string name = 1; // 字段编号必须唯一,类型为字符串
}

// 响应消息:包含返回的问候文本
message HelloReply {
  string message = 1;
}

然后编译生成Go代码,用这个Shell命令:

# 编译Proto文件,生成Go的gRPC基础代码,需要提前安装protoc和protoc-gen-grpc-go
protoc --go_out=. --go_opt=paths=source_relative \
       --go-grpc_out=. --go-grpc_opt=paths=source_relative \
       helloworld.proto

接下来写后端gRPC服务代码:

// Go后端gRPC服务代码
package main

import (
	"context"
	"fmt"
	"net"

	// 导入刚才生成的pb包,路径根据实际生成位置调整
	"your-project-path/pb"
	"google.golang.org/grpc"
	"google.golang.org/grpc/reflection"
)

// 定义gRPC服务实例,需要实现Proto里的接口
type greeterServer struct {
	// 嵌入未实现的GreeterServer,自动补全未写的方法
	pb.UnimplementedGreeterServer
}

// 实现Proto里的SayHello方法,处理请求返回结果
func (s *greeterServer) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloReply, error) {
	// 注释:从请求中取名字,拼接问候语
	greeting := fmt.Sprintf("Hello, %s!这是gRPC返回的结果", req.GetName())
	// 注释:返回响应,无错误
	return &pb.HelloReply{Message: greeting}, nil
}

func main() {
	// 注释:监听本地50051端口,这是gRPC服务的默认端口,避免和其他服务冲突
	listener, err := net.Listen("tcp", ":50051")
	if err != nil {
		fmt.Printf("监听端口失败: %v\n", err)
		return
	}

	// 注释:创建gRPC服务器实例
	grpcServer := grpc.NewServer()
	// 注释:把我们的服务实例注册到gRPC服务器上
	pb.RegisterGreeterServer(grpcServer, &greeterServer{})
	// 注释:开启服务反射,方便后续调试工具(比如grpcurl)发现服务,生产环境可关闭
	reflection.Register(grpcServer)

	// 注释:启动gRPC服务,开始接收请求
	fmt.Println("gRPC服务已启动,监听端口50051")
	if err := grpcServer.Serve(listener); err != nil {
		fmt.Printf("启动gRPC服务失败: %v\n", err)
	}
}

2.3 Nginx的正确配置

Nginx是中间转发层,必须开启HTTP/2和ALPN支持,不然无法和gRPC协商:

# Nginx核心配置,支持gRPC over HTTP/2
http {
    # 注释:设置日志格式(可选,方便排错)
    log_format gRPC_log '$remote_addr - $remote_user [$time_local] '
                       '"$request" $status $body_bytes_sent '
                       '"$http_referer" "$http_user_agent" $request_time';

    server {
        # 注释:监听443端口,必须开启http2参数,这是HTTP/2的核心标识
        listen 443 ssl http2;
        # 注释:你的域名,比如grpc.example.com,生产环境要绑定实际域名
        server_name grpc.example.com;
        # 注释:SSL证书,生产环境用Let's Encrypt的免费证书,测试环境用自签名
        ssl_certificate /etc/nginx/ssl/fullchain.pem;
        ssl_certificate_key /etc/nginx/ssl/privkey.pem;

        # 注释:SSL协议和ALPN配置,必须包含h2(HTTP/2的协议标识),否则协商失败
        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_ciphers HIGH:!aNULL:!MD5;
        ssl_prefer_server_ciphers on;
        ssl_alpn_protocols h2 http/1.1;

        # 注释:gRPC请求的核心转发配置,必须用grpc_pass,不是普通的proxy_pass
        location / {
            grpc_pass grpc://localhost:50051; # 转发到本地的gRPC服务端口
            grpc_set_header Host $host; # 注释:透传Host头,让后端服务识别域名
            grpc_set_header X-Real-IP $remote_addr; # 注释:透传真实客户端IP,方便日志排错
            access_log /var/log/nginx/grpc_access.log gRPC_log; # 注释:用自定义日志格式
        }
    }
}

2.4 AWS ALB的配置要点

ALB是最外层的负载均衡,必须和Nginx的HTTP/2配置对应:

  1. 监听配置:新建HTTPS监听,协议版本选HTTP/2(默认可能是HTTP/1.1,必须改)
  2. 目标组配置:协议选HTTPS,端口对应Nginx的443端口,健康检查路径选Nginx的健康页(比如/),别选gRPC的服务路径
  3. 安全组:入站规则允许443端口,出站规则允许访问Nginx的443端口

三、实际踩坑对比

3.1 错误配置的典型问题

很多新手会犯这些错:比如Nginx监听没加http2,SSL的ALPN没写h2;ALB的协议版本选了HTTP/1.1,结果gRPC客户端连不上,报错显示“gRPC status 14: unavailable”,日志里会有“protocol error”或者“connection reset”,用curl测试的话,会提示“HTTP/2 stream error”,因为curl对HTTP/2的支持有限,根本测不出实际问题。

3.2 正确配置的验证方法

用grpcurl测试,命令如下:

# 用grpcurl工具测试gRPC服务,需要提前安装grpcurl
grpcurl -d '{"name": "小王"}' grpc.example.com:443 pb.Greeter/SayHello

如果返回{"message": "Hello, 小王!这是gRPC返回的结果"},说明配置完全正确,链路通了。

四、应用场景和优缺点

4.1 适合的业务场景

  1. 微服务内部通信:多个Go微服务之间的高性能调用,需要低延迟、多路复用,减少连接数
  2. 对外提供gRPC服务:需要绑定域名、SSL证书,结合AWS的WAF、监控等安全服务
  3. 高可用架构:多实例gRPC后端,通过ALB做自动扩缩容,提升服务可用性

4.2 这种架构的优缺点

优点:

  1. 稳定性高:AWS ALB的高可用设计,加上Nginx的转发,不会因为单实例挂掉影响服务
  2. 性能好:HTTP/2的多路复用,比HTTP/1.1少用70%的连接数,延迟更低
  3. 可观测性:结合ALB和Nginx的日志,能清晰追踪请求路径,排错方便 缺点:
  4. 配置复杂:比直接部署gRPC多了两层配置,容易踩HTTP/2的坑
  5. 成本略高:需要付ALB的服务费,还有域名证书的费用
  6. 转发损耗:多一层Nginx转发,比直接暴露gRPC服务多了一点点延迟,小流量场景感知不明显

五、部署时的核心注意事项

  1. 必须双端开启HTTP/2:Nginx和ALB都要确认HTTP/2开启,ALPN里必须有h2,少一个都不行
  2. 生产环境必须用HTTPS:AWS ALB的HTTP/2只支持HTTPS监听,gRPC over HTTP/2不能用明文,不然会被拒绝
  3. gRPC和Nginx的端口对应:Nginx的grpc_pass端口必须和后端gRPC服务的端口一致,不能写错
  4. 健康检查要选对路径:ALB的健康检查不能选gRPC的服务路径,要选Nginx的静态页或者健康接口,否则会一直显示不健康
  5. 测试用gRPC工具:别用curl或者postman,这些工具对HTTP/2的支持不完善,测不出来实际的gRPC问题

六、总结

gRPC在AWS ALB和Nginx后面部署,核心是解决HTTP/2的兼容性问题,本质是让中间件和gRPC的“对话规则”(也就是协议)一致。很多开发者踩坑都是因为忽略了HTTP/2和ALPN的配置细节,只要按照步骤一步步来,从后端服务到中间件再到最外层负载均衡,每一层都开启HTTP/2支持,就能实现稳定高性能的gRPC负载均衡架构。这种架构适合生产环境的微服务通信,能发挥gRPC的低延迟优势,同时结合AWS的服务提升稳定性,虽然配置稍复杂,但长期来看利大于弊。