Grpc之流模式

一 流模式简介

RPC是远程函数调用,因此每次调用的函数参数和返回值不能太大,否则将严重影响每次调用的响应时间。因此传统的RPC方法调用对于上传和下载较大数据量场景并不适合。同时传统RPC模式也不适用于对时间不确定的订阅和发布模式。为此,gRPC框架针对服务器端和客户端分别提供了流特性。

//hello.proto

syntax = "proto3";
option go_package = "./;proto";

service StreamService{
   // 关键字stream指定启用流特性,参数部分是接收客户端参数的流,返回值是返回给客户端的流。
  //重新生成代码可以看到接口中新增加的Channel方法的定义
  rpc GetStream(StreamRequestData) returns (stream StreamResponsetData){}//服务端流模式
  rpc PostStream(stream StreamRequestData) returns (StreamResponsetData){}//客户端流模式
  rpc AllStream(stream StreamRequestData) returns (stream StreamResponsetData){}//流模式
}

message StreamRequestData{
  string data = 1;
}
message StreamResponsetData{
  string msg = 1;
}
// protoc -I . --go_out=. hello.proto --go-grpc_out=. hello.proto
//server.go
package main

import (
	"fmt"
	"google.golang.org/grpc"
	"net"
	"testgrpc/grpc-ch/proto"
	"time"
)

type Server struct{
	proto.UnimplementedStreamServiceServer
}

//GetStream 服务端流模式:服务端不断的给客户端发数据
func (server  *Server ) GetStream(request *proto.StreamRequestData,s proto.StreamService_GetStreamServer)  (err error) {
	response := &proto.StreamResponsetData{}
	if request.Data == "time" {

		for {
			response.Msg = fmt.Sprintf("当前时间为: %v",time.Now().Unix())
			err = s.Send(response)
			time.Sleep(time.Second)
		}

	}
	return
}


// PostStream 客户端流模式: 客户端不断的给服务端发数据,服务端接收
func (server *Server) PostStream(s proto.StreamService_PostStreamServer) error {
	for {
		//可以发现服务端和客户端的流辅助接口均定义了Send和Recv方法用于流数据的双向通信。
		//服务端在循环中接收客户端发来的数据,如果遇到io.EOF表示客户端流被关闭,
		//如果函数退出表示服务端流关闭。生成返回的数据通过流发送给客户端,双向流数据的发送和接收都是完全独立的行为。
		//需要注意的是,发送和接收的操作并不需要一一对应,用户可以根据真实场景进行组织代码。
		requestData,err := s.Recv()
		if err != nil {
			panic(err)
		}
		fmt.Println(requestData)
	}
	return nil
}


func (server *Server) AllStream(s proto.StreamService_AllStreamServer) error {
	return nil
}

func main() {
	server := grpc.NewServer()
	proto.RegisterStreamServiceServer(server, new(Server))
	lis, err := net.Listen("tcp", "0.0.0.0:9999")
	if err != nil {
		panic("fail to listen port:" + err.Error())
	}
	fmt.Println("启动grpc服务成功:0.0.0.0:9999")
	err = server.Serve(lis)
	if err != nil {
		panic("fail to start grpc:" + err.Error())
	}
}

//client.go
package main

import (
	"context"
	"fmt"
	"google.golang.org/grpc"
	"testgrpc/grpc-ch/proto"
	"time"
)

type Client struct {
	Url string
}

func (c *Client) dial()  *grpc.ClientConn{

	conn ,err := grpc.Dial(c.Url,grpc.WithInsecure())
	if err != nil {
		panic(err)
	}
	return conn

}

func (c *Client) GetStream() {
	conn:= c.dial()
	defer conn.Close()

	client := proto.NewStreamServiceClient(conn)
	stream,err := client.GetStream(context.Background(),&proto.StreamRequestData{
		Data: "time",
	})
	if err != nil {
		return
	}

	for {
		data , _ := stream.Recv()
		fmt.Println(data)

	}

}

func (c *Client) PostStream()  {
	conn := c.dial()
	defer conn.Close()

	client := proto.NewStreamServiceClient(conn)
	stream ,err := client.PostStream(context.Background())
	if err != nil {
		return
	}

	request := &proto.StreamRequestData{}
	for {
		request.Data = fmt.Sprintf("当前时间: %v",time.Now().Unix())
		stream.Send(request)
		time.Sleep(time.Second)
	}
}


func main() {
	client := Client{"127.0.0.1:9999"}
	client.GetStream()
	//client.PostStream()
}

二 发布与订阅模式

我们基于Go内置的RPC库实现了一个简化版的Watch方法。基于Watch的思路虽然也可以构造发布和订阅系统,但是因为RPC缺乏流机制导致每次只能返回一个结果。在发布和订阅模式中,由调用者主动发起的发布行为类似一个普通函数调用,而被动的订阅者则类似gRPC客户端单向流中的接收者。现在我们可以尝试基于gRPC的流特性构造一个发布和订阅系统。

发布订阅是一个常见的设计模式,开源社区中已经存在很多该模式的实现。其中docker项目中提供了一个pubsub的极简实现,下面是基于pubsub包实现的本地发布订阅代码:

//service.proto

