目录
在分布式系统开发中,RPC(远程过程调用)是一种常见的通信机制,它允许不同进程或不同机器上的程序相互调用对方的函数,就像调用本地函数一样方便。今天我们要深入探讨的是zrpc
包,它为我们提供了便捷且功能丰富的 RPC 客户端实现。下面将通过具体示例详细介绍如何使用这个包的源码来完成一系列 RPC 相关的操作。
一、准备工作
首先,确保你的项目已经正确导入了zrpc
包及其相关依赖。在我们之前解读的源码中,zrpc
包依赖了一些其他的包,如go-zero
框架中的部分模块(core/conf
、core/logx
等)以及google.golang.org/grpc
及其相关子包(keepalive
)等,要确保这些依赖都已正确安装并配置在你的项目环境中。
二、创建 RPC 客户端
1. 配置结构体
使用zrpc
包创建 RPC 客户端,首先需要准备好配置信息。我们可以通过RpcClientConf
结构体来配置客户端的各项参数。例如:
package main
import (
"fmt"
"github.com/zeromicro/go-zero/core/conf"
"github.com/zeromicro/go-zero/zrpc"
)
func main() {
var rpcClientConf zrpc.RpcClientConf
// 假设这里从配置文件中读取配置信息并填充到结构体中,这里简单示例设置部分参数
rpcClientConf.App = "your_app_name"
rpcClientConf.Token = "your_token"
rpcClientConf.Timeout = 5000 // 设置连接超时时间为5秒(单位:毫秒)
rpcClientConf.KeepaliveTime = 30000 // 设置连接保活时间为30秒(单位:毫秒)
rpcClientConf.NonBlock = true // 设置为非阻塞模式
}
在上述代码中,我们创建了一个RpcClientConf
结构体实例,并设置了一些常见的参数,如应用名称、认证令牌、连接超时时间、连接保活时间以及连接模式等。实际应用中,你可能会从配置文件中读取这些参数并填充到结构体中,这里只是为了演示方便做了简单的设置。
2. 创建客户端实例
有了配置结构体后,我们就可以使用zrpc
包提供的函数来创建 RPC 客户端实例了。zrpc
包提供了多种创建客户端的方式,这里我们介绍两种常见的方法。
使用MustNewClient
函数
MustNewClient
函数会在创建客户端过程中如果出现任何错误,直接记录错误日志并退出程序。示例如下:
package main
import (
"fmt"
"github.com/zeromicro/go-zero/core/conf"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/zrpc"
)
func main() {
var rpcClientConf zrpc.RpcClientConf
// 假设这里从配置文件中读取配置信息并填充到结构体中,这里简单示例设置部分参数
rpcClientConf.App = "your_app_name"
rpcClientConf.Token = "your_token"
rpcClientConf.Timeout = 5000 // 设置连接超时时间为5秒(单位:毫秒)
rpcClientConf.KeepaliveTime = 30000 // 设置连接保活时间为30秒(单位:毫秒)
rpcClientConf.NonBlock = true // 设置为非阻塞模式
client := zrpc.MustNewClient(rpcClientConf)
fmt.Println("RPC客户端创建成功:", client)
}
在上述代码中,我们通过MustNewClient
函数传入之前准备好的RpcClientConf
结构体实例,成功创建了一个 RPC 客户端实例。如果在创建过程中出现错误,程序会直接退出并记录错误日志。
使用NewClient
函数
NewClient
函数则会返回创建客户端的结果以及可能出现的错误,这样我们可以根据错误情况进行更灵活的处理。示例如下:
package main
import (
"fmt"
"github.com/zeromicro/go-zero/core/conf"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/zrpc"
)
func main() {
var rpcClientConf zrpc.RpcClientConf
// 假设这里从配置文件中读取配置信息并填充到结构体中,这里简单示例设置部分参数
rpcClientConf.App = "your_app_name"
rpcClientConf.Token = "your_token"
rpcClientConf.Timeout = 5000 // 设置连接超时时间为5秒(单位:毫秒)
rpcClientConf.KeepaliveTime = 30000 // 设置连接保活时间为30秒(单位:毫秒)
rpcClientConf.NonBlock = true // 设置为非阻塞模式
client, err := zrpc.NewClient(rpcClientConf)
if err!= nil {
logx.Error("创建RPC客户端失败:", err)
return
}
fmt.Println("RPC客户端创建成功:", client)
}
在上述代码中,我们使用NewClient
函数创建 RPC 客户端实例,并通过判断返回的错误值来处理可能出现的创建失败情况。
三、设置客户端参数
1. 认证凭据设置
在创建客户端时,我们可以设置认证凭据,以便在进行 RPC 调用时服务端能够识别客户端的身份。这在NewClient
函数的实现中已经有所体现,当RpcClientConf
结构体中的HasCredential()
方法返回true
时(即配置了认证凭据相关信息),会通过以下方式设置认证凭据:
if c.HasCredential() {
opts = append(opts, WithDialOption(grpc.WithPerRPCCredentials(&auth.Credential{
App: c.App,
Token: c.Token,
})))
}
这里通过WithDialOption
别名函数结合grpc.WithPerRPCCredentials
函数,将RpcClientConf
结构体中的App
和Token
字段值设置到auth.Credential
结构体实例中,从而为客户端设置了认证凭据。
2. 连接模式设置
我们可以设置客户端的连接模式为非阻塞模式。在RpcClientConf
结构体中,通过设置NonBlock
字段为true
,并在创建客户端时,NewClient
函数会根据这个配置添加相应的配置选项:
if c.NonBlock {
opts = append(opts, WithNonBlock())
}
这样就将客户端的连接模式设置为了非阻塞模式,在进行连接操作时不会阻塞程序的执行,而是会立即返回连接结果(成功或失败)。
3. 连接超时设置
设置连接超时时间可以避免客户端在连接服务端时无限期地等待。在RpcClientConf
结构体中设置Timeout
字段的值(单位为毫秒),然后在创建客户端时,NewClient
函数会将这个超时时间转换为time.Duration
类型并添加相应的配置选项:
if c.Timeout > 0 {
opts = append(opts, WithTimeout(time.Duration(c.Timeout)*time.Millisecond))
}
这样就为客户端设置了连接超时时间,当超过这个时间还未成功连接到服务端时,会返回连接失败的错误。
4. 连接保活设置
为了确保连接在一段时间内没有数据交互时不会被自动关闭,我们可以设置连接保活参数。在RpcClientConf
结构体中设置KeepaliveTime
字段的值(单位为毫秒),然后在创建客户端时,NewClient
函数会通过以下方式设置保活参数:
if c.KeepaliveTime > 0 {
opts = append(opts, WithDialOption(grpc.WithKeepaliveParams(keepalive.ClientParameters{
Time: c.KeepaliveTime,
})))
}
这样就为客户端设置了连接保活时间,当连接在没有数据交互达到设置的保活时间时,会按照gRPC
框架的保活机制发送保活探测消息,以维持连接的活性。
四、进行 RPC 调用
1. 定义 RPC 消息结构体
在进行 RPC 调用之前,我们需要定义 RPC 消息结构体,用于在客户端和服务端之间传递数据。虽然在之前解读的源码中没有明确给出RpcMessage
结构体的完整定义,但我们可以假设一个简单的示例如下:
package main
import (
"github.com/zeromicro/go-zero/zrpc"
)
// RpcMessage 用于表示RPC消息结构体
type RpcMessage struct {
Payload []byte
MessageType string
TargetService string
}
在上述代码中,我们定义了一个RpcMessage
结构体,包含了Payload
(消息负载,用于存储实际要传递的数据)、MessageType
(消息类型,用于标识消息的种类,如请求、响应等)和TargetService
(目标服务,用于指定消息要发送到的服务名称)等字段。实际应用中,你可以根据具体的 RPC 协议和业务需求来定义RpcMessage
结构体的具体内容。
2. 发送 RPC 消息
假设我们已经创建好了 RPC 客户端实例client
,并且定义好了RpcMessage
结构体实例rpcMsg
,我们就可以通过客户端实例的Send
方法来发送 RPC 消息了。虽然在之前解读的源码中没有完整展示Send
方法的实现细节,但我们可以假设其基本用法如下:
package main
import (
"context"
"fmt"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/zrpc"
)
func main() {
var rpcClientConf zrpc.RpcClientConf
// 假设这里从配置文件中读取配置信息并填充到结构体中,这里简单示例设置部分参数
rpcClientConf.App = "your_app_name"
rpcClientConf.Token = "your_token"
rpcClientConf.Timeout = 5000 // 设置连接超时时间为5秒(单位:毫秒)
rpcClientConf.KeepaliveTime = 30000 // 设置连接保活时间为30秒(单位:毫秒)
rpcClientConf.NonBlock = true // 设置为非阻塞模式
client, err := zrpc.NewClient(rpcClientConf)
if err!= nil {
logx.Error("创建RPC客户端失败:", err)
return
}
// 定义RPC消息结构体实例
rpcMsg := &RpcMessage{
Payload: []byte("Hello, RPC!"),
MessageType: "REQUEST",
TargetService: "your_target_service",
}
ctx := context.Background()
err = client.Send(ctx, rpcMsg)
if err!= nil {
logx.Error("发送RPC消息失败:", err)
return
}
fmt.Println("RPC消息发送成功")
}
在上述代码中,我们首先创建了 RPC 客户端实例,然后定义了一个RpcMessage
结构体实例,设置了相应的字段值。接着通过context.Background()
创建了一个上下文环境,最后使用客户端实例的Send
方法将RpcMessage
结构体实例发送出去。如果发送过程中出现错误,会记录错误日志并返回。
3. 接收 RPC 消息
在发送 RPC 消息后,我们通常还需要接收服务端返回的消息。同样,虽然源码中没有完整展示Recv
方法的实现细节,但我们可以假设其基本用法如下:
package main
import (
"context"
"fmt"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/zrpc"
)
func main() {
var rpcClientConf zrpc.RpcClientConf
// 假设这里从配置文件中读取配置信息并填充到结构体中,这里简单示例设置部分参数
rpcClientConf.App = "your_app_name"
rpcClientConf.Token = "your_token"
rpcClientConf.Timeout = 5000 // 设置连接超时时间为5秒(单位:毫秒)
rpcClientConf.KeepaliveTime = 3000快照点:30000 // 设置连接保活时间为30秒(单位:毫秒)
rpcClientConf.NonBlock = true // 设置为非阻塞模式
client, err := zrpc.NewClient(rpcClientConf)
if err!= nil {
logx.Error("创建RPC客户端失败:", err)
return
}
// 定义RPC消息结构体实例
rpcMsg := &RpcMessage{
Payload: []byte("Hello, RPC!"),
MessageType: "REQUEST",
TargetService: "your_target_service",
}
ctx := context.Background()
err = client.Send(ctx, rpcMsg)
if err!= nil {
logx.Error("发送RPC消息失败:", err)
return
}
resp, err := client.Recv(ctx)
if err!= nil {
logx.Error("接收RPC消息失败:", err)
return
}
fmt.Println("接收到的RPC消息:", resp)
}
在上述代码中,在成功发送 RPC 消息后,我们使用客户端实例的Recv
方法来接收服务端返回的消息。如果接收过程中出现错误,会记录错误日志并返回。这里假设Recv
方法返回的是一个与发送的RpcMessage
结构体类似的结构体实例,用于存储接收到的消息内容,实际应用中,你需要根据具体的 RPC 协议和服务端的返回格式来处理接收到的消息。
五、其他常用操作
1. 获取底层 grpc.ClientConn 实例
有时候我们可能需要获取与 RPC 客户端实例相关的底层grpc.ClientConn
实例,以便进行一些底层的操作,比如检查连接状态等。我们可以通过RpcClient
结构体的Conn
方法来获取:
package main
import (
"fmt"
"github.com/zeromicro/go-zero/core/conf"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/zrpc"
"google.golang.org/grpc"
)
func main() {
var rpcClientConf zrpc.RpcClientConf
// 假设这里从配置文件中读取配置信息并填充到结构体中,这里简单示例设置部分参数
rpcClientConf.App = "your_app_name"
rpcClientConf.Token = "your_token"
rpcClientConf.Timeout = 5000 // 设置连接超时时间为5秒(单位:毫秒)
rpcClientConf.KeepaliveTime = 30000 // 设置连接保活时间为30秒(单位:毫秒)
rpcClientConf.NonBlock = true // 设置为非阻塞模式
client, err := zrpc.NewClient(rpcClientConf)
if err!= nil {
logx.Error("创建RPC客户端失败:", err)
return
}
conn := client.Conn()
fmt.Println("获取到的底层grpc.ClientConn实例:", conn)
}
在上述代码中,我们通过调用client.Conn()
方法获取到了与 RPC 客户端实例相关的底层grpc.ClientConn
实例,并将其打印出来。
2. 禁用特定方法的日志记录内容
如果我们不想在进行某些 RPC 调用时记录详细的调用内容,我们可以使用DontLogClientContentForMethod
方法来禁用对指定方法的日志记录内容。示例如下:
package main
import (
"fmt"
"github.com/zeromicro/go-zero/core/conf"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/zrpc"
)
func main() {
var rpcClientConf zrpc.RpcClientConf
// 假设这里从配置文件中读取配置信息并填充到结构体中,这里简单示例设置部分参数
rpcClientConf.App = "your_app_name"
rpcClientConf.Token = "your_token"
rpcClientConf.Timeout = 5000 // 设置连接超时时间为5秒(单位:毫秒)
rpcClientConf.KeepaliveTime = 30000 // 设置连接保活时间为30秒(单位:毫秒)
rpcClientConf.NonBlock = true // 设置为非阻塞模式
client, err := zrpc.NewClient(rpcClientConf)
if err!= nil {
logx.Error("创建RPC客户端失败:", err)
return
}
// 禁用指定方法的日志记录内容
zrpc.DontLogClientContentForMethod("your_method_name")
}
在上述代码中,我们通过调用zrpc.DontLogClientContentForMethod("your_method_name")
方法,禁用了对指定方法(这里假设为your_method_name
)的日志记录内容。
3. 设置客户端慢阈值
我们可以设置客户端的慢阈值,当某次 RPC 调用的时间超过了设置的慢阈值时,可能会触发相应的慢调用处理逻辑(比如记录慢调用日志等)。我们可以通过SetClientSlowThreshold
方法来设置客户端慢阈值。示例如下:
package main
import (
"fmt"
"github.com/zeromicro/go-zero/core/conf"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/zrpc"
"time"
)
func main() {
var rpcClientConf zrpc.RpcClientConf
// 假设这里从配置文件中读取配置信息并填充到结构体中,这里简单示例设置部分参数
rpcClientConf.App = "your_app_name"
rpcClientConf.Token = "your_token"
rpcClientConf.Timeout = 5000 // 设置连接超时时间为5秒(单位:毫秒)
rpcClientConf.KeepaliveTime = 30000 // 设置连接保活时间为30秒(单位:毫秒)
rpcClientConf.NonBlock = true // 设置为非阻塞模式
client, err := zrpc.NewClient(rpcClientConf)
if err!= nil {
logx.Error("创建RPC客户端失败:", err)
return
}
// 设置客户端慢阈值为10秒(单位:毫秒)
zrpc.SetClientSlowThreshold(time.Second * 10)
// 后续可进行RPC调用等操作,当某次调用时间超过此阈值,会触发相应处理逻辑
// 这里可继续添加相关RPC调用代码,如发送和接收消息等操作,示例如下
// 定义RPC消息结构体实例
rpcMsg := &RpcMessage{
Payload: []byte("Hello, RPC!"),
MessageType: "REQUEST",
TargetService: "your_target_service",
}
ctx := context.Background()
err = client.Send(ctx, rpcMsg)
if err!= nil {
logx.Error("发送RPC消息失败:", err)
return
}
resp, err := client.Recv(ctx)
if err!= nil {
logx.Error("接收RPC消息失败:", err)
return
}
fmt.Println("接收到的RPC消息:", resp)
}
在上述代码中,我们通过调用 zrpc.SetClientSlowThreshold(time.Second * 10)
方法,将客户端的慢阈值设置为 10 秒(这里以 time.Second
为单位进行设置,实际转换为了毫秒在内部处理,与前面设置其他时间参数的方式类似)。当后续进行 RPC 调用时,如果某次调用所花费的时间超过了这个设置的慢阈值,就可能会触发相应的慢调用处理逻辑,比如在 zrpc
包内部可能会记录慢调用的日志等操作,具体的处理逻辑取决于 zrpc
包内部对于慢阈值处理的实现细节。
4. 设置带有超时的 RPC 调用选项
在进行 RPC 调用时,我们还可以为每次调用单独设置超时时间,以避免某次调用因为网络问题或服务端处理缓慢等原因导致无限期等待响应。我们可以利用 WithCallTimeout
方法来返回一个带有指定超时时间的调用选项,然后在发送 RPC 消息时使用这个调用选项。示例如下:
package main
import (
"context"
"fmt"
"github.com/zeromicro/go-zero/core/conf"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/zrpc"
"google.golang.org/grpc"
"time"
)
func main() {
var rpcClientConf zrpc.RpcClientConf
// 假设这里从配置文件中读取配置信息并填充到结构体中,这里简单示例设置部分参数
rpcClientConf.App = "your_app_name"
rpcClientConf.Token = "your_token"
rpcClientConf.Timeout = 5000 // 设置连接超时时间为5秒(单位:毫秒)
rpcClientConf.KeepaliveTime = 30000 // 设置连接保活时间为30秒(单位:毫秒)
rpcClientConf.NonBlock = true // 设置为非阻塞模式
client, err := zrpc.NewClient(rpcClientConf)
if err!= nil {
logx.Error("创建RPC客户端失败:", err)
return
}
// 定义RPC消息结构体实例
rpcMsg := &RpcMessage{
Payload: []byte("Hello, RPC!"),
MessageType: "REQUEST",
TargetService: "your_target_service",
}
ctx := context.Background()
// 设置本次RPC调用的超时时间为8秒(单位:毫秒)
callOption := zrpc.WithCallTimeout(time.Second * 8)
err = client.Send(ctx, rpcMsg, callOption)
if err!= nil {
logx.Error("发送RPC消息失败:", err)
return
}
resp, err := client.Recv(ctx)
if err!= nil {
logx.Error("接收RPC消息失败:", err)
return
}
fmt.Println("接收到的RPC消息:", resp)
}
在上述代码中,我们首先通过 zrpc.WithCallTimeout(time.Second * 8)
方法创建了一个带有指定超时时间(这里设置为 8 秒)的调用选项 callOption
。然后在调用客户端实例的 Send
方法发送 RPC 消息时,将这个调用选项作为额外的参数传入(注意,这里假设 Send
方法在实际实现中支持传入这样的调用选项参数,具体要根据 zrpc
包内部 Send
方法的真实定义来确定)。这样,当发送 RPC 消息后,如果在设置的 8 秒内没有收到服务端的响应,就会返回一个超时错误,避免程序无限期地等待下去。
六、总结
通过以上详细的示例,我们全面地了解了如何使用 zrpc
包的源码来完成一系列 RPC 相关的操作。从客户端的创建、各种参数的设置(如认证凭据、连接模式、超时时间、保活时间等),到进行 RPC 消息的发送和接收,以及其他一些常用的操作(如获取底层连接实例、禁用特定方法的日志记录、设置慢阈值、设置带有超时的调用选项等)。在实际的分布式系统开发中,熟练掌握这些操作对于实现高效、可靠的 RPC 通信至关重要。