Protobuf文件实现
若需要采用grpc相关,需要在proto文件中以service定义,例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| syntax = "proto3"; package auth;
import "user.proto"; option go_package = "./auth;auth";
service AuthService { rpc Login(LoginRequest) returns (LoginResponse); rpc Register(RegisterRequest) returns (RegisterResponse); }
message LoginRequest { string username = 1; string password = 2; }
message LoginResponse { string token = 1; user.User user = 2; }
message RegisterRequest { string username = 1; string password = 2; string email = 3; }
message registerresponse {}
|
使用编译命令编译后,除了普通的.pb.go文件,还会生成xx_grpc.pb.go文件,里面实现了客户端和服务端需要的一些方法
1 2
| protoc -I. -I/usr/include --go_out=. --go_opt=module=grpc-study --go-grpc_out=. --go-grpc_opt=module=grpc-study ./au th.proto
|
实现一个server
首先在/internal/grpc下创建一个server.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| package grpc
import ( "fmt" "net"
"google.golang.org/grpc" )
func StartGrpcServer() { ln, err := net.Listen("tc p", ":8000") if err != nil { panic(err) } s := grpc.NewServer() err = s.Serve(ln) if err != nil { fmt.Println(err) return } }
|
接下来需要实现方法,在service文件夹下创建auth.go,实现一个登录方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package service
import ( "context" userProto "grpc-study/auth" "grpc-study/model" )
type AuthService struct { }
func NewAuthService() *AuthService { return &AuthService{} }
func (s *AuthService) Login(ctx context.Context, req *userProto.LoginRequest) (*model.UserModel, error) { return &model.UserModel{ Id: 1, Username: req.Username, Password: req.Password, Email: "", },nil }
|
Model层实现如下:
1 2 3 4 5 6 7 8 9
| package model
type UserModel struct { Id int64 Username string Password string Email string }
|
接下来需要将业务的注册到服务端,需要实现注册方法,ctrl建立文件auth.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| package ctrl
import ( "context" userProto "grpc-study/auth" "grpc-study/service" userModelProto "grpc-study/user" "log" ) type AuthController struct { userProto.UnimplementedAuthServiceServer }
func validateLoginRequest(req *userProto.LoginRequest) error { return nil }
func (c *AuthController) Login(ctx context.Context, req *userProto.LoginRequest) (*userProto.LoginResponse, error) { if err := validateLoginRequest(req); err != nil { return nil, err }
log.Println("Login request received:", req.Username, req.Password) userModel, err := service.NewAuthService().Login(ctx, req) if err != nil { return nil, err }
resp := &userProto.LoginResponse{ Token: "123", User: &userModelProto.User{ Id: userModel.Id, }, } return resp, nil }
func (c *AuthController) Register(ctx context.Context, req *userProto.RegisterRequest) (*userProto.RegisterResponse, error) { return &userProto.RegisterResponse{}, nil }
|
RegisterAuthServiceServer用于方法的内部注册,即将我们自己定义的AuthController交给gRPC服务器,让将来发入的auth服务请求交给这个实例处理,其中AuthController是我们自己定义的,实现了AuthServiceServer接口要求的方法的实例
最后在server端添加注册:
1 2 3 4
| authCtrl := &ctrl.AuthController{} s := grpc.NewServer() userProto.RegisterAuthServiceServer(s, authCtrl)
|
实现一个Client
开一个文件夹放客户端,/authclient下的main.go文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| package main
import ( "context" userProto "grpc-study/auth" "fmt" "log" "sync" "sync/atomic" "google.golang.org/grpc" )
func main() { cc := NewUserClientPool("localhost:8000", 5) resp,err := cc.GetClient().Login(context.Background(), &userProto.LoginRequest{ Username: "admin", Password: "admin", }) if err != nil { log.Fatal(err) } fmt.Println("Login response received:", resp.Token, resp.User.Id) }
type userClientPool struct { clients []userProto.AuthServiceClient mu sync.Mutex index int64
}
func NewUserClientPool(addr string, size int) *userClientPool { cc, err := grpc.Dial(addr, grpc.WithInsecure()) if err != nil { panic(err) } var clients []userProto.AuthServiceClient for i := 0; i < size; i++ { clients = append(clients, userProto.NewAuthServiceClient(cc)) } return &userClientPool{ clients: clients, index: 0, } } func (p *userClientPool) GetClient() userProto.AuthServiceClient { index := atomic.AddInt64(&p.index, 1) return p.clients[int(index)%len(p.clients)] }
|
- 这里采用了多路复用技术,即开一个用户池,可以开启多个连接
- 这里实现了负载均衡,每次调用后悔从池里挑一个client发送请求
- 请求再发送给服务端,服务端执行业务逻辑,返回响应
整体链路
上面实现的是一个标准的gRPC调用闭环:
客户端生成stub发起RPC→
服务端启动时把controller注册到gRPC的服务端→
收到请求后由controller调用业务层→
把业务结果组装为proto响应返回
入口
服务端主程序仅仅调用了server.go的开启服务端函数,这个函数通过net.Listen()监听tcp,创建gRPCserver,再把AuthController通过通过RegisterAuthServiceServer注册进去
这样,proto里定义的auth服务就和本地实现的auth业务逻辑绑定起来了,服务器知道如何处理rpc请求
controller
请求进来以后,会落到ctrl/auth.go,进入controller层
先进行参数校验,再调用方法AuthService.Login,实现后返回内部模型model.UserModel,ctrl再把返回的数据转换成proto的数据类型并组装成响应返回
因此service层只管业务数据,ctrl层负责把内部模型翻译为对外rpc协议
客户端
客户端在main.go中先使用grpc.Dial()建立连接,通过NewUserClientPool创建用户池,并从中GetClient()获取一个用户创建客户端stub
后面调用方法Login()会直接把你的LoginRequest序列化后发送到服务端,服务端处理后再把LoginResponse反序列化后返回客户端,拿到想要的东西