首页 > 其他分享 >GO实现高可用高并发分布式系统:gRPC实现客户端与服务端的一对一通讯

GO实现高可用高并发分布式系统:gRPC实现客户端与服务端的一对一通讯

时间:2023-06-14 11:32:44浏览次数:62  
标签:调用 grpc gRPC 接口 go 分布式系统 GO 服务端 客户端


分布式系统的特点是不同的功能模块会以独立服务器程序的方式运行在不同主机上。当服务A想请求位于另一台机器的服务B完成特定请求时,就必须将要处理的数据提交给B。这个过程就涉及到一系列问题,首先A需要把数据进行序列化然后通过网络连接发送给B,B接收到数据后需要进行反序列化得到数据原型,进行相应处理得到结果,接着把结果序列化后再传递给A,A收到结果后进行反序列化,得到处理结果的数据结构。

这一系列过程涉及到数据序列化,网络连接的建立,数据的传输效率,同时还涉及到身份认证,数据加密等,这些步骤繁琐麻烦,而且跟业务逻辑无关,如果都要由开发者来处理,那么很多宝贵的时间和精力就得浪费在这些不产生效益的工作上,而且这些工作处理起来还很复杂。因此要想高效的搭建基于微服务的分布式系统,我们需要高效的工具来处理服务之间的跨进程通讯,gRPC就是满足于该目的的有效框架。

gRPC的目的就是让位于不同主机的进程在相互调用特定接口时尽可能的省却不必要的操作,让接口调用变得像处于同一进程间的模块相互调用那么简单。gRPC的运行有四种模式,第一种是客户端向服务端发出一个请求,服务端处理后给客户端返回一个结果;第二种是客户端向服务端发起一个请求,然后服务端向客户端返回一系列结果;第三种是客户端向服务端发送一系列请求,服务端返回一个结果;第4种是客户端向服务端发送一系列请求,服务端给客户端返回一系列结果,本节我们先看前两种情况。

假设我们现在要开发一个电商后台系统,系统有一个订单存储查询服务,客户端向服务发送订单ID,服务接收到ID后将相应订单的详细信息返回。如果服务端模块跟客户端模块属于同一个进程的话,那么它们之间就会存在调用关系,服务端模块会导出一个接口,该接口接收的参数就是订单ID,调用返回的结果就是订单的具体数据,由于服务端和客户端处于不同进程,甚至位于不同主机,但我们希望能实现两者之间的交互就像同一进程内不同模块之间相互调用那么简单。

为了实现gRPC,我们首先要安装相应环境,gRPC使用protobuf来作为进程之间交互数据的结构,因此需要先安装好protocolBuf,然后还需要安装gRPC组件,组件的下载可以点击这里:https://github.com/grpc/grpc-go/releases。根据不同系统下载相应可执行文件,然后将它放置到PATH环境变量对应的路径下即可。

接下来我们需要通过proto文件来定义订单存储查询服务导出的接口,以及订单信息的数据结构。先在根目录下创建一个GRPC文件夹,然后创建子目录proto用来存放proto文件,proto文件的内容如下:

syntax="proto3";

import "google/protobuf/wrappers.proto";

option go_package="service/order_management";

service OrderManagement {
    rpc getOrder(google.protobuf.StringValue) returns(Order);
}

message Order {
    string id = 1;
    repeated string items = 2;
    string description = 3;
    float price = 4;
    string destination = 5;
}

其中getOrder就是订单存储查询模块向外导出接口的定义,它接收一个StringValue类型作为订单ID,然后以Order描述的数据结构作为订单具体信息返回,注意看这里我们使用关键字service来定义服务导出的接口,服务的名称为OrderManagement,导出接口名称为getOrder,在proto目录里面我们执行命令:

protoc -I=. --go_out=plugins=grpc:. *.proto

其中grpc:. 表示调用我们前面路径下载的应用程序编译proto文件中定义的service信息,如果没有下载前面路径所给组件的话,上面命令执行就会有问题。完成编译后,在当前路径下就会生成子目录service/order_management,下面有一个名为order_management.pb.go的文件,我们需要看一下他的内容。

这个文件里面代码很复杂,我们只需要关注几部分,使用关键字getOrder进行搜索,我们可以发现如下定义:

// OrderManagementServer is the server API for OrderManagement service.
type OrderManagementServer interface {
	GetOrder(context.Context, *wrappers.StringValue) (*Order, error)
}

这是一个接口定义,它主要用于初始化服务器程序,我们的工作就是要实现接口函数GetOrder,然后将实现的实例传给服务器对象,到时候客户端请求过来时,服务器会自动调用我们实现的接口。

第二个需要关注的是:

func RegisterOrderManagementServer(s *grpc.Server, srv OrderManagementServer) {
	s.RegisterService(&_OrderManagement_serviceDesc, srv)
}

