RPC
概念
RPC(Remote Procedure Call)远程过程调用。是一种通过网络向远程计算机请求服务信息,但又不需要了解底层网络技术的通信方式。就是像调用本地服务一样调用远程服务。
RPC采用客户端/服务端的模式,通过request-reponse消息模式实现。
实现步骤
RPC的主要实现步骤如下(加粗部分为rpc框架要实现的步骤):
- 客户端发起本地连接请求连接服务端的服务;
- 客户端程序句柄(stub)负责将客户端请求的消息序列化成二进制消息;
- 客户端通过网络模块将消息发送至服务端;
- 服务端程序句柄(stub)将收到的消息进行反序列化;
- 服务端程序句柄(stub)根据解析后的方法调用本地服务;
- 服务端上的服务处理完成后将消息发送至程序句柄(stub);
- 服务端程序句柄(stub)将消息序列化成二进制消息;
- 服务端通过网络模块将消息发送至客户端;
- 客户端程序句柄(stub)将消息反序列化;
- 客户端程序句柄(stub)根据解析后的方法调用本地服务;
实现原理
RPC 架构主要包括三部分:
- 服务注册中心(Registry),负责将本地服务发布成远程服务,管理远程服务,提供给服务消费者使用,主要用于实现负载均衡和故障切换。
- RPC Server,服务提供者,提供服务接口定义与服务实现类,服务提供者启动后主动向服务注册中心(Registry)注册机器IP、端口以及提供的服务列表;
- RPC Client,服务消费者,通过远程代理对象调用远程服务,启动时向服务注册中心(Registry)获取服务提供方地址列表。
优缺点
优点
- 对于使用者来说,简单高效,无需关心底层逻辑,仅调用接口即可;
- 主流的rpc框架(如Dubbo、grpc、Hessian等)跨语言支持;
缺点
Stub
Stub是一段用于转换参数的代码。
为什么使用存根(Stub)?
rpc调用中,服务端提供方法和接口实现,客户端调用方法。但往往客户端和服务端位于不同的服务器上,主要有以下差异:
- 编码不同。例如一端是GBK,另一端是UTF-8;
- 大小端不同;
- 编译语言不同。例如一段是c++,另一端是java;
- 内存布局不同;
- 尾款不同。有32/64位之分;
存根(Stub)选取一种中间形式,让两端的数据都可以正确可逆的与中间数据进行转换。常用的中间数据类型包括:纯字符串、XML、JSON、protobuf。
HTTP协议
从RPC的实现中可以看出,RPC的通信需要底层网络协议的支持,HTTP是一种比较好的实现方式。
概念
HTTP(HyperText Transfer Protocol)超文本传输协议,是一个双向协议,可以用来传输文字、图片、音视频等数据。
HTTP协议在3.0版本之前是基于TCP的,3.0版本底层改成了UDP。
HTTP/1.0
优点
- 简单,报文的格式为header+body;
- 灵活、易于扩展;
- 应用广泛、跨平台;
缺点
- 无状态;
- 明文传输;
- 不安全(后续HTTPS做了相关方面的修复);
HTTP/1.1
优点
- 支持tcp长连接,改善了HTTP/1.0短连接造成的开销;
- 支持管道(pipeline)网络传输;
缺点
- 请求 / 响应头部(Header)未压缩,首部信息越多延迟越大;
- 无请求优先级控制;
- 服务器按请求顺序响应,会造成队头阻塞;
- 请求只能从客户端开始,服务端被动响应;
HTTP/2.0
优点
- 头部压缩;
- 二进制格式。头信息和数据体都是二进制,统称为帧:头信息帧和数据帧;
- 数据流;
- 多路复用。一个连接中并发处理多个请求和响应;
- 服务器推送。服务器可以主动向客户端发送消息;
缺点
- 多个请求复用一个TCP连接,一旦发生丢包,就会阻塞住所有的 HTTP 请求;
HTTP/3.0
优点
- 底层协议改为UDP。基于UDP的QUIC协议可以实现类似TCP的可靠性传输,QUIC是一个在UDP之上的伪TCP+TLS+HTTP/2的多路复用协议;
GRPC-WEB
gRPC-Web是一个JavaScript客户端库,使Web应用程序能够直接与后端gRPC服务通信,而不需要HTTP服务器充当中介。
关于详细的grpc-web可以查看:
ProtoBuf
概念
protobuf(protocol buffer)一种用于序列化结构数据的工具,用于数据的存储和交换。是开源的一种数据格式,适合高性能,对响应速度有要求的数据传输场景。因为protobuf是二进制数据格式,需要编码和解码。
序列化和反序列化
在前面介绍rpc实现步骤时讲到,客户端或服务端的程序句柄(stub)在接收自身服务的消息后,会将消息进行序列化,在接收网络模块发送的消息后,会将数据进行反序列化。
- 序列化:将结构数据或者对象转换成能够用于存储和传输的格式;
- 反序列化:将序列化后的数据还原为结构数据和对象;
为什么要进行序列化和反序列化?
- 减少存储空间;
- 方便网络传输;
- 可以在进程间传递对象;
特点
- 使用二进制数据交换格式;
- 使用扩展名为.proto的文件定义存储类的内容;
- 使用自己的编译器protoc;
优点
- 数据压缩性高;
- 传输速度快;
- 序列化速度快;
- 简单、可扩展性高、加密性好;
- 跨平台、跨语言、可扩展性强;
安装protoc
1)下载通用编译器
下载地址:https://github.com/protocolbuffers/protobuf/releases
下载21.7-win64版本即可。
2)添加至环境变量
3)安装go专用的protoc的生成器
go get github.com/golang/protobuf/protoc-gen-go
安装时会出现一些报错,解决方案可参考:https://blog.csdn.net/www_dong/article/details/127149660
示例
1)创建.proto文件
syntax = "proto3";
option go_package = "../service";
package service;
message User {
string username = 1;
int32 age = 2;
}
2)编译生成user.pb.go文件
执行命令
protoc --go_out=./ user.proto
如图所示,成功生成user.pb.go文件。
3)测试
测试程序main.go如下:
package main
import (
"fmt"
"service"
)
func main() {
user := &service.User{
Username: "dong",
Age: 18,
}
marshal, err := proto.Marshal(user)
if err != nil {
panic(err)
}
newUser := &service.User{}
err := proto.Unmarshal(marshal, newUser)
if err != nil {
panic(err)
}
fmt.Println(newUser.String())
}
程序执行结果如下:
proto文件
message
message是定义一个消息类型的关键字。
字段规则
requried: 消息体中必填字段,不设置会导致解码异常;
optional: 消息体中的可选字段;
repeated: 消息体中可重复的字段,重复的值的顺序会被保留;
示例:
.proto中定义类型如下:
message User {
optional string passwd = 3;
repeated string address = 4;
}
生成的go文件中的定义如下:
type User struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Passwd *string `protobuf:"bytes,3,opt,name=passwd,proto3,oneof" json:"passwd,omitempty"`
Address []string `protobuf:"bytes,4,rep,name=address,proto3" json:"address,omitempty"`
}
字段映射
默认值
类型 | 默认值 |
---|
bool | false | 整型 | 0 | string | “” | enum | 第一个枚举元素的值,默认值为0 | message | DEFAULT_INSTANCE |
标识号
在消息体的定义中,每个字段都必须要有一个唯一的标识号,标识号的范围是[0, 2^29-1]。
// 传输的对象
message User {
string username = 1;
int32 age = 2;
optional string passwd = 3;
repeated string address = 4;
}
嵌套消息
message Student
{
message BoyStudent
{
string name = 1;
int32 age = 2;
repeated int32 height = 3;
}
repeated BoyStudent boy = 1;
}
message StudentMsg
{
Student.BoyStudent boy = 1;
}
定义接口
service AccessService {
// rpc 服务的函数名 (传入参数) 返回 (返回参数)
rpc ListDevices(ListRequest) returns (ListResponse);
}
gRPC
概念
概念
gRPC是由开发的一个高性能、通用的开源RPC 框架,主要面向移动应用开发且基于HTTP/2 协议标准而设计,同时支持大多数流行的编程语言。
理念
定义一个服务,指定其能被远程调用的方法(包括参数和返回类型),在服务端实现这个接口,并运行一个gRPC服务器来处理客户端调用。客户端拥有一个存根能够保存像服务端一样的方法。
框架
安装
1)查看GOPATH的路径
go env
2)下载grpc源码
cd $GOPATH
mkdir -p src/google.golang.org
cd src/google.golang.org/
git clone https://github.com/grpc/grpc-go
mv grpc-go grpc
3)下载grpc依赖1
cd $GOPATH
mkdir -p src/golang.org/x
cd src/golang.org/x/
git clone https://github.com/golang/net
git clone https://github.com/golang/text
4)下载grpc依赖2
cd $GOPATH
cd src/google.golang.org/
git clone https://github.com/google/go-genproto
mv go-genproto genproto
依赖需要下载完整,否则使用时报错!!!
实例
1)创建.proto文件
syntax = "proto3";
option go_package="../service";
package service;
message ProductRequest {
int32 prod_id = 1;
}
message ProductReponse {
int32 prod_stock = 1;
}
service ProdService {
rpc GetProductStock(ProductRequest) returns(ProductReponse);
}
2)编译.proto文件
// 指定grpc插件
protoc --go_out=plugins=grpc:./ Product.proto
3)创建接口实现文件
package service
import "context"
var ProductService = &productService{}
type productService struct {
}
func (p *productService) GetProductStock(context context.Context, request *ProductRequest) (*ProductReponse, error) {
stock := p.GetStockById(request.ProdId)
return &ProductReponse{ProdStock: stock}, nil
}
func (p *productService) GetStockById(id int32) int32 {
return 100
}
4)创建服务端
package main
import (
"service"
"google.golang.org/grpc"
"net"
"log"
"fmt"
)
func main() {
rpcServer := grpc.NewServer()
service.RegisterProdServiceServer(rpcServer, service.ProductService)
listener, err := net.Listen("tcp", ":8800")
if err != nil {
log.Fatal("启动监听失败", err)
}
err = rpcServer.Serve(listener)
if err != nil {
log.Fatal("启动服务失败", err)
}
fmt.Println("启动服务成功")
}
5)创建客户端
package main
import (
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"log"
"service"
"context"
"fmt"
)
func main() {
conn, err := grpc.Dial(target:":8800", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatal("服务端连接失败: ", err)
}
defer conn.Close()
productServiceClient := service.NewProdServiceClient(conn)
resq, err := productServiceClient.GetProductStock(context.Background(), &service.ProductRequest{ProdId: 233})
if err != nil {
log.Fatal("调用gRPC方法失败: ", err)
}
fmt.Println("调用gRPC方法成功, ProdStock = ", resq.ProdStock)
}
6)测试
启动grpc_server,开始监听。
go run grpc_server.go
启动grpc_client,连接成功。
go run grpc_client.go
打印如下:
7)工程目录
部分参考:
rpc介绍:http://wjhsh.net/GreenForestQuan-p-11543779.html
HTTP协议:https://blog.csdn.net/qq_34827674/article/details/104732605
grpc-web:https://www.cncf.io/blog/2018/10/24/grpc-web-is-going-ga/
Protobuf官网:https://developers.google.com/protocol-buffers/docs/proto3
gRPC官网:https://grpc.io
|