本章介绍rpc服务,并且在go-zero框架中运用rpc服务,api调用rpc服务,让读者更加理解rpc服务是什么东西
rpc服务介绍
RPC(Remote procedure Call)远程过程调用,是一种进程间通信方式,允许程序调用另一个地址空间的过程或函数,而不用了解底层通信细节。
RPC的主要流程是:
- 客户端通过 Stub 提供的接口调用服务端的方法。
- Stub将方法参数备份成请求数据,通过网络发送给服务端。
- 服务端的Skeleton接收请求数据,找到相应的方法处理。
- 服务端方法执行结果返回给Skeleton。
- 骨架将返回结果发送给客户端。
- Client Stub 接收结果,解包迁移调用方。
RPC的优点:
- 隐藏了底层的通信细节,对用户透明
- 客户端调用方式类似调用本地服务,简单便捷
- 可传输复杂的构造数据
- 面向服务,符合现代设计理念
RPC的缺点:
- 开销大,需要序列化和网络开销
- 不如消息队列灵活
- 调试和测试不方便
在go-zero中使用rpc服务
rpc 服务
编写 proto 文件
- 生成 user.proto 文件
使用命令 goctl rpc template -o user.proto, 生成 user.proto 文件
- user.proto 文件的作用
user.proto 的作用是用来生成 rpc 服务的相关代码。
protobuf 的语法已经超出了 go-zero 的范畴了,这里就不详细展开了。
- 编写 user.proto 文件
鉴于文章篇幅考虑完整的 user.proto 文件请参考 gitee 上的仓库。
生成 rpc 相关代码
- 生成 user rpc 服务相关代码
使用命令 goctl rpc proto -src user.proto -dir . 生成 user rpc 服务的代码。
小结
rpc 服务相关命令:
- 使用命令 goctl rpc template -o user.proto, 生成 user.proto 文件
- 使用命令 goctl rpc proto -src user.proto -dir . 生成 user rpc 服务的代码。
api 服务调用 rpc 服务
A:为什么本节要安排在 rpc 服务的后面?
Q:因为 logic 部分的内容主体就是调用对应的 user rpc 服务,所以我们必须要在 user rpc 的代码已经生成后才能开始这部分的内容。
A:api 网关层调用 rpc 服务的步骤
Q:对这部分目录结构不清楚的,可以参考前文 “api 网关层-api 相关代码-目录介绍”。
- 编辑配置文件 etc/blog-api.yaml,配置 rpc 服务的相关信息。
Name: blog-api
Host: 0.0.0.0
Port: 8888
# 新增 user rpc 服务.
User:
Etcd:
# Hosts 是 user.rpc 服务在 etcd 中的 value 值
Hosts:
- localhost:2379
# Key 是 user.rpc 服务在 etcd 中的 key 值
Key: user.rpc
- 编辑文件 config/config.go
type Config struct {
rest.RestConf
// 手动添加
// RpcClientConf 是 rpc 客户端的配置, 用来解析在 blog-api.yaml 中的配置
User zrpc.RpcClientConf
}
- 编辑文件 internal/svc/servicecontext.go
type ServiceContext struct {
Config config.Config
// 手动添加
// users.Users 是 user rpc 服务对外暴露的接口
User users.Users
}
func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{
Config: c,
// 手动添加
// zrpc.MustNewClient(c.User) 创建了一个 grpc 客户端
User: users.NewUsers(zrpc.MustNewClient(c.User)),
}
}
- 编辑各个 logic 文件,这里以 internal/logic/loginlogic.go 为例
func (l *LoginLogic) Login(req types.ReqUser) (*types.RespLogin, error) {
// 调用 user rpc 的 login 方法
resp, err := l.svcCtx.User.Login(l.ctx, &users.ReqUser{Username: req.Username, Password: req.Password})
if err != nil {
return nil, err
}
return &types.RespLogin{Token: resp.Token}, nil
}
rpc 调用 model 层的代码
rpc 目录结构
rpc 服务我们只需要关注下面加注释的文件或目录即可。
├── etc
│ └── user.yaml # 配置文件,数据库的配置写在这
├── internal
│ ├── config
│ │ └── config.go # config.go 是 yaml 对应的结构体
│ ├── logic # 填充业务逻辑的地方
│ │ ├── createlogic.go
│ │ ├── deletelogic.go
│ │ ├── getalllogic.go
│ │ ├── getlogic.go
│ │ ├── loginlogic.go
│ │ └── updatelogic.go
│ ├── server
│ │ └── usersserver.go
│ └── svc
│ └── servicecontext.go # 封装各种依赖
├── user
│ └── user.pb.go
├── user.go
├── user.proto
└── users
└── users.go
rpc 调用 model 层代码的步骤
- 编辑 etc/user.yaml 文件
Name: user.rpc
ListenOn: 127.0.0.1:8080
Etcd:
Hosts:
- 127.0.0.1:2379
Key: user.rpc
# 以下为手动添加的配置
# mysql 配置
DataSource: root:1234@tcp(localhost:3306)/gozero
# 对应的表
Table: user
# redis 作为换存储
Cache:
- Host: localhost:6379
- 编辑 internal/config/config.go 文件
type Config struct {
// zrpc.RpcServerConf 表明继承了 rpc 服务端的配置
zrpc.RpcServerConf
DataSource string // 手动代码
Cache cache.CacheConf // 手动代码
}
- 编辑 internal/svc/servicecontext.go, 把 model 等依赖封装起来。
type ServiceContext struct {
Config config.Config
Model model.UserModel // 手动代码
}
func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{
Config: c,
Model: model.NewUserModel(sqlx.NewMysql(c.DataSource), c.Cache), // 手动代码
}
}
然后就可以编写业务了。
小结
go-zero中使用RPC调用的主要方式如下:
- 定义proto文件,指定服务接口和消息结构。
- 使用protoc-gen-go生成pb.go文件。
- 服务端实现pb.go中的接口。
- 客户端初始化rpc客户端,调用接口方法。
- Stub负责请求序列化备份发送。
- Server端处理请求并返回。
- Client端接收响应并解包。
go-zero使用grpc实现了RPC框架:
- zrpc提供了抽象的RPC接口。
- znet实现了grpc的传输层。
- service/rpc实现了服务端的grpc Server。
- client/grpc 实现了客户端的grpc Client。
go-zero的RPC调用隐藏了底层网络和序列化,最简单,可以快速构建RPC服务。
主要优点:
- 接口通过协议文件一致定义
- 自动生成客户端和服务器端代码
- 简单的调用方式
- 服务发现和负载均衡
- 超时和重试机制
通过go-zero的RPC框架,可以方便地实现微服务间的远程调用。