使用go-zero框架做rpc服务端

本文使用grpc协议

微服务导读

使用gRPC一个完整的调用过程如下
1.客户端调用A方法,发起RPC调用
2.客户端对请求信息、参数使用Protobuf进行对象序列化压缩
3.服务端接收到请求后,解码请求体,进行业务逻辑处理并返回。
4.服务端对响应结果使用Protobuf进行对象序列化压缩
5.客户端接受到服务端响应,解码请求体。回调被调用的A方法,唤醒正在等待响应(阻塞)的客户端调用并返回响应结果

前4步为重要的请求周期,这个请求过程不在通过http方式运行,客户端与服务端的通讯参数,通过protocol buff通讯,而.proto文件定义了编码解码的规范,犹如一把钥匙(交互文档)一样。
gRPC的支持请求响应参数一元传输(一次性传递完)、流式传输(持续的流式传输)

示例 user.proto
syntax = "proto3";

package user;
  
// protoc-gen-go 版本大于1.4.0, proto文件需要加上go_package,否则无法生成
option go_package = "core/user";

message IdRequest {
    int32 id = 1;
}
  
message UserResponse {
    // 用户id
    int32 id = 1;
    // 用户名称
    string name = 2;
}

message UserOauthResponse {
    int32 id = 1;
    string  open_id = 2;
    string nickname = 3;
    string avatar = 4;
}

service User {
    rpc getUser(IdRequest) returns(UserResponse);
    rpc getUserOauth(IdRequest) returns(UserOauthResponse);
}

定义了两个rpc服务getUser,getUserOauth,其中定义了请求、返回参数

交互过程中,此文件可代替对接文档,下放调用者即可

基础依赖

gRPC通讯依赖Protocol Buffers(简称 protobuf),需安装3个软件包protoc,protoc-gen-go,protoc-gen-go-grpc,作用分别是:

  1. protoc 是 Protocol Buffers 的编译器,它将 .proto 文件编译成多种编程语言的源代码,包括 C++、Java、Python、Go 等。它可以根据 .proto 文件生成对应语言的数据类型定义和序列化/反序列化方法等代码。
官方仓库下载 https://github.com/protocolbuffers/protobuf/releases 直接下编译好的zip包到本地,可执行文件放入环境变量中
wancheng@MacBook-Pro-4 www % protoc --version
libprotoc 3.12.4
  1. protoc-gen-go 是用于生成 Go 语言代码的插件,它是 protoc 的一个插件。当使用 protoc 编译 .proto 文件时,可以指定使用 protoc-gen-go 插件来生成 Go 语言代码。生成的 Go 代码包括 Protocol Buffers 的消息类型定义、序列化/反序列化方法、以及一些辅助函数等。
通过命令 go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26 应确保GOPATH/bin目录在环境变量中
这个程序本质是一个插件,没有进行命令行单独运行,需搭配protoc命令,因此无法通过 `protoc-gen-go --version` 确认安装成功
  1. protoc-gen-go-grpc 是用于生成 Go 语言 gRPC 服务接口代码的插件,它也是 protoc 的一个插件。它可以根据 .proto 文件生成 gRPC 服务接口的定义和实现,包括服务接口中的方法定义、请求和响应消息类型、以及服务端和客户端的 stub 代码等。
通过命令 go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2 应确保GOPATH/bin目录在环境变量中
wancheng@MacBook-Pro-4 www % protoc-gen-go-grpc --version
protoc-gen-go-grpc 1.2.0

由此看出,protoc命令是基础命令,protoc-gen-go命令是解读.proto文件生成序列化编码解码、操作数据的插件,protoc-gen-go-grpc命令是做服务通讯方面的插件

基础使用

命令执行

protoc --go_out=./user --go_opt=paths=source_relative --go-grpc_out=./user --go-grpc_opt=paths=source_relative user.proto

参数含义

--go_out=.   使用 protoc-gen-go 插件将 user.proto 文件编译为 Go 语言代码,并将生成的代码放置在 ./user目录下
--go_opt=paths=source_relative   设置生成的 Go 代码中的导入路径为相对路径
--go-grpc_out=.  使用 protoc-gen-go-grpc 插件将 user.proto 文件编译为带有 gRPC 服务的 Go 语言代码,并将生成的代码放置在./user目录下
--go-grpc_opt=paths=source_relative  设置生成的带有 gRPC 服务的 Go 代码中的导入路径为相对路径

