一、RPC
1.1 what & why need?
一言以蔽之,RPC 是分布式系统的基石。
RPC(Remote Procedure Call),中文名为远程过程调用。它最初由 Xerox 公司提出并对其定义为:
“RPC 是一种语言级别的通讯协议,它允许运行于一台计算机上的程序以某种管道作为通讯媒介,去调用另外一个地址空间”。
- 从类型上说,RPC 是一种通讯协议;
- 从功能上说,RPC 实现的功能是在一台机器上调用另一台机器的地址空间,该地址空间可能对应函数、变量等;
- 从实现手段上说,RPC 需要借助计算机网络中的传输层来实现管道通讯。传输层的管道通信可以理解为通过 IP 地址和端口号来确定通信管道的两端。
随着互联网的发展,“客户端—服务器—数据库” 的单体架构(Monolithic Architecture)已无法满足日益复杂的业务逻辑和日渐增多的业务访问,因此需要转向微服务架构(Microservices Architecture)。以下是 Google Cloud Platform 中对微服务的定义:
微服务架构(通常简称为微服务)是指开发应用所用的一种架构形式。通过微服务,可将大型应用分解成多个独立的组件,其中每个组件都有各自的责任领域。在处理一个用户请求时,基于微服务的应用可能会调用许多内部微服务来共同生成其响应。
![](https://i-blog.csdnimg.cn/blog_migrate/d60994e8f0fca52ca1bb6df4ac822513.jpeg)
根据上述定义可知,对于微服务架构中的一个请求,各个服务间需要相互调用才能最终生成对应的响应,因此服务间的调用问题就是成为一个关键问题。而 RPC 正好能解决该问题,所以会有人说 “要想搞懂微服务,先搞定RPC”。在微服务架构中加入合适的 RPC 框架,不仅能降低微服务架构的开发成本,也能提高服务间的调用效率。
二、gRPC
2.1 一个通用开源框架
gRPC 官方文档中文版:开源中国。似乎很久没有更新了,最新的英文文档请参考 docs。
gRPC 最开始由 Google 开发,是一款语言中立、平台中立、开源的远程过程调用(RPC)系统。gRPC 基于 HTTP/2 标准设计,带来诸如双向流、流控、头部压缩、单 TCP 连接上的多路复用请求等特性。这些特性使得其在移动设备上表现更好,更省电和节省空间占用。根据 grpc.io 的数据,目前该框架已支持多种语言,包括 Java、C#、Go、C++、Dart、Python、Kotlin、PHP、Ruby、Objective-C 和 Node。
2.2 配置 gRPC 环境
- 在 go.mod 中配置 gRPC 核心库(会放在
$GOPATH/pkg
中)
$ go get google.golang.org/grpc
- 下载对应语言的代码生成插件(会放在
$GOPATH/bin
中)
$ go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
$ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
![](https://i-blog.csdnimg.cn/blog_migrate/e03b2ffa01ddcc008ffcbed30704f3df.png)
三、Golang 实现简单的 RPC 服务
3.1 编写 proto 文件并生成对应的 Go 代码
proto 文件的部分语法
service
服务名 {rpc
rpc方法名(请求消息)returns
(响应消息) { } }message
消息名 { 变量类型 变量名 = 在消息中的位次; }
编写 server.proto 文件,声明语法为 proto3
,service
类似于 Go 中的 func
,message
类似于 Go 中的 struct
。rpc
表示 ShakeHands 是 RPC 方法,接受请求 ShakeReq
,返回结果 ShakeRes
。
syntax = "proto3";
option go_package = ".;service"; // 生成的.go文件在哪个目录的哪个包下(用;隔开 目录;包名)
service ShakeHands {
rpc ShakeHands(ShakeReq) returns (ShakeRes) {}
}
message ShakeReq {
string requestName = 1; // 这是在定义变量在 message 中的位置
}
message ShakeRes {
string responseMsg = 1;
}
然后进入 proto 文件所在目录,执行 protoc 命令。
$ cd path/to/proto/file
$ protoc --go_out=. server.proto
$ protoc --go-grpc_out=. server.proto
生成的文件如下,–go_out 表示 .pb.go
文件输出的目录位置,–go-grpc_out 表示 _grpc.pb.go
文件输出的目录位置。
![](https://i-blog.csdnimg.cn/blog_migrate/37c1dc3204cecf1cdcbe392499a2e265.png)
3.2 实现服务端代码
主要流程如下:
- 实现
_grpc.pb.go
文件中定义的服务(或叫 RPC 方法)。 - 开启 TCP 端口。
- 创建 gRPC 服务并在 gRPC 服务中注册实现了的服务。
- 启动 gRPC 服务。
package main
import (
"context"
"fmt"
"net"
"google.golang.org/grpc"
pb "gRPC/server/proto"
)
type server struct {
pb.UnimplementedShakeHandsServer
}
func (s *server) ShakeHands(ctx context.Context, req *pb.ShakeReq) (res *pb.ShakeRes, err error) {
return &pb.ShakeRes{ResponseMsg: "res: hello!" + req.RequestName}, nil
}
func main() {
listen, _ := net.Listen("tcp", "127.0.0.1:9999") // 开启tcp端口
grpcServer := grpc.NewServer() // 创建grpc服务
pb.RegisterShakeHandsServer(grpcServer, &server{}) // 在grpc服务中注册我们的服务
err := grpcServer.Serve(listen) // 启动服务
if err != nil {
fmt.Println("server error!")
return
}
}
3.3 不带安全认证的客户端
客户端的逻辑很简单,就是建立与服务端的连接:
- 拨号,发起连接。
grpc.Dial()
服务端的 IP 地址和端口; - 通过
_grpc.pb.go
文件提供的方法创建客户端实例; - 调用 RPC 方法发起请求。
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
pb "gRPC/server/proto"
)
func main() {
conn, err := grpc.Dial("127.0.0.1:9999", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
fmt.Println("connection error!")
}
defer conn.Close()
client := pb.NewShakeHandsClient(conn) // 建立连接
resp, err := client.ShakeHands(context.Background(), &pb.ShakeReq{RequestName: "test"})
if err != nil {
fmt.Println(err.Error())
}
fmt.Println(resp.GetResponseMsg())
}