红魔咖啡馆

头发越掉越多,头发越掉越少

0%

【gRPC】服务端与客户端搭建

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
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
	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反序列化后返回客户端,拿到想要的东西