上面命令在user目录下生成了两个文件user_pb.go,user_grpc.pb.go

  1. user_pb.go 包含了 Protobuf 文件中定义的所有消息类型、服务以及相关的方法。服务端需要使用这个文件来实现服务器端的业务逻辑。
服务端、客户端均可通过此文件中封装好的方法,操作传输的数据
此文件不建议修改,已满足大部分数据的增删改查
  1. user_grpc.pb.go 包含了 gRPC 相关的代码,比如客户端需要使用的 Client 类型以及服务器端需要实现的 Server 接口。客户端需要使用这个文件来调用服务器端提供的服务
服务端、客户端均可通过此文件中封装好的方法,创建rpc服务端,rpc请求客户端
此文件不建议修改

上述两个文件,为最原始的gRPC调用方法

一个完整客户端代码如下

当我们客户端拿到服务端定义的.proto文件后,通过protoc命令生成上面两个核心代码文件,调用其方法

	conn, _ := grpc.Dial("127.0.0.1:8080", grpc.WithInsecure())  // "google.golang.org/grpc"
	client := user.NewUserClient(conn)
	sendParams := &user.IdRequest{Id: 5}
	response, _ := client.GetUser(context.Background(), sendParams)

	fmt.Println(response)

这种方式是最简单的例子,客户端直链服务端的地址,未引入etcd服务发现等概念,下面,我们通过go-zero框架,重新来实现一个完整的调用

使用go-zero 依赖安装

goctl是干嘛的不在做过多说明

goctl为我们提供了一键安装方法,免去很多步骤,上诉的3个命令可通过下面一条命令执行即可,无论之前是否安装过,也不怕

goctl env check --install --verbose --force

检查是否成功

goctl env check --verbose
[goctl-env]: preparing to check env

[goctl-env]: looking up "protoc"
[goctl-env]: "protoc" is installed

[goctl-env]: looking up "protoc-gen-go"
[goctl-env]: "protoc-gen-go" is not found in PATH

[goctl-env]: looking up "protoc-gen-go-grpc"
[goctl-env]: "protoc-gen-go-grpc" is installed

是不是感觉很方便,下面我们来做具体业务

使用go-zero框架开发gRPC

在工作目录下,新建一个rpc项目 servjj

# 这个命令会新建一个空的gRPC项目,并为我们准备了一个最简单的demo示例
goctl rpc new servjj

初始化项目

cd servjj && go mod tidy

为了避免混淆,我们把demo给删掉,目录结构如下

├── etc          配置文件目录,存放 .yaml文件
├── go.mod
├── go.sum
├── internal     业务文件夹,大部分编码工作的地方
│   ├── config
│   │   └── config.go    配置文件加载类
│   ├── logic     业务逻辑层  
│   └── svc
│       └── servicecontext.go   项目初始化上下文类
└── proto    此目录为自己建的,专门用来存放.proto文件
    └── user.proto   还是用上面的.proto文件做演示

这是个空项目,main文件和配置文件都没有,通过下面的命令,可以初始化服务文件
需要注意的是,.proto 文件中package user;为整个微服务的名称主体,goctl会生成一个对应的 user.go (main包)文件和一个 对应的 user.ymal配置文件,如项目服务过多,可通过import方式管理多个.proto文件

在go-zero框架中生成user.proto的业务操作代码

goctl rpc protoc proto/user.proto --go_out=./core --go-grpc_out=./core --zrpc_out=. --style=goZero

生成代码文件,将原生的protoc命令创建的操作protobuff的文件放入core文件夹,(–zrpc_out)go-zero进行封装的代码放入当前目录下,代码风格为小驼峰
user.proto文件中,定义了两个调用方法getUser,getUserOauth,传入用户id返回用户信息
现在的目录结构为

