当前位置: 首页 > 科技观察

使用gRPC、Ballerina和Go构建有效的微服务

时间:2023-03-12 22:31:19 科技观察

KeyTakeaways根据交互和通信方式,我们可以将微服务分为两类:面向外部的微服务和内部微服务。RESTfulAPI是面向外部的微服务的实际通信技术(REST的普遍存在和丰富的支持生态系统在其持续成功中发挥着关键作用)。gRPC是远程过程调用(RPC)API范例的一个相对较新的实现。它可以在内部微服务之间的所有同步通信中发挥重要作用在这里,我们通过使用真实世界的微服务用例来检查关键的gRPC概念、它们的用法以及gRPC作为服务间通信的好处。许多主要的编程语言都支持gRPC。我们将讨论使用Ballerinalang和Golang作为编程语言的示例实现。在现代微服务架构中,我们可以根据微服务的交互和通信将微服务分为两类。第一组微服务充当面向外部的微服务,直接暴露给消费者。它们主要是基于HTTP的API,使用针对外部开发人员优化的常规基于文本的消息传递有效负载(JSON、XML等),并使用表示状态传输(REST)作为事实上的通信技术。REST的普遍存在和丰富的生态系统在这些面向外部的微服务的成功中起着至关重要的作用。OpenAPI为描述、生成、使用和可视化这些RESTAPI提供了定义明确的规范。API管理系统与这些API配合良好,并提供安全性、速率限制、缓存和货币化以及业务需求。GraphQL可以替代基于HTTP的RESTAPI,但这超出了本文的范围。另一组微服务是内部的,不与外部系统或外部开发人员通信。这些微服务相互交互以完成一组给定的任务。内部微服务使用同步或异步通信。在许多情况下,我们可以看到使用基于HTTP的RESTAPI作为同步模式,但这并不是最好的技术。在本文中,我们将仔细研究如何利用gRPC等二进制协议,它可以成为服务间通信的优化通信协议什么是gRPC?gRPC是一种相对较新的远程过程调用(RPC)API范例,用于服务间通信。与所有其他RPC一样,它允许直接调用不同机器上的服务器应用程序上的方法,就好像它是本地对象一样。与Thrift和Avro等其他二进制协议一样,gRPC使用接口描述语言(IDL)来定义服务契约。gRPC使用最新的网络传输协议HTTP/2作为默认传输协议,与基于HTTP/1.1的REST相比,这使得gRPC快速且健壮。您可以使用ProtocolBuffers来定义gRPC服务契约,其中每个服务定义指定具有预期输入和输出消息以及参数和返回类型的数据结构的方法的数量。使用主要编程语言提供的工具,可以使用定义服务契约的相同协议缓冲区文件生成服务器端框架和客户端代码(存根)。gRPC的实用微服务用例图1:在线零售店微服务架构的一部分微服务架构的主要好处之一是使用最合适的编程语言构建不同的服务,而不是用一种语言构建所有内容。图1显示了在线零售商店的部分微服务架构,其中在Ballerina(本文其余部分简称为Ballerina)和Golang中实现了四个微服务,以提供在线零售商店的部分功能。由于很多主流编程语言都支持gRPC,所以当我们定义一个服务契约时,我们可以使用一种非常合适的编程语言来实现它。让我们为每个服务定义服务契约。syntax="proto3";packageretail_shop;ser??viceOrderService{rpcUpdateOrder(Item)returns(Order);}messageItem{stringitemNumber=1;int32quantity=2;}messageOrder{stringitemNumber=1;int32总数量=2;浮动小计=3;清单1:Order微服务的服务合同(order.proto)Order微服务将获取购物项目和数量并返回小计。这里我使用BallerinagRPC工具分别生成gRPC服务样板代码和存根/客户端。$ballerinagrpc--modeservice--inputproto/order.proto--outputgen_code这将生成OrderService服务器样板代码。importballerina/grpc;listenergrpc:Listenerep=new(9090);serviceOrderServiceonep{resourcefunctionUpdateOrder(grpc:Callercaller,Itemvalue){//实现在这里。//你应该返回一个订单}}publictypeOrderrecord{|字符串itemNumber="";int总数量=0;浮动小计=0.0;|};公共类型项目记录{|字符串itemNumber="";int数量=0;|};清单2:生成的样板代码片段(OrderService_sample_service.bal)gRPC服务完美映射到Ballerina的服务类型,gRPCrpc映射到Ballerina的类型,资源函数gRPC消息映射到Ballerina的记录类型。我为Order微服务创建了一个单独的Ballerina项目,并使用生成的OrderService样板代码来实现gRPCmonadic服务。一元阻塞OrderService在Cart微服务中调用。我们可以使用以下Ballerina命令生成客户端存根和客户端代码。$ballerinagrpc--modeclient--inputproto/order.proto--outputgen_code生成的客户端存根有阻塞和非阻塞远程方法。此示例代码演示了gRPCmonadic服务如何与gRPC阻塞客户端交互。公共远程函数UpdateOrder(Itemreq,grpc:Headers?headers=())returns([Order,grpc:Headers]|grpc:Error){varpayload=checkself.grpcClient->blockingExecute("retail_shop.OrderService/UpdateOrder",请求,标题);grpc:标题resHeaders=new;任意数据结果=();[结果,resHeaders]=有效负载;返回[result,resHeaders];}};清单3:为阻塞模式生成的远程对象代码片段Ballerina的远程方法抽象是一个非常合适的gRPC客户端存根,可以看到UpdateOrder调用代码非常干净整洁。Checkout微服务通过汇总从Cart微服务收到的所有临时订单来发出最终账单。在这种情况下,我们将所有临时订单作为流订单消息发送。syntax="proto3";packageretail_shop;ser??viceCheckoutService{rpcCheckout(streamOrder)returns(FinalBill){}}messageOrder{stringitemNumber=1;int32总数量=2;floatsubTotal=3;}messageFinalBill{floattotal=1;}清单4:Checkout微服务(checkout.proto)的服务契约您可以使用ballerinagrpc命令为checkout.proto生成样板代码。$ballerinagrpc--modeservice--inputproto/checkout.proto--outputgen_codegRPCclientstreamingCart微服务(客户端)流消息作为流对象参数提供,可以使用循环迭代处理客户端发送的每条消息。请参阅以下示例实例:serviceCheckoutServiceonep{resourcefunctionCheckout(grpc:Callercaller,streamclientStream){floattotalBill=0;//在这里遍历流消息错误?e=clientStream.forEach(function(Orderorder){totalBill+=order.subTotal;});//一旦客户端完成流,将返回一个grpc:EOS错误来指示它if(eisgrpc:EOS){FinalBillfinalBill={total:totalBill};//发送总账单给客户端grpc:Error?结果=呼叫者->发送(finalBill);if(resultisgrpc:Error){log:printError("发送Finalbill时出错:"+result.message()+"-"+result.detail()["message"]);}else{log:printInfo("发送最终账单总额:"+finalBill.total.toString());}结果=来电者->完成();if(resultisgrpc:Error){log:printError("关闭连接时出错:"+result.message()+"-"+result.detail()["message"]);}}//如果客户端发送了一个错误,它可以在这里处理elseif(eisgrpc:Error){log:printError("Anunexpectederroroccurred:"+e.message()+"-"+e.detail()["消息"]);}}}List5:(CheckoutService_sample_service.bal)服务示例实现代码片段CheckoutService客户端流程完成后,将返回grpc:EOS错误,该错误示例客户端代码和客户端存根可用于确定何时发送可以使用以下命令生成使用CheckoutService的调用者对象的客户端的最终响应消息(总计):$ballerinagrpc--modeclient--inputproto/checkout.proto--outputgen_code让我们看看购物车的实现微服务。购物车微服务有两个RESTAPI——一个用于将商品添加到购物车,一个用于最终结账。当一个项目被添加到购物车时,它通过对订单微服务进行gRPC调用并将其存储在内存中来获得一个临时订单,其中包含每个项目的小计。调用Checkout微服务会将存储在内存中的所有临时订单作为gRPC流发送到Checkout微服务,并返回要支付的总金额。Ballerina使用内置的Stream类型和客户端对象抽象实现gRPC客户端流。请参见图2,它说明了Ballerina的客户端流式传输的工作原理。图2:BallerinagRPC客户端流CheckoutService客户端流流的完整实现可以在购物车微服务结帐资源函数中找到。最后,在结帐过程中,对在Golang中实现的Stock微服务进行gRPC调用,并通过扣除已售出的商品来更新库存。grpc-gatewaysyntax="proto3";packageretail_shop;optiongo_package="../stock;gen";import"google/api/annotations.proto";serviceStockService{rpcUpdateStock(UpdateStockRequest)返回(Stock){option(google.api.http)={//从对/api/v1/stock的POST请求路由到此方法put:"/api/v1/stock"body:"*"};}}消息UpdateStockRequest{字符串itemNumber=1;int32数量=2;}messageStock{stringitemNumber=1;int32数量=2;清单6:Stock微服务的服务契约(stock.proto)在本例中,面向外部的API调用相同的UpdateStock服务,并通过使用gRPC调用作为服务间调用。grpc-gateway是protoc的一个插件,它读取gRPC服务定义并生成一个反向代理服务器,将RESTfulJSONAPI转换为gRPC。图3:grpc网关grpc-gateway可帮助您同时提供gRPC和RESTfulAPI。以下命令生成一个GolanggRPC存根:protoc-I/usr/local/include-I。\-I$GOROOT/src\-I$GOROOT/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis\--go_out=plugins=grpc:.\stock.proto以下命令生成Golanggrpc-gateway代码:protoc-I/usr/local/include-I。\-I$GOROOT/src\-I$GOROOT/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis\--grpc-gateway_out=logtostderr=true:.\stock.proto以下命令生成stock.swagger.json:protoc-I/usr/local/include-I。\-I$GOROOT/src\-I$GOROOT/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis\-I$GOROOT/src\--swagger_out=logtostderr=true:../股票/gen/.\./stock.proto示例运行clonemicroservices-with-grpcgitrepo并按照README.md说明进行操作。结论gRPC相对较新,但其快速发展的生态系统和社区肯定会对微服务开发产生影响。由于gRPC是一种开放标准并受到所有主要编程语言的支持,因此它非常适合在多语言微服务环境中工作。作为一种通用的做法,我们可以使用gRPC来进行内部微服务之间的所有同步通信,或者我们可以使用grpc-gateway等新兴技术将其暴露为RESTfulAPI。除了我们在本文中讨论的内容之外,gRPC功能(如截止日期、取消、通道和xDS支持)将为开发人员提供构建有效微服务的能力和灵活性。