gRPC是Google开源的高性能RPC框架,基于HTTP/2和Protocol Buffers。在微服务架构中,gRPC在性能和类型安全上完胜REST。本文用Go实现一个完整的gRPC服务。
1. Protocol Buffers定义
先定义服务接口。创建proto/user.proto:
syntax = "proto3";
package user;
option go_package = "github.com/example/grpc-demo/proto/user";
// 用户服务
service UserService {
// 一元RPC
rpc GetUser(GetUserRequest) returns (GetUserResponse);
rpc CreateUser(CreateUserRequest) returns (CreateUserResponse);
// 服务端流式RPC
rpc ListUsers(ListUsersRequest) returns (stream UserInfo);
}
message GetUserRequest {
int64 id = 1;
}
message GetUserResponse {
UserInfo user = 1;
}
message CreateUserRequest {
string name = 1;
string email = 2;
int32 age = 3;
}
message CreateUserResponse {
int64 id = 1;
string message = 2;
}
message ListUsersRequest {
int32 page_size = 1;
}
message UserInfo {
int64 id = 1;
string name = 2;
string email = 3;
int32 age = 4;
}
Proto3语法要点:
service定义服务接口,rpc定义方法stream关键字标记流式响应- 字段编号(
= 1)是序列化标识,一旦确定不能更改
2. protoc代码生成
安装protoc编译器和Go插件:
# 安装protoc
# macOS: brew install protobuf
# Linux: apt install 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
生成代码:
protoc --go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
proto/user.proto
会生成两个文件:
user.pb.go:消息类型的序列化/反序列化代码user_grpc.pb.go:gRPC服务端和客户端的接口代码
3. 实现gRPC服务端
package main
import (
"context"
"fmt"
"log"
"net"
"sync"
"time"
pb "github.com/example/grpc-demo/proto/user"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type userServer struct {
pb.UnimplementedUserServiceServer
mu sync.Mutex
users map[int64]*pb.UserInfo
nextID int64
}
func newUserServer() *userServer {
return &userServer{
users: make(map[int64]*pb.UserInfo),
nextID: 1,
}
}
func (s *userServer) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.GetUserResponse, error) {
s.mu.Lock()
defer s.mu.Unlock()
user, ok := s.users[req.Id]
if !ok {
return nil, status.Errorf(codes.NotFound, "user %d not found", req.Id)
}
return &pb.GetUserResponse{User: user}, nil
}
func (s *userServer) CreateUser(ctx context.Context, req *pb.CreateUserRequest) (*pb.CreateUserResponse, error) {
s.mu.Lock()
defer s.mu.Unlock()
id := s.nextID
s.nextID++
s.users[id] = &pb.UserInfo{
Id: id,
Name: req.Name,
Email: req.Email,
Age: req.Age,
}
return &pb.CreateUserResponse{
Id: id,
Message: fmt.Sprintf("user %s created", req.Name),
}, nil
}
func (s *userServer) ListUsers(req *pb.ListUsersRequest, stream pb.UserService_ListUsersServer) error {
s.mu.Lock()
defer s.mu.Unlock()
for _, user := range s.users {
if err := stream.Send(user); err != nil {
return err
}
time.Sleep(100 * time.Millisecond) // 模拟延迟
}
return nil
}
func main() {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
grpcServer := grpc.NewServer()
pb.RegisterUserServiceServer(grpcServer, newUserServer())
log.Printf("gRPC server listening on :50051")
if err := grpcServer.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
关键点:
- 嵌入
UnimplementedUserServiceServer实现向前兼容 - 使用
status.Errorf返回gRPC标准错误码 - 流式RPC通过
stream.Send()逐个发送消息
4. 实现gRPC客户端
package main
import (
"context"
"fmt"
"io"
"log"
"time"
pb "github.com/example/grpc-demo/proto/user"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
func main() {
conn, err := grpc.Dial("localhost:50051",
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil {
log.Fatalf("failed to connect: %v", err)
}
defer conn.Close()
client := pb.NewUserServiceClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// 创建用户
createResp, err := client.CreateUser(ctx, &pb.CreateUserRequest{
Name: "Alice",
Email: "alice@example.com",
Age: 28,
})
if err != nil {
log.Fatalf("CreateUser failed: %v", err)
}
fmt.Printf("Created: id=%d, msg=%s\n", createResp.Id, createResp.Message)
// 查询用户
getResp, err := client.GetUser(ctx, &pb.GetUserRequest{Id: createResp.Id})
if err != nil {
log.Fatalf("GetUser failed: %v", err)
}
fmt.Printf("Got: %+v\n", getResp.User)
// 流式列表
stream, err := client.ListUsers(ctx, &pb.ListUsersRequest{PageSize: 10})
if err != nil {
log.Fatalf("ListUsers failed: %v", err)
}
for {
user, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
log.Fatalf("stream recv error: %v", err)
}
fmt.Printf("Stream: %+v\n", user)
}
}
5. 流式RPC简介
gRPC支持四种通信模式:
| 模式 | 定义 | 场景 |
|---|---|---|
| 一元 | rpc Method(Req) returns (Resp) |
普通请求-响应 |
| 服务端流 | rpc Method(Req) returns (stream Resp) |
分页数据、实时推送 |
| 客户端流 | rpc Method(stream Req) returns (Resp) |
文件上传、批量写入 |
| 双向流 | rpc Method(stream Req) returns (stream Resp) |
聊天、实时协作 |
// 双向流示例
service ChatService {
rpc Chat(stream ChatMessage) returns (stream ChatMessage);
}
流式RPC基于HTTP/2的多路复用实现,一个TCP连接可以承载多个并发的RPC调用。
小结
gRPC的开发流程是:定义Proto -> 生成代码 -> 实现服务 -> 编写客户端。相比REST,gRPC的优势在于强类型约束、高效的二进制序列化和原生的流式支持。在Go微服务架构中,服务间通信用gRPC基本是标配。