一、RPC

1.1 what & why need?

一言以蔽之,RPC 是分布式系统的基石

RPC(Remote Procedure Call),中文名为远程过程调用。它最初由 Xerox 公司提出并对其定义为:
“RPC 是一种语言级别的通讯协议,它允许运行于一台计算机上的程序以某种管道作为通讯媒介,去调用另外一个地址空间”。

  1. 类型上说,RPC 是一种通讯协议;
  2. 功能上说,RPC 实现的功能是在一台机器上调用另一台机器的地址空间,该地址空间可能对应函数、变量等;
  3. 实现手段上说,RPC 需要借助计算机网络中的传输层来实现管道通讯。传输层的管道通信可以理解为通过 IP 地址和端口号来确定通信管道的两端。

随着互联网的发展,“客户端—服务器—数据库” 的单体架构(Monolithic Architecture)已无法满足日益复杂的业务逻辑和日渐增多的业务访问,因此需要转向微服务架构(Microservices Architecture)。以下是 Google Cloud Platform 中对微服务的定义:

微服务架构(通常简称为微服务)是指开发应用所用的一种架构形式。通过微服务,可将大型应用分解成多个独立的组件,其中每个组件都有各自的责任领域。在处理一个用户请求时,基于微服务的应用可能会调用许多内部微服务来共同生成其响应。

根据上述定义可知,对于微服务架构中的一个请求,各个服务间需要相互调用才能最终生成对应的响应,因此服务间的调用问题就是成为一个关键问题。而 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 环境

$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
三、Golang 实现简单的 RPC 服务

3.1 编写 proto 文件并生成对应的 Go 代码

proto 文件的部分语法

servicerpcreturnsmessage
proto3servicefuncmessagestructrpcShakeReqShakeRes
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
.pb.go_grpc.pb.go

3.2 实现服务端代码

主要流程如下:

_grpc.pb.go
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()_grpc.pb.go
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())
}

3.4 进行 SSL/TLS 安全认证