提供用于 https://github.com/zly-app/zapp 的服务
客户端说明转到 这里
对于 AI 助手: 在协助开发本 gRPC 项目时,请优先阅读
AI_REFERENCE.md文档,无需扫描整个代码仓库。该文档已包含完整开发参考:核心 API、配置项、服务端/客户端/网关快速开始、负载均衡器、注册发现机制、链路追踪、proto 编译命令、关键源码索引等。
仅在以下情况需要深入阅读源码:
- 调试具体问题时
- 实现高级定制功能时
- 文档信息不足以解决当前问题时
- 安装 protoc 编译器
从 https://github.com/protocolbuffers/protobuf/releases 下载 protoc 编译器,解压 protoc 执行文件到 ${GOPATH}/bin/
- 安装 ProtoBuffer Golang 支持
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest- 安装 ProtoBuffer GRpc Golang 支持。文档
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest- 获取依赖 proto 文件
linux
mkdir -p ${GOPATH}/protos/zly-app && cd ${GOPATH}/protos/zly-app
git clone --depth=1 https://github.com/zly-app/grpc.gitGoland 在 设置 -> 语言和框架 -> Protocol Buffers/协议缓冲区 的 Import Paths, 取消勾选 Configure automatically/自动配置.
将 ${GOPATH}/protos/zly-app/grpc/protos 添加到 IDE 的 proto 导入路径.
win cmd
if not exist %GOPATH%\protos\zly-app mkdir %GOPATH%\protos\zly-app
cd /d %GOPATH%\protos\zly-app
git clone --depth=1 https://github.com/zly-app/grpc.gitwin PowerShell
if not exist $env:GOPATH\protos\zly-app mkdir $env:GOPATH\protos\zly-app
cd $env:GOPATH\protos\zly-app
git clone --depth=1 https://github.com/zly-app/grpc.gitGoland 在 设置 -> 语言和框架 -> Protocol Buffers/协议缓冲区 的 Import Paths, 取消勾选 Configure automatically/自动配置.
将 %GOPATH%\protos\zly-app\grpc\protos 添加到 IDE 的 proto 导入路径.
官方 proto 文件参考 https://github.com/googleapis/googleapis/
创建工程
mkdir grpc-test && cd grpc-test && go mod init grpc-test
准备 pb/hello/hello.proto 文件
syntax = 'proto3';
package hello; // 决定 proto 引用路径和 rpc 路由
option go_package = "grpc-test/pb/hello"; // 用于对 golang 包管理的定位
service helloService{
rpc Say(SayReq) returns (SayResp);
}
message SayReq{
string msg = 1;
}
message SayResp{
string msg = 1;
}
编译 proto
protoc \
--go_out . --go_opt paths=source_relative \
--go-grpc_out . --go-grpc_opt paths=source_relative \
pb/hello/hello.proto
服务端 server/main.go
package main
import (
"context"
"github.com/zly-app/zapp"
"github.com/zly-app/zapp/log"
"github.com/zly-app/grpc"
"grpc-test/pb/hello"
)
var _ hello.HelloServiceServer = (*HelloService)(nil)
type HelloService struct {
hello.UnimplementedHelloServiceServer
}
func (h *HelloService) Say(ctx context.Context, req *hello.SayReq) (*hello.SayResp, error) {
log.Info(ctx, "收到请求", req.Msg)
return &hello.SayResp{Msg: req.GetMsg() + "world"}, nil
}
func main() {
app := zapp.NewApp("grpc-server",
grpc.WithService(), // 启用 grpc 服务
)
// 注册 rpc 服务
hello.RegisterHelloServiceServer(grpc.Server("hello"), new(HelloService))
// 同名服务复用: 同一个 serverName 的多个 gRPC 服务会自动复用同一个 GRpcServer 实例
// 正确做法: 只调用一次 grpc.Server() 获取注册器,然后注册多个服务
// registrar := grpc.Server("hello")
// hello.RegisterHelloServiceServer(registrar, new(HelloService))
// hello2.RegisterHello2ServiceServer(registrar, new(Hello2Service))
// 注意: 对同一个 serverName 多次调用 grpc.Server() 会 panic
app.Run()
}运行服务端
go mod tidy && go run server/main.go服务端配置文件是可选的
添加配置文件 configs/default.yaml.
services:
grpc:
hello:
Bind: :3000 # bind 地址
HeartbeatTime: 20 # 心跳时间,单位秒
ReqDataValidate: true # 是否启用请求数据校验
ReqDataValidateAllField: false # 是否对请求数据校验所有字段。如果设为 true, 会对所有字段校验并返回所有的错误。如果设为 false, 校验错误会立即返回.
SendDetailedErrorInProduction: false # 在生产环境发送详细的错误到客户端。如果设为 false, 在生产环境且错误状态码为 Unknown, 则会返回 service internal error 给客户端.
TLSCertFile: '' # tls 公钥文件路径
TLSKeyFile: '' # tls 私钥文件路径
RegistryAddress: 'static' # 注册地址,默认 static, 参考 https://github.com/zly-app/grpc/tree/master/registry
PublishName: '' # 公告名,在注册中心中定义的名称,如果为空则自动设为当前 grpc 服务名
PublishAddress: '' # 公告地址,在注册中心中定义的地址,客户端会根据这个地址连接服务端,如果为空则自动设为 实例 ip:BindPort
PublishWeight: 100 # 公告权重,默认 100我们使用 protoc-gen-validate 作为数据校验工具
go install github.com/envoyproxy/protoc-gen-validate@latest添加 pb/a.proto 示例文件
syntax = "proto3";
package pb; // 决定 proto 引用路径和 rpc 路由
option go_package = "grpc-test/pb"; // 用于对 golang 包管理的定位
import "validate/validate.proto";
message A {
// 字符串
string a = 1 [(validate.rules).string = {
ignore_empty: true, // 可以是空字符串
// len: 11, // 长度必须为 11
max_len: 20, // rune 长度最大为 20
min_len: 5, // rune 长度最小为 5
prefix: 'hello', // 前缀
suffix: 'world', // 后缀
contains: 'hello world' // 包含字符串
}];
// 数字
int32 b = 2 [(validate.rules).int32 = {
ignore_empty: true, // 可以是 0
// lte: 10, // 必须小等于 10
lt: 10, // 必须小于 10
gt: 3, // 必须大于 3
// gte: 3, // 必须大于等于 3
// const: 5, // 必须等于 5
}];
// 布尔型
bool c = 3[(validate.rules).bool = {
const: true, // 必须为 true
}];
// 数组
repeated string d = 4[(validate.rules).repeated = {
max_items: 3, // 最多包含 3 个数据
min_items: 2, // 最多包含 2 个数据
unique: true, // 内部数据不允许重复
items: {
string: {
// ... string 选项
}
}
}];
}编译 proto
linux
protoc \
-I . \
-I ${GOPATH}/protos/zly-app/grpc/protos \
--go_out . --go_opt paths=source_relative \
--validate_out "lang=go:." --validate_opt paths=source_relative \
pb/a.protowin cmd
protoc ^
-I . ^
-I %GOPATH%/protos/zly-app/grpc/protos ^
--go_out . --go_opt paths=source_relative ^
--validate_out "lang=go:." --validate_opt paths=source_relative ^
pb/a.protowin PowerShell
protoc `
-I . `
-I $env:GOPATH/protos/zly-app/grpc/protos `
--go_out . --go_opt paths=source_relative `
--validate_out "lang=go:." --validate_opt paths=source_relative `
pb/a.proto创建客户端文件 client/main.go
package main
import (
"context"
"github.com/zly-app/zapp"
"github.com/zly-app/grpc"
"grpc-test/pb/hello"
)
func main() {
app := zapp.NewApp("grpc-client")
defer app.Exit()
helloClient := hello.NewHelloServiceClient(grpc.GetClientConn("hello")) // 获取客户端
// 调用
resp, err := helloClient.Say(context.Background(), &hello.SayReq{Msg: "hello"})
if err != nil {
app.Fatal(resp)
}
app.Info("收到结果", resp.GetMsg())
}运行客户端
go mod tidy && go run server/main.go更多客户端说明参考 这里
使用 grpc-gateway 作为 http 网关
安装 grpc-gateway
go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@latest
go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2@latest修改 pb/hello/hello.proto 文件
+ import "google/api/annotations.proto"; // 添加导入
service helloService{
- rpc Say(SayReq) returns (SayResp);
+ rpc Say(SayReq) returns (SayResp){ // 修改 rpc 接口
+ option (google.api.http) = {
+ post: "/hello/say"
+ body: "*"
+ };
+ };
}
完整文件如下
syntax = 'proto3';
package hello; // 决定 proto 引用路径和 rpc 路由
option go_package = "grpc-test/pb/hello"; // 用于对 golang 包管理的定位
import "google/api/annotations.proto"; // 添加导入
service helloService{
rpc Say(SayReq) returns (SayResp){// 修改 rpc 接口
option (google.api.http) = {
post: "/hello/say"
body: "*"
};
};
}
message SayReq{
string msg = 1;
}
message SayResp{
string msg = 1;
}
重新编译 proto
linux
protoc \
-I . \
-I ${GOPATH}/protos/zly-app/grpc/protos \
--go_out . --go_opt paths=source_relative \
--go-grpc_out . --go-grpc_opt paths=source_relative \
--grpc-gateway_out . --grpc-gateway_opt paths=source_relative \
pb/hello/hello.protowin cmd
protoc ^
-I . ^
-I %GOPATH%/protos/zly-app/grpc/protos ^
--go_out . --go_opt paths=source_relative ^
--go-grpc_out . --go-grpc_opt paths=source_relative ^
--grpc-gateway_out . --grpc-gateway_opt paths=source_relative ^
pb/hello/hello.protowin PowerShell
protoc `
-I . `
-I $env:GOPATH/protos/zly-app/grpc/protos `
--go_out . --go_opt paths=source_relative `
--go-grpc_out . --go-grpc_opt paths=source_relative `
--grpc-gateway_out . --grpc-gateway_opt paths=source_relative `
pb/hello/hello.proto可以看到新出现了一个 pb/hello/hello.pb.gw.go 文件
网关服务端 server/main.go
package main
import (
"context"
"github.com/zly-app/zapp"
"github.com/zly-app/grpc"
"github.com/zly-app/grpc/example/pb/hello"
)
func main() {
app := zapp.NewApp("grpc-gateway",
grpc.WithGatewayService(), // 启用网关服务
)
helloClient := hello.NewHelloServiceClient(grpc.GetGatewayClientConn("hello")) // 获取客户端。网关会通过这个 client 对 service 发起调用
_ = hello.RegisterHelloServiceHandlerClient(context.Background(), grpc.GetGatewayMux(), helloClient) // 注册网关
app.Run()
}运行网关服务端
go mod tidy && go run gateway/main.go现在可以通过 curl 访问了
curl -X POST http://localhost:8080/hello/say -d '{"msg": "hello"}'
注意。这里请求和返回的 json 字段名完全等于 proto 中定义的 message 字段名,与 json 标签无关
网关配置是可选的
添加配置文件 configs/default.yaml.
services:
grpc-gateway:
Bind: :8080 # bind 地址
CloseWait: 3 # 关闭前等待处理时间,单位秒
CorsAllowAll: true # 允许全局跨域
Route: # 路由配置生成 swagger
linux
protoc \
-I . \
-I ${GOPATH}/protos/zly-app/grpc/protos \
--openapiv2_out=enums_as_ints=true:. \
--go_out . --go_opt paths=source_relative \
pb/hello/hello.protowin cmd
protoc ^
-I . ^
-I %GOPATH%/protos/zly-app/grpc/protos ^
--openapiv2_out=enums_as_ints=true:. ^
--go_out . --go_opt paths=source_relative ^
pb/hello/hello.protowin PowerShell
protoc `
-I . `
-I $env:GOPATH/protos/zly-app/grpc/protos `
--openapiv2_out=enums_as_ints=true:. `
--go_out . --go_opt paths=source_relative `
pb/hello/hello.proto转到 这里
# makefile
pb:
protoc \
-I . \
-I ${GOPATH}/protos/zly-app/grpc/protos \
--go_out . --go_opt paths=source_relative \
--go-grpc_out . --go-grpc_opt paths=source_relative \
--grpc-gateway_out . --grpc-gateway_opt paths=source_relative \
--validate_out "lang=go:." --validate_opt paths=source_relative \
--openapiv2_out . \
./*.proto#makefile.ps1
protoc `
-I . `
-I $env:GOPATH/protos/zly-app/grpc/protos `
--go_out . --go_opt paths=source_relative `
--go-grpc_out . --go-grpc_opt paths=source_relative `
--grpc-gateway_out . --grpc-gateway_opt paths=source_relative `
--validate_out "lang=go:." --validate_opt paths=source_relative `
--openapiv2_out . `
./*.proto