syntax = "proto3";
option go_package = "./;proto";

message String{
  string  value = 1 ;
}
//其中pubsub.NewPublisher构造一个发布对象,p.SubscribeTopic()可以通过函数筛选感兴趣的主题进行订阅。
//现在尝试基于gRPC和pubsub包,提供一个跨网络的发布和订阅系统。首先通过Protobuf定义一个发布订阅服务接口:
service PubsubService {
  //其中Publish是普通的RPC方法,Subscribe则是一个单向的流服务。然后gRPC插件会为服务端和客户端生成对应的接口:
  rpc Publish (String) returns (String);
  //因为Subscribe是服务端的单向流,因此生成的HelloService_SubscribeServer接口中只有Send方法。
  //
  //然后就可以实现发布和订阅服务了:
  rpc Subscribe (String) returns (stream String);
}
//server.go
package main

import (
	"fmt"
	"github.com/moby/moby/pkg/pubsub"
	"google.golang.org/grpc"
	"net"
	"strings"
	"time"
	"context"

	. "testgrpc/grpc-watch/proto"
)

type PubsubService struct {
	pub *pubsub.Publisher
	UnimplementedPubsubServiceServer
}

func NewPubsubService() *PubsubService {
	return &PubsubService{
		pub: pubsub.NewPublisher(100*time.Millisecond, 10),
	}
}

func (p *PubsubService) Publish(
	ctx context.Context, arg *String,
) (*String, error) {
	p.pub.Publish(arg.GetValue())
	return &String{}, nil
}

func (p *PubsubService) Subscribe(
	arg *String, stream PubsubService_SubscribeServer,
) error {
	ch := p.pub.SubscribeTopic(func(v interface{}) bool {
		if key, ok := v.(string); ok {
			if strings.HasPrefix(key,arg.GetValue()) {
				return true
			}
		}
		return false
	})

	for v := range ch {
		if err := stream.Send(&String{Value: v.(string)}); err != nil {
			return err
		}
	}

	return nil
}


func main() {

	p := pubsub.NewPublisher(100*time.Millisecond, 10)

	golang := p.SubscribeTopic(func(v interface{}) bool {
		if key, ok := v.(string); ok {
			if strings.HasPrefix(key, "golang:") {
				return true
			}
		}
		return false
	})
	docker := p.SubscribeTopic(func(v interface{}) bool {
		if key, ok := v.(string); ok {
			if strings.HasPrefix(key, "docker:") {
				return true
			}
		}
		return false
	})

	go p.Publish("hi")
	go p.Publish("golang: https://golang.org")
	go p.Publish("docker: https://www.docker.com/")
	time.Sleep(1)

	go func() {
		fmt.Println("golang topic:", <-golang)
	}()
	go func() {
		fmt.Println("docker topic:", <-docker)
	}()

	server := grpc.NewServer()

	RegisterPubsubServiceServer(server,NewPubsubService())
	lis, err := net.Listen("tcp", "0.0.0.0:1234")
	if err != nil {
		panic("fail to listen port:" + err.Error())
	}
	fmt.Println("启动grpc服务成功:0.0.0.0:9999")
	err = server.Serve(lis)
	if err != nil {
		panic("fail to start grpc:" + err.Error())
	}


	<-make(chan bool)

}

//client1.go   生产
package main

import (
	"google.golang.org/grpc"

	"log"

	"context"

	. "testgrpc/grpc-watch/proto"
)

func main() {
	conn, err := grpc.Dial("localhost:1234", grpc.WithInsecure())
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()

	client := NewPubsubServiceClient(conn)

	_, err = client.Publish(
		context.Background(), &String{Value: "golang: hello Go"},
	)
	if err != nil {
		log.Fatal(err)
	}
	_, err = client.Publish(
		context.Background(), &String{Value: "docker: hello Docker"},
	)
	if err != nil {
		log.Fatal(err)
	}

	_, err = client.Publish(
		context.Background(), &String{Value: "docker: nizhencai Docker"},
	)
	if err != nil {
		log.Fatal(err)
	}
}
//client2.go 消费
package main

import (
	"context"
	"fmt"
	"google.golang.org/grpc"
	"io"
	"log"
	"time"

	. "testgrpc/grpc-watch/proto"
)

func main() {
	conn, err := grpc.Dial("localhost:1234", grpc.WithInsecure())
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()

	client := NewPubsubServiceClient(conn)
	stream1, err := client.Subscribe(
		context.Background(), &String{Value: "golang:"},
	)
	if err != nil {
		log.Fatal(err)
	}
	stream2, err := client.Subscribe(
		context.Background(), &String{Value: "docker:"},
	)
	if err != nil {
		log.Fatal(err)
	}
	go func() {
		for {
			reply, err := stream1.Recv()
			if err != nil {
				if err == io.EOF {
					break
				}
				log.Fatal(err)
			}

			fmt.Println(reply.GetValue())
		}
	}()
	go func() {
		for {
			reply, err := stream2.Recv()
			if err != nil {
				if err == io.EOF {
					break
				}
				log.Fatal(err)
			}

			fmt.Println(reply.GetValue())
		}
	}()
	time.Sleep(time.Hour)
}
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

来自万古的忧伤

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值