Loading

go-gRPC微服务调用

协议介绍

RPC协议

RPC(远程过程调用协议),通过网络从远程计算机上请求服务,而不需要了解底层网络技术的协议。RPC假定某些协议的存在,例如TCP/UDP等,为通信程序之间携带信息数据。在OSI网络七层模型中,RPC跨越了传输层和应用层,RPC使得开发包括网络分布式多程序在内的应用程序更加容易,过程就是像调用本地方法一样调用远程的过程,RPC采用客户端/服务端的模式,通过request-response消息模式实现

gRPC协议

gRPC是一个现代的开源高性能远程过程调用(RPC)框架,gRPC的客户端可以像调用本地对象一样直接调用另一台不同的机器上服务端应用的方法,使能够更容易地创建分布式应用和服务,gRPC 基于以下理念:

  • 定义一个服务,指定其能够被远程调用的方法(包含参数和返回类型)
  • 在服务端实现这个接口,并运行一个 gRPC 服务器来处理客户端调用
  • 在客户端拥有一个存根能够像服务端一样的方法
HTTP2

gRPC的底层是基于HTTP2协议:

  • HTTP1里的header对应HTTP2里的 HEADERS frame
  • HTTP1里的payload对应HTTP2里的 DATA frame
  • gGRPC把元数据放到HTTP/2 Headers里,请求参数序列化之后放到 DATA frame
  • HTTP2里面gRPC调用需要解码流程,一次是HEADERS frame,一次是DATA frame
  • HTTP2标准本身是只有一个TCP链接,但是实际在gRPC里是会有多个TPC连接

gRPC调用实例

grpc服务端
  1. .proto文件

    // 请求参数
    message  UserInfoRequest{
      int32 user_id = 1;
    }
    
    // 响应参数
    message  UserInfoResponse {
      string user_name = 1;
    }
    
    // 定义接口
    service  UserInfoService{
      rpc GetUserInfo(UserInfoRequest) returns(UserInfoResponse);
    }
    
  2. 生成grpc调用的文件,使用plugins=grpc指定

     protoc --go_out=plugins=grpc:./ test.proto     
    
    // 生成对应的test.pb.go文件
    // 生成的部分代码
    
    // 自动生成GetUserInfo接口方法
    func (*UnimplementedUserInfoServiceServer) GetUserInfo(context.Context, *UserInfoRequest) (*UserInfoResponse, error) {
    	return nil, status.Errorf(codes.Unimplemented, "method GetUserInfo not implemented")
    }
    
    
    // GetUserInfo方法的响应参数
    type UserInfoResponse struct {
    	state         protoimpl.MessageState
    	sizeCache     protoimpl.SizeCache
    	unknownFields protoimpl.UnknownFields
    
    	UserName string `protobuf:"bytes,1,opt,name=user_name,json=userName,proto3" json:"user_name,omitempty"`
    }
    
  3. 实现接口逻辑

    // service/product.go
    // 根据第二步生成的接口,由于只有接口没有业务逻辑实现,所以去实现具体的业务逻辑
    
    
    package service
    
    import "context"
    
    // 定义对应的实例
    var UserService = &UserInfoService{}
    
    type UserInfoService struct {
    }
    
    func (u *UserInfoService) GetUserInfo(context.Context, *UserInfoRequest) (*UserInfoResponse, error) {
    	// 实现业务逻辑
    	return &UserInfoResponse{UserName: "名字"}, nil
    }
    
    
  4. 注册服务

    package main
    
    import (
    	"go_rpc_demo/service"
    	"google.golang.org/grpc"
    )
    
    func main() {
    	// 创建一个grpc服务端
    	rpcServer := grpc.NewServer()
    
    	// RegisterUserInfoServiceServer是默认生成
    	// 调用RegisterUserInfoServiceServer ,将第三部的接口的实现注册到rpcServer
    	service.RegisterUserInfoServiceServer(rpcServer, service.UserService)
    
    }
    
  5. 启动服务

    package main
    
    import (
    	"go_rpc_demo/service"
    	"google.golang.org/grpc"
    	"log"
    	"net"
    )
    
    func main() {
    	// 创建grpc服务实例
    	rpcServer := grpc.NewServer()
    
    	// RegisterUserInfoServiceServer是由 Protobuf 编译器根据.ptoto定义的服务自动生成的函数
    	// 调用RegisterUserInfoServiceServer ,将第三步的接口的实现注册到rpcServer
    	service.RegisterUserInfoServiceServer(rpcServer, service.UserService)
    
    	// 在tpc协议的8002端口开启监听,返回一个 net.Listener 类型的对象和可能出现的错误
    	listener, err := net.Listen("tcp", ":8002")
    	if err != nil {
    		log.Fatal("启动监听出错", err)
    	}
    
    	// 调用 rpcServer 的 Serve 方法,让 gRPC 服务器开始在 listener 所监听的端口上提供服务。此方法会一直阻塞,直至服务器关闭
    	err = rpcServer.Serve(listener)
    	if err != nil {
    		log.Fatal("启动服务出错") 
    	}
    
    
    }
    
grpc客户端
  1. 在客户端目录中,将 service下的product.go和test.pb.go 复制一份

    image-20250319135354979

  2. 客户端请求

    package main
    
    import (
    	"context"
    	"go_rpc_demo/service"
    	"google.golang.org/grpc"
    	"google.golang.org/grpc/credentials/insecure"
    	"log"
    )
    
    func main() {
    	// 创建一个连接
    	// 连接到8002端口的grpc服务器
    	// 第二个参数是连接凭证
    	// grpc.WithTransportCredentials(insecure.NewCredentials()) 表示使用不安全的传输凭证,也就是不进行 TLS 加密,一般用于开发和测试环境
    	conn, err := grpc.Dial(":8002", grpc.WithTransportCredentials(
    		insecure.NewCredentials()))
    
    	if err != nil {
    		log.Fatal("连接出错", err)
    	}
    
    	// 执行完关闭连接
    	defer conn.Close()
    
    	// NewUserInfoServiceClient是由 Protobuf 编译器根据服务名字生成的
    	// 创建客户端,传入对应的连接
    	client := service.NewUserInfoServiceClient(conn)
    	// 请求参数
    	request := &service.UserInfoRequest{UserId: 1}
    	// 通过客户端调用对应的方法,像调用本地方法一样
    	infoResponse, err := client.GetUserInfo(context.Background(), request)
    	if err != nil {
    		log.Fatal("请求失败", err)
    	}
    	// 请求结果
    	log.Println(infoResponse)
    
    }
    
    
posted @ 2025-03-19 13:58  木子七  阅读(139)  评论(0)    收藏  举报