1. 概述
- 请求-响应(Unary)
客户端建立连接,发送一个请求,服务端响应一个请求,然后连接就关闭了。
- 适用于简单业务场景,比如查询用户信息、查询商品详情等等。
- 服务端流(server stream)
客户端建立连接,发送一个请求,服务端可以持续不断地返回数据,直到发送完毕。
- 适用于请求的服务端数据太大的场景,比如查询历史订单、加载社交平台的动态流、聊天记录等
- 为什么查询历史订单适合服务端流,请求响应模式不行吗?
- 假如用户的历史订单数据只有几个,那么可以使用”请求响应“模式。但如果用户的历史订单数量有上千条,要是一次性发送,可能会造成传输慢、并且消耗网络带宽。而且用户等待所有订单加载完成才看到数据显式,用户体验感也不好。
- 服务端流的优势:
- 逐步发送数据:服务端先返回几条订单记录,客户端可以马上展示给用户。
- 提升用户体验:用户不用等待全部订单加载完,就能看到部分订单,感觉会更流畅。
- 减轻网络压力:数据分批发送,不会让网络一次性处理过多数据,更稳定可靠。
- 客户端流(client stream)
客户端建立连接,持续不断地向服务端发送数据,直到发送完毕后,服务端最后再统一返回一个数据。
- 适用场景:
- 批量上传:比如在电商系统中,假设一个用户要批量上传多个商品图片,可以一次性上传所有图片文件,服务端接收完后统一返回上传结果。
- 数据聚合:假设用户上传一批数据,比如订单信息,服务端计算出这些订单的总金额后返回给用户。
- 双向流(bidirectional stream)
客户端和服务端各自都能随意发送数据,就类似于实时聊天通信一样。
- 适用场景:
- 实时消息传输:比如在电商系统的客服系统中,用户和客服之间可以实时双向发送消息。还有视频流传输,股票价格更新等等。
- 实时数据处理:比如双向数据更新,客户端发送数据更新请求,服务端实时返回处理结果。
2. 请求-响应模式
- 模拟实现查询用户信息的服务功能:客户端调用服务端的”用户服务(UserService)“的查询用户信息方法(GetUserInfo),根据用户id(userId)查询用户的个人信息数据,然后打印出来。
先思考一下编码思路。
- 定义和生成服务接口和消息数据
- 定义服务接口和消息数据
- 接口有什么方法
- 请求消息的变量属性
- 响应消息的变量属性
- 编写proto文件
- protoc生成go代码
- server端:
- 创建服务结构体,实现”用户服务“的接口方法(GetUserInfo)
- 结构体里嵌入接口:
pb.UnimplementedXxxServiceServer
- 方法内容:
获取请求信息中的用户id,根据id返回用户的信息。
- 创建监听器,监听指定的端口
- 创建一个grpc服务:
grpc.NewServer()
- 把“用户服务”对象注册到grpc服务中
- 启动用户服务
- client端:
- 拨号连接指定的域名端口
- 实例化客户端对象
- 客户端对象调用服务的方法,发送用户id数据,得到返回的响应数据(用户信息)。
- 打印用户信息
syntax="proto3";
package userinfo;
option go_package = ".;proto";
service UserService {
rpc GetUserInfo(UserRequest) returns (UserResponse);
}
message UserRequest {
string id = 1;
}
message UserResponse {
string name = 1;
int32 age = 2;
string email = 3;
}
3. 服务端流
- 模拟实现查询用户所有历史订单的功能:客户端调用订单服务的查询订单方法(GetUserOrders),根据用户id查询用户历史订单。
- 先思考一下编码思路:和请求响应模式差不多,仅仅是几点不同
- proto中服务方法的参数不同
- 返回参数多一个stream,用于客户端不断从这个流中读取数据。
- 服务方法发送数据时要使用
stream.Send()逐个发送订单数据。
- 客户端调用服务方法获取流,使用流接收数据
stream.Recv()。
syntax = "proto3";
package order_service;
option go_package=".;order_service";
service OrderService{
rpc GetUserOrders(OrderRequest) returns (stream OrderResponse);
}
message OrderRequest {
string user_id = 1;
}
message OrderResponse {
string order_id = 1;
string product_name = 2;
float price = 3;
}
4. 客户端流
- 实现一个批量提交订单数据的接口,客户端可以把多个订单一次性发送给服务端,服务端在接收到所有订单后,计算出总金额并返回。
- 先思考编码思路,和服务端流区别在于:
- proto文件中服务接口的定义,方法的接收参数多一个stream,能够持续不断接收数据
- 服务的方法中,循环接收数据(
steam.Recv()),接收完数据后发送响应数据并关闭连接stream.SendAndClose()
- 客户端,调用服务方法获取流
stream,然后循环发送数据stream.Send(),最后关闭连接并接收响应数据stream.CloseAndRecv()
syntax = "proto3";
package order_service;
option go_package=".;order_service";
service OrderService {
rpc SubmitOrders(stream OrderRequest) returns (OrderSummary);
}
message OrderRequest {
string order_id = 1;
float price = 2;
}
message OrderSummary {
int32 total_orders = 1;
float total_price = 2;
}
5. 双向流
- 实现一个实时订单状态更新接口,客户端可以持续发送订单状态更新请求,服务端会在接收到更新后即时返回处理结果。
- 先思考一下编码思路:
- proto服务接口定义中,参数列表和返回值中都带有stream
- 服务端:接收数据-->更新处理-->返回更新。
stream.Recv(),stream.Send()
- 客户端:调用服务方法获取流
stream,然后发送数据stream.Send(),接收数据stream.Recv()
syntax = "proto3";
option go_package=".;order_status_service";
package order_status_service;
service OrderStatusService {
rpc UpdateOrderStatus(stream OrderStatusRequest) returns (stream OrderStatusResponse);
}
message OrderStatusRequest {
string order_id = 1;
string status = 2;
}
message OrderStatusResponse {
string order_id = 1;
string confirmation = 2;
}