gRPC 与 Protobuf 实战指南

news2026/5/4 23:11:57
引言gRPC 是 Google 开源的高性能 RPC 框架而 ProtobufProtocol Buffers则是其默认的序列化协议。两者结合带来了高性能、跨语言、契约优先的现代微服务通信方案。传统的 REST API 使用 JSON 或 XML 作为数据格式存在以下问题体积大JSON 文本格式冗余解析慢需要解析字符串无强类型字段变化不易发现代码生成弱缺乏好的工具链gRPC Protobuf 通过二进制格式和代码生成很好地解决了这些问题。本文将深入探讨 Protobuf 语法、gRPC 服务开发、以及生产环境中的最佳实践。一、Protobuf 语法详解1.1 消息定义基础Protobuf 的核心是.proto文件它定义消息的结构syntax proto3; // 指定 protobuf 版本 ​ package user; // 包名用于避免命名冲突 ​ // 定义用户消息 message User { string name 1; // 字段名 字段编号 int32 id 2; // 编号必须唯一用于二进制编码 string email 3; bool active 4; int64 created_at 5; }字段编号规则1-15常用字段使用一个字节编码16-2047非常用字段使用两个字节编码19000-19999保留编号系统使用建议将 1-15 分配给最常用的字段1.2 标量数据类型Protobuf 支持丰富的数据类型message TypesDemo { // 整数类型 int32 var_int32 1; // 变长有符号整数 int64 var_int64 2; // 变长有符号长整数 uint32 var_uint32 3; // 变长无符号整数 uint64 var_uint64 4; // 变长无符号长整数 sint32 var_sint32 5; // 变长有符号整数负数效率更高 sint64 var_sint64 6; // 变长有符号长整数 ​ // 固定长度类型 fixed32 fixed32 7; // 固定4字节无符号整数 fixed64 fixed64 8; // 固定8字节无符号整数 sfixed32 sfixed32 9; // 固定4字节有符号整数 sfixed64 sfixed64 10; // 固定8字节有符号整数 ​ // 浮点数类型 float float_val 11; // 32位浮点数 double double_val 12; // 64位浮点数 ​ // 布尔和字符串 bool bool_val 13; string string_val 14; bytes bytes_val 15; }1.3 嵌套与组合// 嵌套消息 message User { message Address { string street 1; string city 2; string country 3; } ​ string name 1; Address address 2; // 使用嵌套消息 repeated Phone phones 3; // 数组/列表 } ​ // 枚举类型 message Order { enum Status { UNKNOWN 0; // 枚举必须从 0 开始 PENDING 1; PAID 2; SHIPPED 3; DELIVERED 4; CANCELLED 5; } ​ string order_id 1; Status status 2; User buyer 3; repeated Item items 4; }1.4 Map 类型message Product { // 键值对映射 mapstring, string attributes 1; mapint64, User related_users 2; }1.5 oneof 联合类型当一个字段可以是多种类型之一时使用oneofmessage Response { oneof result { User user 1; Order order 2; string error 3; } int64 timestamp 4; }二、proto 文件编译与代码生成2.1 安装 protocWindows 环境安装 protoc# 下载 protoc从 GitHub releases curl -LO https://github.com/protocolbuffers/protobuf/releases/download/v25.1/protoc-25.1-win64.zip unzip protoc-25.1-win64.zip -d $HOME/.local ​ # 设置 PATH export PATH$PATH:$HOME/.local/bin ​ # 验证安装 protoc --version2.2 安装 Go 插件# 安装 protoc-gen-go用于生成 .pb.go 文件 go install google.golang.org/protobuf/cmd/protoc-gen-golatest ​ # 安装 protoc-gen-go-grpc用于生成 gRPC 服务代码 go install google.golang.org/grpc/cmd/protoc-gen-go-grpclatest ​ # 设置 PATH export PATH$PATH:$(go env GOPATH)/bin2.3 编译 proto 文件目录结构project/ ├── proto/ │ ├── user.proto │ └── order.proto ├── generated/ └── main.go编译命令protoc \ --go_outgenerated \ --go_optpathssource_relative \ --go-grpc_outgenerated \ --go-grpc_optpathssource_relative \ proto/*.proto生成的代码结构// user.pb.go - 消息类型定义 type User struct { Name string Id int32 Email string Active bool CreatedAt int64 // ... 序列化/反序列化方法 } ​ // user_grpc.pb.go - gRPC 服务定义 type UserServiceClient interface { GetUser(ctx context.Context, in *GetUserRequest, opts ...grpc.CallOption) (*User, error) ListUsers(ctx context.Context, in *ListUsersRequest, opts ...grpc.CallOption) (*ListUsersResponse, error) // ... }2.4 完整 proto 示例创建proto/user.protosyntax proto3; ​ package user; ​ option go_package github.com/example/project/gen/user;user; ​ import google/protobuf/timestamp.proto; ​ // 用户服务定义 service UserService { // 获取单个用户 rpc GetUser(GetUserRequest) returns (User); ​ // 列出用户支持分页 rpc ListUsers(ListUsersRequest) returns (ListUsersResponse); ​ // 创建用户 rpc CreateUser(CreateUserRequest) returns (User); ​ // 更新用户 rpc UpdateUser(UpdateUserRequest) returns (User); ​ // 删除用户 rpc DeleteUser(DeleteUserRequest) returns (Empty); ​ // 双向流示例批量操作 rpc BatchProcessUsers(stream User) returns (stream OperationResult); } ​ // 用户消息定义 message User { int32 id 1; string name 2; string email 3; bool active 4; google.protobuf.Timestamp created_at 5; repeated string roles 6; } ​ // 请求消息定义 message GetUserRequest { int32 id 1; } ​ message ListUsersRequest { int32 page 1; int32 page_size 2; string search 3; } ​ message ListUsersResponse { repeated User users 1; int32 total 2; int32 page 3; int32 page_size 4; } ​ message CreateUserRequest { string name 1; string email 2; repeated string roles 3; } ​ message UpdateUserRequest { int32 id 1; string name 2; string email 3; bool active 4; } ​ message DeleteUserRequest { int32 id 1; } ​ // 通用响应 message Empty {} ​ // 批量操作结果 message OperationResult { int32 id 1; bool success 2; string message 3; }三、gRPC 服务端开发3.1 项目结构grpc-demo/ ├── proto/ │ └── user.proto ├── gen/ │ ├── user.pb.go │ └── user_grpc.pb.go ├── server/ │ └── main.go ├── client/ │ └── main.go └── go.mod3.2 服务端实现package main ​ import ( context fmt io log net ​ google.golang.org/grpc google.golang.org/grpc/codes google.golang.org/grpc/metadata google.golang.org/grpc/status ​ pb github.com/example/grpc-demo/gen/user google.golang.org/protobuf/types/known/timestamppb ) ​ type UserServer struct { pb.UnimplementedUserServiceServer // 数据库模拟 users map[int32]*pb.User nextID int32 } ​ func NewUserServer() *UserServer { return UserServer{ users: make(map[int32]*pb.User), nextID: 1, } } ​ func (s *UserServer) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.User, error) { // 从 metadata 获取调用者信息 if md, ok : metadata.FromIncomingContext(ctx); ok { log.Printf(GetUser called by: %v, md.Get(client-id)) } ​ user, ok : s.users[req.Id] if !ok { return nil, status.Errorf(codes.NotFound, 用户 %d 不存在, req.Id) } ​ return user, nil } ​ func (s *UserServer) ListUsers(ctx context.Context, req *pb.ListUsersRequest) (*pb.ListUsersResponse, error) { var users []*pb.User for _, user : range s.users { // 简单搜索过滤 if req.Search ! { if user.Name ! req.Search user.Email ! req.Search { continue } } users append(users, user) } ​ // 分页 start : (req.Page - 1) * req.PageSize end : start req.PageSize if start len(users) { users []*pb.User{} } else if end len(users) { users users[start:] } else { users users[start:end] } ​ return pb.ListUsersResponse{ Users: users, Total: int32(len(users)), Page: req.Page, PageSize: req.PageSize, }, nil } ​ func (s *UserServer) CreateUser(ctx context.Context, req *pb.CreateUserRequest) (*pb.User, error) { // 参数验证 if req.Name { return nil, status.Error(codes.InvalidArgument, 用户名为必填项) } if req.Email { return nil, status.Error(codes.InvalidArgument, 邮箱为必填项) } ​ user : pb.User{ Id: s.nextID, Name: req.Name, Email: req.Email, Active: true, Roles: req.Roles, CreatedAt: timestamppb.Now(), } ​ s.users[s.nextID] user s.nextID ​ log.Printf(创建用户: ID%d, Name%s, user.Id, user.Name) return user, nil } ​ func (s *UserServer) UpdateUser(ctx context.Context, req *pb.UpdateUserRequest) (*pb.User, error) { user, ok : s.users[req.Id] if !ok { return nil, status.Errorf(codes.NotFound, 用户 %d 不存在, req.Id) } ​ if req.Name ! { user.Name req.Name } if req.Email ! { user.Email req.Email } user.Active req.Active ​ log.Printf(更新用户: ID%d, user.Id) return user, nil } ​ func (s *UserServer) DeleteUser(ctx context.Context, req *pb.DeleteUserRequest) (*pb.Empty, error) { if _, ok : s.users[req.Id]; !ok { return nil, status.Errorf(codes.NotFound, 用户 %d 不存在, req.Id) } ​ delete(s.users, req.Id) log.Printf(删除用户: ID%d, req.Id) ​ return pb.Empty{}, nil } ​ // 双向流 RPC 实现 func (s *UserServer) BatchProcessUsers(stream pb.UserService_BatchProcessUsersServer) error { for { user, err : stream.Recv() if err io.EOF { // 客户端发送完毕发送响应 return nil } if err ! nil { return err } ​ log.Printf(处理用户: ID%d, Name%s, user.Id, user.Name) ​ // 模拟处理 result : pb.OperationResult{ Id: user.Id, Success: true, Message: fmt.Sprintf(用户 %s 处理成功, user.Name), } ​ // 发送响应 if err : stream.Send(result); err ! nil { return err } } }3.3 服务启动与注册func main() { // 创建监听 lis, err : net.Listen(tcp, :50051) if err ! nil { log.Fatalf(监听端口失败: %v, err) } ​ // 创建 gRPC 服务器可以添加选项 opts : []grpc.ServerOption{ grpc.UnaryInterceptor(unaryServerInterceptor), grpc.StreamInterceptor(streamServerInterceptor), } server : grpc.NewServer(opts...) ​ // 注册服务 userServer : NewUserServer() pb.RegisterUserServiceServer(server, userServer) ​ log.Printf(gRPC 服务启动监听端口 :50051) ​ // 启动服务 if err : server.Serve(lis); err ! nil { log.Fatalf(服务启动失败: %v, err) } } ​ // 单元拦截器示例 func unaryServerInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { log.Printf(调用方法: %s, info.FullMethod) ​ // 前置处理 start : time.Now() ​ // 调用实际方法 resp, err : handler(ctx, req) ​ // 后置处理 log.Printf(方法 %s 耗时: %v, info.FullMethod, time.Since(start)) ​ return resp, err } ​ // 流拦截器示例 func streamServerInterceptor(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { log.Printf(流方法调用: %s, info.FullMethod) return handler(srv, ss) }四、gRPC 客户端开发4.1 简单客户端package main ​ import ( context log time ​ google.golang.org/grpc google.golang.org/grpc/credentials/insecure ​ pb github.com/example/grpc-demo/gen/user ) ​ func main() { // 连接 gRPC 服务器 conn, err : grpc.Dial( localhost:50051, grpc.WithTransportCredentials(insecure.NewCredentials()), // 测试环境不使用 TLS grpc.WithBlock(), // 阻塞直到连接成功 grpc.WithTimeout(time.Second*10), // 超时设置 ) if err ! nil { log.Fatalf(连接服务器失败: %v, err) } defer conn.Close() ​ // 创建客户端 client : pb.NewUserServiceClient(conn) ​ // 调用 GetUser ctx, cancel : context.WithTimeout(context.Background(), time.Second*5) defer cancel() ​ user, err : client.GetUser(ctx, pb.GetUserRequest{Id: 1}) if err ! nil { log.Printf(获取用户失败: %v, err) } else { log.Printf(获取用户成功: %v, user) } }4.2 带认证的客户端// 认证元数据 type Auth struct { Token string } ​ func (a *Auth) GetRequestMetadata(ctx context.Context, urls ...string) (map[string]string, error) { return map[string]string{ authorization: Bearer a.Token, }, nil } ​ func (a *Auth) RequireTransportSecurity() bool { return false // 测试环境设为 false } ​ // 认证连接示例 func authenticatedClient() (*grpc.ClientConn, error) { creds : Auth{Token: your-jwt-token} return grpc.Dial( localhost:50051, grpc.WithPerRPCCredentials(creds), ) }4.3 客户端流调用// 批量创建用户 func batchCreateUsers(client pb.UserServiceClient, users []*pb.User) error { ctx, cancel : context.WithTimeout(context.Background(), time.Minute) defer cancel() ​ stream, err : client.BatchProcessUsers(ctx) if err ! nil { return err } ​ // 发送请求流 for _, user : range users { if err : stream.Send(user); err ! nil { return err } } ​ // 关闭发送流并接收响应 reply, err : stream.CloseAndRecv() if err ! nil { return err } ​ log.Printf(批量处理完成: %v, reply) return nil }4.4 双向流调用// 双向流实时通信 func bidirectionalStream(client pb.UserServiceClient) error { ctx, cancel : context.WithCancel(context.Background()) defer cancel() ​ stream, err : client.BatchProcessUsers(ctx) if err ! nil { return err } ​ // 使用两个 goroutine 分别处理发送和接收 var wg sync.WaitGroup wg.Add(2) ​ // 发送协程 go func() { defer wg.Done() for i : 0; i 10; i { user : pb.User{ Id: int32(i), Name: fmt.Sprintf(User%d, i), } if err : stream.Send(user); err ! nil { log.Printf(发送失败: %v, err) return } time.Sleep(100 * time.Millisecond) } stream.CloseSend() }() ​ // 接收协程 go func() { defer wg.Done() for { result, err : stream.Recv() if err io.EOF { return } if err ! nil { log.Printf(接收失败: %v, err) return } log.Printf(收到结果: ID%d, Success%t, Message%s, result.Id, result.Success, result.Message) } }() ​ wg.Wait() return nil }五、元数据与拦截器5.1 元数据MetadatagRPC 使用 Metadata 在请求中传递额外信息// 定义可选的消息头 message ExtraInfo { string trace_id 1; string span_id 2; mapstring, string tags 3; }服务端读取元数据func (s *UserServer) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.User, error) { // 读取元数据 md, ok : metadata.FromIncomingContext(ctx) if !ok { return nil, status.Error(codes.Internal, 无法获取元数据) } ​ // 获取特定字段 traceID : md.Get(x-trace-id) if len(traceID) 0 { log.Printf(Trace ID: %s, traceID[0]) } ​ // 处理请求... return s.users[req.Id], nil }客户端发送元数据func callWithMetadata(client pb.UserServiceClient) { // 创建元数据 md : metadata.Pairs( x-trace-id, abc123, x-client-version, 1.0.0, ) ​ // 创建带元数据的上下文 ctx : metadata.NewOutgoingContext(context.Background(), md) ​ // 调用 client.GetUser(ctx, pb.GetUserRequest{Id: 1}) }5.2 拦截器实现Unary 拦截器func loggingInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { log.Printf( 收到请求: %s, info.FullMethod) ​ // 添加追踪 md, _ : metadata.FromIncomingContext(ctx) traceID : md.Get(x-trace-id) if len(traceID) 0 { ctx context.WithValue(ctx, trace_id, traceID[0]) } ​ // 调用实际处理函数 resp, err : handler(ctx, req) ​ if err ! nil { log.Printf( 请求失败: %s, error: %v, info.FullMethod, err) } else { log.Printf( 请求成功: %s, info.FullMethod) } ​ return resp, err }Stream 拦截器func streamLoggingInterceptor(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { log.Printf( 收到流请求: %s, IsServerStream: %v, info.FullMethod, info.IsServerStream) ​ // 包装原始流以添加日志功能 wrapped : loggingServerStream{ServerStream: ss} ​ return handler(srv, wrapped) } ​ type loggingServerStream struct { grpc.ServerStream } ​ func (x *loggingServerStream) SendMsg(m interface{}) error { log.Printf( 发送消息: %T, m) return x.ServerStream.SendMsg(m) } ​ func (x *loggingServerStream) RecvMsg(m interface{}) error { err : x.ServerStream.RecvMsg(m) if err nil { log.Printf( 收到消息: %T, m) } return err }5.3 认证拦截器// 简单 Token 认证 func authInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { // 白名单不需要认证的方法 whitelist : map[string]bool{ /user.UserService/GetUser: true, } if whitelist[info.FullMethod] { return handler(ctx, req) } ​ // 检查 Token md, ok : metadata.FromIncomingContext(ctx) if !ok { return nil, status.Error(codes.Unauthenticated, 缺少元数据) } ​ tokens : md.Get(authorization) if len(tokens) 0 { return nil, status.Error(codes.Unauthenticated, 缺少认证 Token) } ​ token : strings.TrimPrefix(tokens[0], Bearer ) if !validateToken(token) { return nil, status.Error(codes.Unauthenticated, 无效的 Token) } ​ return handler(ctx, req) } ​ func validateToken(token string) bool { // 实际应该验证 JWT 或其他 token return token valid-token }六、双向流 RPC 详解6.1 四种 RPC 类型gRPC 支持四种 RPC 类型1. 一元 RPC (Unary RPC) 客户端 → 服务端 ClientStream → 服务器处理 → ClientStream ​ 2. 客户端流 RPC (Client Streaming RPC) ClientStream → 服务器处理 → ClientStream 客户端发送多个请求服务器返回一个响应 ​ 3. 服务端流 RPC (Server Streaming RPC) ClientStream → 服务器处理 → ClientStream 客户端发送一个请求服务器返回多个响应 ​ 4. 双向流 RPC (Bidirectional Streaming RPC) ClientStream ↔ 服务器处理 ↔ ClientStream 双方都可以发送多个消息6.2 双向流聊天服务示例定义 protoservice ChatService { // 双向流聊天 rpc Chat(stream ChatMessage) returns (stream ChatMessage); } ​ message ChatMessage { string sender 1; string content 2; int64 timestamp 3; }服务端实现type ChatServer struct { pb.UnimplementedChatServiceServer clients map[string]pb.ChatService_ChatServer mu sync.Mutex } ​ func (s *ChatServer) Chat(stream pb.ChatService_ChatServer) error { var sender string ​ // 等待第一个消息用于注册 firstMsg, err : stream.Recv() if err ! nil { return err } sender firstMsg.Sender ​ // 注册客户端 s.mu.Lock() s.clients[sender] stream s.mu.Unlock() ​ defer func() { // 注销客户端 s.mu.Lock() delete(s.clients, sender) s.mu.Unlock() }() ​ // 启动接收协程 errChan : make(chan error, 1) go func() { for { msg, err : stream.Recv() if err io.EOF { errChan - nil return } if err ! nil { errChan - err return } ​ // 广播消息给所有客户端 s.broadcast(msg) } }() ​ // 等待错误 select { case err : -errChan: return err } } ​ func (s *ChatServer) broadcast(msg *pb.ChatMessage) { s.mu.Lock() defer s.mu.Unlock() ​ for sender, stream : range s.clients { if sender ! msg.Sender { // 不发给自己 stream.Send(msg) } } }七、实战案例微服务通信框架7.1 项目架构microservices/ ├── proto/ │ ├── user.proto │ ├── order.proto │ └── product.proto ├── pkg/ │ ├── grpc/ │ │ ├── client.go │ │ ├── server.go │ │ └── interceptor.go │ └── discovery/ │ └── consul.go ├── services/ │ ├── user-service/ │ ├── order-service/ │ └── product-service/ └── go.mod7.1 通用 gRPC 客户端封装package grpc ​ import ( context crypto/tls fmt time ​ google.golang.org/grpc google.golang.org/grpc/credentials google.golang.org/grpc/credentials/insecure google.golang.org/grpc/resolver ) ​ // ClientConfig 客户端配置 type ClientConfig struct { Name string Address string Timeout time.Duration TLSCert string Token string MaxRecvMsg int MaxSendMsg int } ​ // Dial 创建 gRPC 连接 func Dial(ctx context.Context, cfg ClientConfig) (*grpc.ClientConn, error) { opts : []grpc.DialOption{ // 超时设置 grpc.WithBlock(), grpc.WithTimeout(cfg.Timeout), } ​ // 凭证设置 if cfg.TLSCert ! { creds, err : credentials.NewClientTLSFromFile(cfg.TLSCert, ) if err ! nil { return nil, fmt.Errorf(加载 TLS 证书失败: %w, err) } opts append(opts, grpc.WithTransportCredentials(creds)) } else { opts append(opts, grpc.WithTransportCredentials(insecure.NewCredentials())) } ​ // Token 认证 if cfg.Token ! { opts append(opts, grpc.WithPerRPCCredentials(tokenAuth{Token: cfg.Token})) } ​ // 消息大小限制 if cfg.MaxRecvMsg 0 { opts append(opts, grpc.WithDefaultCallOptions( grpc.MaxCallRecvMsgSize(cfg.MaxRecvMsg), )) } if cfg.MaxSendMsg 0 { opts append(opts, grpc.WithDefaultCallOptions( grpc.MaxCallSendMsgSize(cfg.MaxSendMsg), )) } ​ return grpc.DialContext(ctx, cfg.Address, opts...) } ​ // tokenAuth 实现 Token 认证 type tokenAuth struct { Token string } ​ func (t *tokenAuth) GetRequestMetadata(ctx context.Context, urls ...string) (map[string]string, error) { return map[string]string{ authorization: Bearer t.Token, }, nil } ​ func (t *tokenAuth) RequireTransportSecurity() bool { return true }7.2 通用服务端封装package grpc ​ import ( context crypto/tls fmt net time ​ google.golang.org/grpc google.golang.org/grpc/keepalive google.golang.org/grpc/reflection ) ​ // ServerConfig 服务端配置 type ServerConfig struct { Port int TLSCert string TLSKey string MaxRecvMsg int MaxSendMsg int Interceptors []grpc.UnaryServerInterceptor StreamInts []grpc.StreamServerInterceptor } ​ // Server 通用 gRPC 服务器 type Server struct { cfg ServerConfig server *grpc.Server lis net.Listener } ​ // NewServer 创建 gRPC 服务器 func NewServer(cfg ServerConfig) (*Server, error) { opts : []grpc.ServerOption{ // 消息大小限制 grpc.MaxRecvMsgSize(cfg.MaxRecvMsg), grpc.MaxSendMsgSize(cfg.MaxSendMsg), ​ // Keepalive 设置 grpc.KeepaliveParams(keepalive.ServerParameters{ MaxConnectionIdle: 5 * time.Minute, MaxConnectionAge: 2 * time.Hour, MaxConnectionAgeGrace: 30 * time.Second, Time: 5 * time.Minute, Timeout: 30 * time.Second, }), ​ // 拦截器 grpc.ChainUnaryInterceptor(cfg.Interceptors...), grpc.ChainStreamInterceptor(cfg.StreamInts...), } ​ // TLS 配置 if cfg.TLSCert ! cfg.TLSKey ! { creds, err : credentials.NewServerTLSFromFile(cfg.TLSCert, cfg.TLSKey) if err ! nil { return nil, fmt.Errorf(加载 TLS 证书失败: %w, err) } opts append(opts, grpc.Creds(creds)) } ​ server : grpc.NewServer(opts...) ​ // 注册反射服务用于 grpcurl 等工具调试 reflection.Register(server) ​ return Server{ cfg: cfg, server: server, }, nil } ​ // RegisterService 注册 gRPC 服务 func (s *Server) RegisterService(registerFunc func(*grpc.Server)) { registerFunc(s.server) } ​ // Start 启动服务器 func (s *Server) Start(ctx context.Context) error { lis, err : net.Listen(tcp, fmt.Sprintf(:%d, s.cfg.Port)) if err ! nil { return fmt.Errorf(监听端口失败: %w, err) } s.lis lis ​ errCh : make(chan error, 1) go func() { errCh - s.server.Serve(lis) }() ​ select { case err : -errCh: return err case -ctx.Done(): s.server.GracefulStop() return nil } } ​ // Stop 停止服务器 func (s *Server) Stop() { s.server.GracefulStop() }7.3 服务发现集成package discovery ​ import ( context fmt ​ github.com/hashicorp/consul/api google.golang.org/grpc/resolver ) ​ // ConsulResolver Consul 服务发现解析器 type ConsulResolver struct { consulClient *api.Client serviceName string scheme string } ​ // NewConsulResolver 创建 Consul 解析器 func NewConsulResolver(consulAddr, serviceName string) (*ConsulResolver, error) { config : api.DefaultConfig() config.Address consulAddr ​ client, err : api.NewClient(config) if err ! nil { return nil, fmt.Errorf(创建 Consul 客户端失败: %w, err) } ​ return ConsulResolver{ consulClient: client, serviceName: serviceName, scheme: consul, }, nil } ​ // Build 实现 resolver.Builder 接口 func (r *ConsulResolver) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) { return consulResolver{ consulClient: r.consulClient, serviceName: r.serviceName, cc: cc, }, nil } ​ // Scheme 返回解析器 scheme func (r *ConsulResolver) Scheme() string { return r.scheme } ​ type consulResolver struct { consulClient *api.Client serviceName string cc resolver.ClientConn } ​ func (r *consulResolver) ResolveNow(options resolver.ResolveNowOptions) { r.resolve() } ​ func (r *consulResolver) resolve() { services, _, err : r.consulClient.Health().Service(r.serviceName, , true, nil) if err ! nil { r.cc.ReportError(err) return } ​ var addrs []resolver.Address for _, svc : range services { addrs append(addrs, resolver.Address{ Addr: fmt.Sprintf(%s:%d, svc.Service.Address, svc.Service.Port), }) } ​ r.cc.UpdateState(resolver.State{Addresses: addrs}) } ​ func (r *consulResolver) Close() {} ​ // Register 注册服务到 Consul func Register(ctx context.Context, consulAddr, serviceName, addr string, port int) error { config : api.DefaultConfig() config.Address consulAddr ​ client, err : api.NewClient(config) if err ! nil { return err } ​ reg : api.AgentServiceRegistration{ ID: fmt.Sprintf(%s-%s, serviceName, addr), Name: serviceName, Port: port, Address: addr, Check: api.AgentServiceCheck{ GRPC: fmt.Sprintf(%s:%d, addr, port), Interval: 10s, Timeout: 5s, DeregisterCriticalServiceAfter: 30s, }, } ​ return client.Agent().ServiceRegister(reg) }总结gRPC Protobuf 是一套成熟的微服务通信解决方案Protobuf 优势体积小、解析快、类型安全、向前兼容gRPC 优势高性能、双工流、代码生成、协议统一四种 RPC 类型一元、流式客户端、流式服务端、双向流元数据与拦截器实现认证、日志、追踪等横切关注点服务发现通过 resolver 实现负载均衡和服务发现最佳实践proto 文件集中管理统一版本使用拦截器实现横切关注点生产环境务必启用 TLS合理设置消息大小限制使用流式 API 处理大数据量场景

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2583134.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…