gRPC基于HTTP/2和Protocol Buffers实现高性能RPC通信,在微服务架构中广泛用于内部服务间调用。后端开发中,gRPC通过Protobuf定义接口契约,自动生成多语言客户端和服务器代码,相比REST+JSON方案在序列化效率和类型安全方面优势明显。Go语言作为gRPC的一等公民语言,grpc-go库提供了完整的实现支持。
Protobuf接口定义与Go代码生成
使用proto3语法定义服务接口和消息类型:
// proto/user_service.proto
syntax = "proto3";
package user.v1;
option go_package = "github.com/example/user-service/api/v1;userv1";
service UserService {
rpc CreateUser(CreateUserRequest) returns (CreateUserResponse);
rpc GetUser(GetUserRequest) returns (GetUserResponse);
rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);
rpc UpdateUser(UpdateUserRequest) returns (UpdateUserResponse);
rpc DeleteUser(DeleteUserRequest) returns (DeleteUserResponse);
}
message User {
int64 id = 1;
string name = 2;
string email = 3;
string phone = 4;
UserStatus status = 5;
int64 created_at = 6;
int64 updated_at = 7;
}
enum UserStatus {
USER_STATUS_UNSPECIFIED = 0;
USER_STATUS_ACTIVE = 1;
USER_STATUS_INACTIVE = 2;
USER_STATUS_SUSPENDED = 3;
}
message CreateUserRequest {
string name = 1;
string email = 2;
string phone = 3;
}
message CreateUserResponse {
User user = 1;
}
message GetUserRequest {
int64 id = 1;
}
message GetUserResponse {
User user = 1;
}
message ListUsersRequest {
int32 page_size = 1;
string page_token = 2;
UserStatus status_filter = 3;
}
message ListUsersResponse {
repeated User users = 1;
string next_page_token = 2;
int32 total = 3;
}
message UpdateUserRequest {
int64 id = 1;
optional string name = 2;
optional string email = 3;
optional string phone = 4;
optional UserStatus status = 5;
}
message UpdateUserResponse {
User user = 1;
}
message DeleteUserRequest {
int64 id = 1;
}
message DeleteUserResponse {
bool success = 1;
}
安装protoc编译器和Go插件,生成代码:
# 安装protoc编译器
apt install -y protobuf-compiler
# 安装Go插件
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
# 生成Go代码
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative proto/user_service.proto
# 生成文件:
# api/v1/user_service.pb.go
# api/v1/user_service_grpc.pb.go
Go gRPC服务器实现与拦截器中间件
package main
import (
"context"
"fmt"
"net"
"log"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/grpc/reflection"
userv1 "github.com/example/user-service/api/v1"
)
type UserServiceServer struct {
userv1.UnimplementedUserServiceServer
}
func (s *UserServiceServer) CreateUser(ctx context.Context, req *userv1.CreateUserRequest) (*userv1.CreateUserResponse, error) {
if req.GetName() == "" {
return nil, status.Error(codes.InvalidArgument, "用户名不能为空")
}
if req.GetEmail() == "" {
return nil, status.Error(codes.InvalidArgument, "邮箱不能为空")
}
user := &userv1.User{
Id: time.Now().Unix(),
Name: req.GetName(),
Email: req.GetEmail(),
Phone: req.GetPhone(),
Status: userv1.UserStatus_USER_STATUS_ACTIVE,
CreatedAt: time.Now().Unix(),
UpdatedAt: time.Now().Unix(),
}
return &userv1.CreateUserResponse{User: user}, nil
}
func (s *UserServiceServer) GetUser(ctx context.Context, req *userv1.GetUserRequest) (*userv1.GetUserResponse, error) {
if req.GetId() <= 0 {
return nil, status.Error(codes.InvalidArgument, "无效的用户ID")
}
user := &userv1.User{
Id: req.GetId(),
Name: "测试用户",
Email: "test@example.com",
Status: userv1.UserStatus_USER_STATUS_ACTIVE,
}
return &userv1.GetUserResponse{User: user}, nil
}
func (s *UserServiceServer) ListUsers(ctx context.Context, req *userv1.ListUsersRequest) (*userv1.ListUsersResponse, error) {
pageSize := int(req.GetPageSize())
if pageSize <= 0 {
pageSize = 10
}
if pageSize > 100 {
pageSize = 100
}
users := make([]*userv1.User, 0, pageSize)
for i := 0; i < pageSize; i++ {
users = append(users, &userv1.User{
Id: int64(i + 1),
Name: fmt.Sprintf("用户%d", i+1),
Email: fmt.Sprintf("user%d@example.com", i+1),
Status: userv1.UserStatus_USER_STATUS_ACTIVE,
})
}
return &userv1.ListUsersResponse{
Users: users,
Total: 1000,
}, nil
}
// 一元拦截器:日志记录
func loggingInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
start := time.Now()
resp, err := handler(ctx, req)
duration := time.Since(start)
log.Printf("gRPC %s | 耗时: %v | 错误: %v", info.FullMethod, duration, err)
return resp, err
}
// 一元拦截器:请求超时控制
func timeoutInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
done := make(chan struct{})
var resp interface{}
var err error
go func() {
resp, err = handler(ctx, req)
close(done)
}()
select {
case <-done:
return resp, err
case <-ctx.Done():
return nil, status.Error(codes.DeadlineExceeded, "请求超时")
}
}
func main() {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("监听失败: %v", err)
}
server := grpc.NewServer(
grpc.ChainUnaryInterceptor(
loggingInterceptor,
timeoutInterceptor,
),
)
userv1.RegisterUserServiceServer(server, &UserServiceServer{})
reflection.Register(server)
log.Println("gRPC服务启动,监听 :50051")
if err := server.Serve(lis); err != nil {
log.Fatalf("服务启动失败: %v", err)
}
}
gRPC客户端调用与连接池管理
package main
import (
"context"
"fmt"
"log"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
userv1 "github.com/example/user-service/api/v1"
)
func main() {
conn, err := grpc.Dial(
"localhost:50051",
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithDefaultCallOptions(
grpc.MaxCallRecvMsgSize(16 * 1024 * 1024),
),
)
if err != nil {
log.Fatalf("连接失败: %v", err)
}
defer conn.Close()
client := userv1.NewUserServiceClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
createResp, err := client.CreateUser(ctx, &userv1.CreateUserRequest{
Name: "张三",
Email: "zhangsan@example.com",
Phone: "13800138000",
})
if err != nil {
log.Fatalf("创建用户失败: %v", err)
}
fmt.Printf("创建用户成功: ID=%d, Name=%s
", createResp.User.Id, createResp.User.Name)
getResp, err := client.GetUser(ctx, &userv1.GetUserRequest{Id: createResp.User.Id})
if err != nil {
log.Fatalf("查询用户失败: %v", err)
}
fmt.Printf("查询用户: %+v
", getResp.User)
listResp, err := client.ListUsers(ctx, &userv1.ListUsersRequest{
PageSize: 5,
})
if err != nil {
log.Fatalf("查询列表失败: %v", err)
}
for i, user := range listResp.Users {
fmt.Printf(" [%d] ID=%d, Name=%s
", i+1, user.Id, user.Name)
}
fmt.Printf("总计: %d
", listResp.Total)
}
gRPC与REST对比及HTTP网关集成
实际项目中gRPC和REST通常共存:内部服务间通信用gRPC获取性能优势,对外API用REST保持兼容性。通过grpc-gateway可以自动生成REST代理:
# 在proto文件中添加google.api.http注解
import "google/api/annotations.proto";
service UserService {
rpc CreateUser(CreateUserRequest) returns (CreateUserResponse) {
option (google.api.http) = {
post: "/api/v1/users"
body: "*"
};
}
rpc GetUser(GetUserRequest) returns (GetUserResponse) {
option (google.api.http) = {
get: "/api/v1/users/{id}"
};
}
rpc ListUsers(ListUsersRequest) returns (ListUsersResponse) {
option (google.api.http) = {
get: "/api/v1/users"
};
}
}
# 生成网关代码
protoc --grpc-gateway_out=. --grpc-gateway_opt=paths=source_relative proto/user_service.proto
# 网关服务器
func main() {
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
mux := runtime.NewServeMux()
err := userv1.RegisterUserServiceHandlerFromEndpoint(
ctx, mux, "localhost:50051",
[]grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())},
)
if err != nil {
log.Fatal(err)
}
log.Println("HTTP网关启动,监听 :8080")
http.ListenAndServe(":8080", mux)
}
Protobuf序列化速度比JSON快3-10倍,二进制体积减少约30-50%。gRPC基于HTTP/2多路复用特性,在长连接场景下比REST的HTTP/1.1+连接池方案吞吐量更高。微服务内部高频调用场景,gRPC的延迟和带宽优势显著。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/grpc-fu-wu-ding-yi-yu-protobuf-xu-lie-hua-zai-go-wei-fu-wu/