├── core
│   └── user              通过user.proto文件生成的原生方法
│       ├── user.pb.go
│       └── user_grpc.pb.go
├── etc
│   └── user.yaml    user服务配置文件
├── go.mod
├── go.sum
├── internal
│   ├── config
│   │   └── config.go
│   ├── logic             user.proto中声明的两个外界调用方法,业务逻辑
│   │   ├── getUserLogic.go
│   │   └── getUserOauthLogic.go
│   ├── server
│   │   └── userServer.go     服务的调用方法handler方法,无需动此文件
│   └── svc
│       └── servicecontext.go
├── proto
│   └── user.proto
├── user.go        main主文件,创建服务端开启运行
└── userclient       客户端的(调用方法,需传入客户端链接具柄)封装,本文演示的是服务端,无需关注
    └── user.go
配置文件示例

我们的项目名为servjj.rpc,rpc服务端监听的端口为8080

Name: servjj.rpc
ListenOn: 0.0.0.0:8080

go-zero框架中封装了etcd注册发现功能,仅仅需添加配置即可

Etcd:
  Hosts:
  - 127.0.0.1:2379
  Key: user.rpc
  # User: root
  # Pass: "123456"

etcd的key为user.rpc,用来标识此项目产生的key,无论是否使用了etcd注册,上面的ListenOn地址都是可以直链的

配置文件最终为 user.ymal

Name: servjj.rpc
ListenOn: 0.0.0.0:8080
Etcd:
  Hosts:
  - 127.0.0.1:2379
  Key: user.rpc
  # User: root
  # Pass: "123456"

如若不使用etcd服务注册,配置文件如下

Name: servjj.rpc
ListenOn: 0.0.0.0:8080

配置文件加载类 config/config.go 无需做任何改动

type Config struct {
	zrpc.RpcServerConf
}

服务配置文档如下 https://go-zero.dev/docs/tutorials/grpc/server/configuration

服务端逻辑编写

internal/logic/getUserLogic.go

func (l *GetUserLogic) GetUser(in *user.IdRequest) (*user.UserResponse, error) {
	return &user.UserResponse{
		Id:     in.Id,
		Name:   fmt.Sprint("来自rpc服务器", l.svcCtx.Config.RpcServerConf.ListenOn, "返回的名字"),
		Gender: 1,
	}, nil
}

internal/logic/getUserOauthLogic.go

func (l *GetUserOauthLogic) GetUserOauth(in *user.IdRequest) (*user.UserOauthResponse, error) {
	return &user.UserOauthResponse{
		Id:       in.Id,
		Nickname: fmt.Sprint("来自rpc服务器", l.svcCtx.Config.RpcServerConf.ListenOn, "返回的名字"), // 注意,这里故意这样写,以方便演示,让客户端知道是哪个后端节点返回的请求
		Avatar:   "",
	}, nil
}

我们把这上面的user.proto定义的两个远程方法,逻辑已编写完成,到目前为止,rpc服务端开发编码工作已全部完成

修改user.proto文件,再添加一个方法
message UserNameResponse { // 新增
    // 用户id
    int32 id = 1;
    // 用户名称
    string name = 2;
}

service User {
    rpc getUser(IdRequest) returns(UserResponse);
    rpc getUserOauth(IdRequest) returns(UserOauthResponse);
    rpc getUserName(IdRequest) returns(UserNameResponse);  // 新增
}

再次执行命令,不会覆盖之前的代码

goctl rpc protoc proto/user.proto --go_out=./core --go-grpc_out=./core --zrpc_out=. --style=goZero

目录结构小微变化

├── core
│   └── user
│       ├── user.pb.go
│       └── user_grpc.pb.go // 代码更新,新增getUserName方法实现
├── etc
│   └── user.yaml
├── go.mod
├── go.sum
├── internal
│   ├── config
│   │   └── config.go
│   ├── logic
│   │   ├── getUserLogic.go
│   │   ├── getUserNameLogic.go // 新增getUserName方法逻辑文件
│   │   └── getUserOauthLogic.go
│   ├── server
│   │   └── userServer.go  // 新增getUserName方法handler
│   └── svc
│       └── servicecontext.go
├── proto
│   └── user.proto
├── user.go
└── userclient
    └── user.go   // 新增getUserName方法

部署

开启etcd服务

不在过多说明etcd的安装开启,参考如下

# 创建容器并启动
docker run -d --name etcd-server --network app-tier --publish 2379:2379 --publish 2380:2380 --env ALLOW_NONE_AUTHENTICATION=yes --env ETCD_ADVERTISE_CLIENT_URLS=http://etcd-server:2379 bitnami/etcd:latest