我们将调用这个接口来生成一个服务器对象,它将负责建立tcp连接等工作,注意看这个函数需要传入OrderManagementServer接口,所以当我们实现了GetOrder的逻辑后,调用该函数就可以完成服务端的工作。

为了能调用order_management.pb.go的代码,我们需要进入到service/order_management目录,然后执行如下命令:

sudo go mod init order_management

接下来我们看看服务端的实现,回到GRPC根目录,新建目录server,在该目录下创建文件main.go,首先我们添加依赖包和初始化一下服务端数据:

package main 

import (
	"context"
	"github.com/golang/protobuf/ptypes/wrappers"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	pb "order_management"
	wrapper "github.com/golang/protobuf/ptypes/wrappers"
	"net"
	"strings"
)

const (
	port = ":50051" //服务端将在50051端口监听客户端请求
)

func initSampleData() {
	//虚拟一些数据以便客户端进行请求
	orderMap["102"] = pb.Order{Id: "102", Items: []string{"Google Pixel 3A", "Mac Book Pro"}, Destination: "Mountain View, CA", Price: 1800.00}
	orderMap["103"] = pb.Order{Id: "103", Items: []string{"Apple Watch S4"}, Destination: "San Jose, CA", Price: 400.00}
	orderMap["104"] = pb.Order{Id: "104", Items: []string{"Google Home Mini", "Google Nest Hub"}, Destination: "Mountain View, CA", Price: 400.00}
	orderMap["105"] = pb.Order{Id: "105", Items: []string{"Amazon Echo"}, Destination: "San Jose, CA", Price: 30.00}
	orderMap["106"] = pb.Order{Id: "106", Items: []string{"Amazon Echo", "Apple iPhone XS"}, Destination: "Mountain View, CA", Price: 300.00}
}

var orderMap = make(map[string]pb.Order)

type server struct {  //server就是要实现接口getOrder的实例
	orderMap map[string]*pb.Order 
}

接下来一个重要的工作就是完成getOrder接口的实现:

func (s *server) GetOrder(ctx context.Context, 
	orderId *wrapper.StringValue) (*pb.Order, error) {
		//根据客户端发来ID查找订单数据
		ord, exists := orderMap[orderId.Value]
		if exists {
			return &ord, status.New(codes.OK, "").Err()
		}

		return nil, status.Errorf(codes.NotFound, "Order does not exist. : ", orderId)
}

接口的实现中有些东西需要强调,首先是codes,它用来标志接口处理结构,它跟http协议的返回状态类似,如果处理成功,那么返回标志就是codes.OK,如果有错误那么就设置相应标志,例如客户端发送的订单号没有对应数据时,错误就是codes.NotFound,这点给http的404类似。
如果有内容,那么我们就以Order数据结构的形式将数据返回,数据的序列化和发送等工作用gRPC框架来负责。

接下来我们要做的就是建立tcp连接,然后使用实现了getOrder接口的server实例来初始化服务端,代码如下:

func main() {
	initSampleData()
	lis, err := net.Listen("tcp", port)
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}

	s := grpc.NewServer()
	pb.RegisterOrderManagementServer(s, &server{})
	if err = s.Serve(lis); err != nil {
		log.Fatalf("failed to serve: %v", err)
	}
}

上面代码中,我们先建立一个监听在端口50051的tcp连接,然后使用grpc.NewServer生成一个服务器对象,RegisterOrderManagmentServer将我们实现的业务逻辑注入到服务器,当客户端将订单ID传过来时,服务器对象就会调用我们实现的接口,以上就是服务端的gRPC实现。完成上面代码后,我们需要执行如下命令:

sudo go mod init server
sudo go mod tidy
go build

如果命令执行没有问题的话,在本地目录就会创建服务端的可执行文件名为server,然后我们先将服务器程序运行起来:

./server

接下来我们看看客户端的实现,回到GRPC根目录,创建目录client,在下面生成文件main.go,首先我们要做的是引入依赖项:

package main 

import (
	"context"
	"log"
	pb "order_management"
	"time"
	"google.golang.org/grpc"
	wrapper "github.com/golang/protobuf/ptypes/wrappers"
	"io"
)

const (
	address = "localhost:50051"  //向服务器端口发出请求
)

接下来的工作就是向服务端那样,调用gprc生成的代码创建客户端对象:

func main() {
	conn, err := grpc.Dial(address, grpc.WithInsecure()) //创建与服务端的连接
	if err != nil {
		log.Fatalf("did not connect: %v", err)
	}

	defer conn.Close()
	client := pb.NewOrderManagementClient(conn) //生成客户端对象
	ctx, cancel := context.WithTimeout(context.Background(), time.Second * 5)
	defer cancel()
	//调用getOrder接口向服务端提供订单ID,发起服务请求
	retriveOrder, err := client.GetOrder(ctx, &wrapper.StringValue{Value : "106"})
	log.Print("GetOrder Response -> :", retriveOrder)
}

