1. 为何要用流模式:
①. 传输比较小的数据,基本模式是客户端请求,服务端响应.
②. 如果传输较大数据呢?
a. 数据包过大导致压力陡增,等待响应时间也不可能是无限制增加.
b. 需要等待客户端包全部发送,才能处理以及响应.
③. 痛点:
a. 服务端需要等这些数据全部发送过来,才能进行响应.
b. 服务端进行一系列逻辑处理后,再返回给客户端也需要一定的时间等待.
④. 3种流模式:
服务端流、客户端流、双向流
(1). 服务端流场景:
一次性发送给服务端
------------------------>
[客户端] [服务端流]
<------------------------
服务端分批返回数据
注:
要从库里去批量查询一批符合条件的用户(x万到几十万),给予一些积分奖励.
①. 客户端一次性把用户列表1000个客户数据发送给服务端.
②. 服务端查询用户积分比较耗时,有点慢.
③. 因此,采用的策略是查到一部分就返回一部分,而不是全部查完再返回给客户端.
2. 服务端代码:
(1). grpc\pbfiles\Models.proto:
syntax="proto3";
package services;
// 用户模型
message UserInfo{
int32 user_id=1;
int32 user_score=2;
}
(2). grpc\pbfiles\User.proto:
syntax="proto3";
package services;
import "Models.proto";
message UserScoreRequest{
repeated UserInfo users=1;
}
message UserScoreResponse{
repeated UserInfo users=1;
}
service UserService{
// stream写在返回表示是服务端流,可以分批进行处理及发送给客户端
rpc GetUserScoreServer(UserScoreRequest) returns (stream UserScoreResponse);
// stream写在接收参数表示客户端流
rpc GetUserScoreClient(stream UserScoreRequest) returns (UserScoreResponse);
// 双向流
rpc GetUserScoreServerClient(stream UserScoreRequest) returns (stream UserScoreResponse);
}
(3). 中间文件生成go文件:
protoc --go_out=plugins=grpc:../services User.proto
(4). grpc\services\UserService.go:
package services
import "time"
type UserService struct {
}
func(*UserService) GetUserScoreServer(request *UserScoreRequest, stream UserService_GetUserScoreServer) error {
var score int32=101
users:=make([]*UserInfo,0)
for index,user:=range request.Users{
user.UserScore=score
score++
users=append(users,user)
// 每两条返回给客户端
if (index+1) %2==0 && index>0{
// 不能直接return出去的
err:=stream.Send(&UserScoreResponse{Users:users})
if err!=nil{
return nil
}
// 清空切片
users=(users)[0:0]
}
// 模拟耗时
time.Sleep(time.Second*2)
}
if len(users) > 0 {
err:=stream.Send(&UserScoreResponse{Users:users})
if err!=nil{
return nil
}
}
return nil
}
func(*UserService) GetUserScoreClient(stream UserService_GetUserScoreClientServer) error {
var score int32=101
users:=make([]*UserInfo,0)
for{
req,err:=stream.Recv()
// 接收完毕了,才能发送给客户端
if err==io.EOF{
// 发送并关闭流
return stream.SendAndClose(&UserScoreResponse{Users:users})
}
if err!=nil{
return err
}
// 服务端的业务处理
for _,user:=range req.Users{
user.UserScore=score
score++
users=append(users,user)
}
time.Sleep(time.Second*2)
}
}
func(* UserService) GetUserScoreServerClient(stream UserService_GetUserScoreServerClientServer) error {
var score int32=101
users:=make([]*UserInfo,0)
for{
req,err:=stream.Recv()
if err==io.EOF{
// 因为是双向的,不存在接收完了,就完成了
return nil
}
if err!=nil{
return err
}
// 服务端的业务处理
for _,user:=range req.Users{
user.UserScore=score
score++
users=append(users,user)
}
time.Sleep(time.Second*2)
err=stream.Send(&UserScoreResponse{Users:users})
if err!=nil{
log.Println(err.Error())
}
users = (users)[0:0]
}
}
(5). grpc\service.go:
...
// 用户服务注册
services.RegisterUserServiceServer(rpcServer, new(services.UserService))
...
(6). 拷贝接口文件: 拷贝grpc\services\Models.pb.go和User.pb.go文件到客户端文件.
(7). grpcclient\client.go:
...
// 函数结束时关闭连接
defer client.Close()
// 一、服务端开启流模式,客户读取
userClient:=services.NewUserServiceClient(client)
var i int32
req:=services.UserScoreRequest{}
req.Users=make([]*services.UserInfo,0)
for i=1;i<20;i++{
req.Users=append(req.Users,&services.UserInfo{UserId:i})
}
stream,_:=userClient.GetUserScoreServer(context.Background(),&req)
for {
resp, err := stream.Recv()
// 表示服务端发送结束了
if err == io.EOF {
break
}
if err != nil {
log.Fatal(err)
}
// 从服务端获取数据,立马开一个协程做处理
fmt.Println(resp.Users)
}
注:
①. 客户端启动后,服务端会每两条数数据返回.直到没有为止.
[user_id:1 user_score:101 user_id:2 user_score:102 ]
[user_id:3 user_score:103 user_id:4 user_score:104 ]
// 二:客户端流模式
userClient:=services.NewUserServiceClient(client)
var i int32
stream,err:=userClient.GetUserScoreClient(context.Background())
if err!=nil{
log.Fatal(err)
}
for j:=1;j<=3;j++{
req:=services.UserScoreRequest{}
req.Users=make([]*services.UserInfo,0)
for i=1;i<=5;i++{ //加了5条用户信息 假设是一个耗时的过程
req.Users=append(req.Users,&services.UserInfo{UserId:i})
}
// 往服务端去发送
err:=stream.Send(&req)
if err!=nil{
log.Println(err)
}
}
// 接收返回值
res,_:=stream.CloseAndRecv()
fmt.Println(res.Users)
注:
①. 客户端启动后,每5条往服务端发送数据,服务端全部收到全部返回.
[user_id:1 user_score:101 user_id:2 user_score:102 ]
[user_id:3 user_score:103 user_id:4 user_score:104 ]
// 三:双向流
userClient:=services.NewUserServiceClient(client)
var i int32
stream,err:=userClient.GetUserScoreServerClient(context.Background())
if err!=nil{
log.Fatal(err)
}
for j:=1;j<=3;j++{
req:=services.UserScoreRequest{}
req.Users=make([]*services.UserInfo,0)
for i=1;i<=5;i++{ //加了5条用户信息 假设是一个耗时的过程
req.Users=append(req.Users,&services.UserInfo{UserId:i})
}
// 往服务端去发送
err:=stream.Send(&req)
if err!=nil{
log.Println(err)
}
res, err := stream.Recv()
if err != nil {
log.Println(err)
}
fmt.Println(res.Users)
}
注:
①. 实际开发会结合协程.
②. 客户端启动后,每5条往服务端发送数据,服务端后马上返回部分数据.
[user_id:1 user_score:101 user_id:2 user_score:102 user_id:3 user_score:103 user_id:4 user_score:104 user_id:5 user_score:105 ]
[user_id:1 user_score:106 user_id:2 user_score:107 user_id:3 user_score:108 user_id:4 user_score:109 user_id:5 user_score:110 ]
[user_id:1 user_score:111 user_id:2 user_score:112 user_id:3 user_score:113 user_id:4 user_score:114 user_id:5 user_score:115 ]