RPC(Remote Procedure Call Protocol)远程过程调用协议,目标就是让远程服务调用更加简单、透明。RPC 框架负责屏蔽底层的传输方式(TCP 或者 UDP)、序列化方式(XML/Json/ 二进制)和通信细节,服务调用者可以像调用本地接口一样调用远程的服务提供的接口,而不需要关心底层通信细节和调用过程。
其大致过程如上图所示。
当我们的业务越来越多、应用也越来越多时,自然的,我们会发现有些功能已经不能简单划分开来或者划分不出来。我们平时写的项目大多数所有的模块都在一起,部署一台服务器上来,但是这也有几个问题:
此时可以将公共业务逻辑抽离出来以及将服务进行模块拆分,将之组成独立的服务 Service 应用,而原有的、新增的应用都可以与那些独立的 Service 应用 交互,以此来完成完整的业务功能,所以我们急需一种高效的应用程序之间的通讯手段来完成这种需求,RPC 大显身手的时候来了。
但是这也有一些我们需要思考的点:
要让网络通信细节对使用者透明,我们需要对通信细节进行封装,我们先看下一个 RPC 调用的流程涉及到哪些通信细节:
而RPC的目标就是要 2~8(上面的2-8) 这些步骤都封装起来,让用户对这些细节透:
1.grpc 的相关概念
什么是gRPC? gRPC 是一个高性能、通用的开源 RPC 框架,其由 Google 2015 年主要面向移动应用开发并基于 HTTP/2 协议标准而设计,基于 ProtoBuf 序列化协议开发,且支持众多开发语言。
由于是开源框架,通信的双方可以进行二次开发,所以客户端和服务器端之间的通信会更加专注于业务层面的内容,减少了对由 gRPC 框架实现的底层通信的关注。
2.gRPC的特点
其交互过程大概如下:
1.交换机在开启 gRPC 功能后充当 gRPC 客户端的角色,采集服务器充当 gRPC 服务器角色;
2.交换机会根据订阅的事件构建对应数据的格式(GPB/JSON),通过 Protocol Buffers 进行编写 proto 文件,交换机与服务器建立 gRPC 通道,通过 gRPC 协议向服务器发送请求消息;
3.服务器收到请求消息后,服务器会通过 Protocol Buffers 解译 proto 文件,还原出最先定义好格式的数据结构,进行业务处理;
4.数据处理完后,服务器需要使用 Protocol Buffers 重编译应答数据,通过 gRPC 协议向交换机发送应答消息;
5.交换机收到应答消息后,结束本次的 gRPC 交互。
ProtoBuffer 是一种更加灵活、高效的数据格式,与 XML、JSON 类似,在一些高性能且对响应速度有要求的数据传输场景非常适用。
ProtoBuffer 在 gRPC 的框架中主要有三个作用:
为什么 ProtoBuf 会提高传输效率呢?
我们知道使用 XML、JSON 进行数据编译时,数据文本格式更容易阅读,但进行数据交换时,设备就需要耗费大量的 CPU 在 I/O 动作上,自然会影响整个传输速率。
Protocol Buffers 不像前者,它会将字符串进行序列化后再进行传输,即二进制数据。在这里不详细说明,有兴趣的可以看博客的另外一篇博客.
为什么 ProtoBuf 会提高传输效率呢?
我们知道使用 XML、JSON 进行数据编译时,数据文本格式更容易阅读,但进行数据交换时,设备就需要耗费大量的 CPU 在 I/O 动作上,自然会影响整个传输速率。
Protocol Buffers 不像前者,它会将字符串进行序列化后再进行传输,即二进制数据
gRPC 能够做到跨平台,多语言,很大一部分得益于Protobuffer自动的编译器。前面提到的 proto 文件就是通过编译器进行编译的,proto 文件需要编译生成一个类似库文件,基于库文件才能真正开发数据应用。
具体用什么编程语言编译生成这个库文件呢?由于现网中负责网络设备和服务器设备的运维人员往往不是同一组人,运维人员可能会习惯使用不同的编程语言进行运维开发,那么 Protocol Buffers 其中一个优势就能发挥出来——跨语言。
在这里博主选择go语言进行演示grpc的使用,其他铁子也可以自行选择提前语言进行使用。在使用grpc之前各位铁子要安装好这个protobuf。
安装grpc最核心的库
go get google.golang.org/grpc
上面安装是安装了这个protocol的编译器,它可以生成各种语言因此除了这个编译器我们还需要配合各个语言的代码生成工具,对于golang来说我们叫做protoc-gen-go
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2
安装好之后我们就可以开始编写proto文件
syntax="proto3";
option go_package=".;service";//这部分内容是关于生成的go代码,在那个目录的包中,service代表生成的go文件的package是service
message HelloRequest{
bytes requestName=1;
}
message HelloResponse{
bytes responseMsg=1;
}
service SayHello{
rpc SayHello(HelloRequest)returns(HelloResponse);
}
编写完成proto文件之后我们开始使用这个命令生成代码
protoc --go_out=. hello.proto
protoc --go-grpc_out=. hello.proto
package main
import (
"context"
"errors"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
service "http/gRpc/server/proto"
"net"
)
type server struct {
*service.UnimplementedSayHelloServer //包含定义的sayHello方法的结构体
}
func (s *server) SayHello(ctx context.Context, req *service.HelloRequest) (*service.HelloResponse, error) {
//获取源数据信息
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, errors.New("未传输token")
}
var appId string
var appKey string
if v, ok := md["appid"]; ok {
appId = v[0]
}
if v, ok := md["appkey"]; ok {
appKey = v[0]
}
fmt.Println("the appkey is" + appKey)
//这里应该要去数据库里面查询,用户id appid
if appId != "ksy" || appKey != "122" {
return nil, errors.New("错误" + appKey + appId)
}
return &service.HelloResponse{
ResponseMsg: []byte(req.RequestName),
}, nil
}
func Test() {
//开启监听
listen, err := net.Listen("tcp", "127.0.0.1:8909")
if err != nil {
panic(err)
}
//创建grpc服务器
grpcServer := grpc.NewServer()
//注册进来,在grpc 服务端当中注册我们编写的服务
service.RegisterSayHelloServer(grpcServer, &server{})
//启动服务
if err := grpcServer.Serve(listen); err != nil {
panic(err)
}
}
func main() {
Test()
}
这个客户端对应代码
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
service "http/gRpc/server/proto"
)
func Test() {
//链接server端,此处禁用安全链接
conn, err := grpc.Dial(":8909", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
panic(err)
}
defer conn.Close()
//建立链接
client := service.NewSayHelloClient(conn)
//发起远程过程调用
resp, err := client.SayHello(context.Background(), &service.HelloRequest{RequestName: []byte("ksy")})
fmt.Println(resp)
}
type ClientTokenAuth struct {
}
// GetRequestMetadata 重写接口
func (c *ClientTokenAuth) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
return map[string]string{
"appid": "ksy",
"appKey": "122",
}, nil
}
func (c *ClientTokenAuth) RequireTransportSecurity() bool {
//不开启安全传输
return false
}
// TestToke token认证
func TestToke() {
var opts []grpc.DialOption//选项
opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))
opts = append(opts, grpc.WithPerRPCCredentials(new(ClientTokenAuth)))
conn, err := grpc.Dial(":8909", opts...)
if err != nil {
panic(err)
}
defer conn.Close()
//建立链接
client := service.NewSayHelloClient(conn)
//发起远程过程调用
resp, err := client.SayHello(context.Background(), &service.HelloRequest{RequestName: []byte("ksy")})
fmt.Println(err)
fmt.Println(resp)
}
func main() {
TestToke()
}
下面我们可以把客户端和服务端启动起来,就可以进行通信了。