以上的代码就是创建客户端实例,然后调用getOrder接口向服务端发出请求。完成上面代码后,我们执行如下命令:

sudo go mod init client
sudo go mod tidy
go build
./client

客户端运行后就会向服务端发出请求,然后将返回的订单数据打印出来,客户端运行后输出结果如下:

GO实现高可用高并发分布式系统:gRPC实现客户端与服务端的一对一通讯_服务端


我们可以看到,使用gRPC实现跨进程调用,在服务端需要实现定义的接口逻辑,然后就调用生成的接口创建服务器实例,将我们实现的接口传入服务器即可。客户端处理创立tcp连接,调用生成的代码获得客户端实例,接下来就可以直接调用定义的接口向服务端发起请求,gRPC框架让能让不同服务直接的调用尽可能像位于同一进程的模块直接发送调用那么简单,当然它也提供了更加复杂的调用功能,后面我们再一一解析,代码可以在该链接获取:https://github.com/wycl16514/go_distributed_system_grpc.git


标签:调用,grpc,gRPC,接口,go,分布式系统,GO,服务端,客户端
From: https://blog.51cto.com/u_16160261/6476553

相关文章

  • go实现高并发高可用分布式系统:设计类似kafka的高并发海量数据存储机制1
    上一节我们实现了日志微服务,它以http服务器的模式运行,客户端通过json方式将日志数据post过来,然后通过httpget的方式读取日志。当时我们的实现是将所有日志信息添加到数组末尾,这意味着所有日志信息都会保存在内存中。但分布式系统的日志数量将非常巨大,例如推特一天的日志数量就达到......
  • windows使用navicate 导出导入MongoDB数据
    1.下载安装navicate以及mongodb-database-tools-windowsmongodb-database-tools-windows下载地址 https://www.mongodb.com/try/download/database-tools 2.navicate设置MongoDBdump、mongorestore可执行文件路径(mongodb-database-tools里的bin目录)3.选择要备份或恢复......
  • 基于 hugo 和 papermod 主题搭建自己的博客
    部署博客到vercelFreeNom申请域名首先,梯子最好选择美国的,并且freenom选择地址时最好与ip所在州可以对应得上;进入FreeNom,输入zwyb.tk,然后点击检查可用性,这里要记得输入后缀,能避免点击现在获取显示不可用的问题。如下图所示:Cloudfare管理域名cloudfare添加站点zwyyy456.ml,然......
  • golang对于[]byte数组转string进行比较的优化
    当需要比较两个[]byte数组是否相等时有好几种方案,下面可以看出前三种方案都是优化过的,效率高的方案。packagemainimport( "bytes" "crypto/rand" mr"math/rand" "testing")funcStringEqual(nint,ffunc(a,b[]byte)bool){ buf:=make([]byte,1024) rand.......
  • 【841】shapely合并多个Polygon/MultiPolygon
    参考:Convertinglistofpolygonstomultipolygonusingshapely?MultiPolygon->Polygonlistlist(multiPoly.geoms)Polygonlist->MultiPolygonshapely.geometry.MultiPolygon([poly1,poly2,poly3,poly4,poly5]) ......
  • 【解决一个小问题】golang 的 `-race`选项导致 unsafe代码 panic
    作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢!cnblogs博客zhihuGithub公众号:一本正经的瞎扯为了提升性能,使用unsafe代码来重构了凯撒加密的代码。代码如下:const( lowerCaseAlphabet="abcdefghijklmnopqrstuvwxyz" upperCaseAlphabet="ABCDEFGHIJKLMN......
  • go调用minio上传文件
    1packagemain23import(4"context"5"fmt"6"log"7"os"89"github.com/minio/minio-go/v7"10"github.com/minio/minio-go/v7/pkg/credentials"11)1213......
  • GO语言集成开发工具环境-JetBrains GoLand 2023
    Go是一种开源编程语言,适用于开发高效、可扩展的软件。由于其高效性和可扩展性,Go已经成为了一种流行的编程语言。为了更好地开发Go应用程序,JetBrains公司开发了一款Go语言集成开发工具环境——GoLand。→→↓↓载GoLand2023mac/win版 GoLand提供了一系列功能和工具,帮助开发......
  • 新的Google Lyra音频编解码器对实时视频流意味着什么?
    正文字数:2602 阅读时长:4分钟通过语言编码中的码率缩减趋势,Lyra与Opus中的区别比较,Lyra的作用,XDN平台上的高效语音编码技术几个方面探讨新的GoogleLyra音频编解码器对实时视频流的意义。文/ RED5PRO https://www.red5pro.com/blog/what-does-the-new-google-lyra-audio-codec-......
  • beego 跨域
    网上资料大部分跨域设置引入插件github.com/astaxie/beego/plugins/cors//允许跨域 beego.InsertFilter("*",beego.BeforeRouter,cors.Allow(&cors.Options{ AllowAllOrigins:true, AllowMethods:[]string{"GET","POST","PUT",&......