推荐一个etcd的GUI客户端 http://etcdmanager.io/
image.png

代码部署

我们以同一个项目不同端口运行,来模拟多个节点运行
节点1:
修改user.yaml文件

ListenOn: 0.0.0.0:8081

新开1个终端,执行 go run user.go
此刻,etcd存储的值为

keyvalue
user.rpc/758787209181285248810.0.89.75:8081

10.0.89.75 是我本机的局域网ip,等价于 127.0.0.1

节点2:
修改user.yaml文件

ListenOn: 0.0.0.0:8082

重新开1个终端,执行 go run user.go
此刻,etcd存储的值为

keyvalue
user.rpc/758787209181285248810.0.89.75:8081
user.rpc/758787209181285249610.0.89.75:8082

image.png

到此,服务端工作已全部完毕~

测试

本文使用postman充当客户端测试,不再使用程序,由于上面我们是通过etcd部署的,但postman不支持链接etcd服务发现功能,故使用postman直链测试

postman 10.16.0

  1. 创建一个grpc的文件夹
    image.png

  2. 在此文件夹下新建一个gRPC服务
    image.png

  3. 填入RPC服务端地址,倒入user.proto文件
    image.png
    image.png

  4. 选中指定的User服务
    image.png
    选中User选项,点击Import as API按钮
    image.png

  5. 选择一个具体rpc方法发送请求
    image.png

本文使用了etcd进行后端部署,下文中,将介绍客户端通过etcd进行服务发现调用rpc方法

源码 https://gitee.com/qq_connect-60293/servjj

Go-Micro 和 Go-Zero 都是 Go 语言领域中受欢迎的微服务框架,它们各自有不同的特点和适用场景。下面是它们的优缺点和受欢迎程度的概述: Go-Micro 框架的优点: 1. 社区活跃:Go-Micro 是一个开源项目,并且拥有活跃的社区支持,可以从社区中获取丰富的资源和支持。 2. 多语言支持:Go-Micro 提供了多语言支持,可以用于构建多语言之间的微服务应用。 3. 插件系统:Go-Micro 提供了插件系统,可以方便地扩展和定制功能。 4. 多种通信协议支持:Go-Micro 支持多种通信协议,包括 HTTP、gRPC、AMQP 等。 5. 高度可定制性:Go-Micro 提供了很高的可定制性,可以根据需求进行灵活配置和扩展。 Go-Micro 框架的缺点: 1. 上手难度较高:对于初学者来说,Go-Micro 的学习曲线可能相对陡峭,需要一些时间和经验来掌握和理解其概念。 2. 文档相对不完善:尽管有一些文档和示例可供参考,但相对来说,Go-Micro 的文档可能相对不够完善。 Go-Zero 框架的优点: 1. 简单易用:Go-Zero 的设计目标是简化开发流程和提高开发效率,提供了简洁易用的 API 和工具,使得开发者能够快速上手并构建稳定可靠的微服务应用。 2. 高性能:Go-Zero 采用了一系列优化策略,包括代码生成、缓存、连接池等,以提供高性能的微服务框架。 3. 内置功能丰富:Go-Zero 提供了许多内置功能,例如日志、配置、错误处理、中间件等,可以帮助开发者更快速地构建微服务应用。 4. 兼容性强:Go-Zero 支持多种传输协议和数据格式,包括 HTTP、gRPC、JSON 等,并且可以与其他框架和组件进行无缝集成。 Go-Zero 框架的缺点: 1. 相对较新:Go-Zero 是一个相对较新的框架,与一些其他框架相比,可能还没有得到广泛的应用和验证。 2. 社区相对较小:相对于一些其他流行的框架,Go-Zero 的社区规模可能相对较小,获取支持和资源可能相对有限。 关于哪个框架使用的人更多,这取决于具体的使用场景、个人偏好和团队需求。目前来说,Go-Micro 可能更受欢迎一些,因为它具有更长时间的发展历史和更大的社区规模。但是随着 Go-Zero 的不断发展和改进,它也在逐渐受到更多人的关注和使用。最终选择哪个框架,建议根据项目需求、团队经验和个人偏好进行评估和选择。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值