【性能工程】性能比较:REST vs gRPC vs 异步通信

6ab5839eb0268fbe463fdacb2248ce0c.jpeg

微服务之间的通信方式对微服务架构内的各种软件质量因素有重大影响(有关微服务网络内通信的关键作用的更多信息)。沟通方式会影响软件的性能和效率等功能性需求,以及可变性、可扩展性和可维护性等非功能性需求。因此,有必要考虑不同方法的所有优缺点,以便在具体用例中合理选择正确的沟通方式。
本文比较了以下样式:REST、gRPC 和使用消息代理 (RabbitMQ) 的异步通信,在微服务网络中了解它们对软件的性能影响。沟通方式的一些最重要的属性(反过来会影响整体表现)是:

  • 数据传输格式

  • 连接处理

  • 消息序列化

  • 缓存

  • 负载均衡

数据传输格式


虽然使用 AMQP 协议(高级消息队列协议)的异步通信和 gRPC 通信使用二进制协议进行数据传输,但 REST-API 通常以文本格式传输数据。与基于文本的协议相比,二进制协议的效率要高得多 [1,2]。因此,使用 gRPC 和 AMQP 进行通信会导致较低的网络负载,而使用 REST API 时可以预期更高的网络负载。

连接处理


REST-API 通常建立在 HTTP/1.1 协议之上,而 gRPC 依赖于 HTTP/2 协议的使用。HTTP/1.1、HTTP/2 以及 AMQP 都在传输层使用 TCP 来确保稳定的连接。要建立这样的连接,需要在客户端和服务器之间进行详细的通信。这些性能影响同样适用于所有沟通方式。但是,对于 AMQP 或 HTTP/2 连接,通信连接的初始建立只需要执行一次,因为这两种协议的请求都可以多路复用。这意味着可以将现有连接重用于使用异步或 gRPC 通信的后续请求。另一方面,使用 HTTP/1.1 的 REST-API 为与远程服务器的每个请求建立新连接。

5a078cc58389f909ab6a60b6f6a477a6.jpeg

Necessary communication to establish a TCP-Connection

消息序列化


通常,在通过网络传输消息之前,使用 JSON 执行 REST 和异步通信以进行消息序列化。另一方面,gRPC 默认以协议缓冲区格式传输数据。协议缓冲区通过允许使用更高级的序列化和反序列化方法来编码和使用消息内容 [1] 来提高通信速度。然而,选择正确的消息序列化格式取决于工程师。关于性能,protocol buffers 有很多优势,但是当必须调试微服务之间的通信时,依赖人类可读的 JSON 格式可能是更好的选择。


缓存


有效的缓存策略可以显着减少服务器的负载和必要的计算资源。由于其架构,REST-API 是唯一允许有效缓存的通信方式。REST-API 响应可以被其他服务器和缓存代理(如 Varnish)缓存和复制。这减少了 REST 服务的负载并允许处理大量的 HTTP 流量 [1]。但是,这只有在基础架构上部署更多服务(缓存代理)或使用第三方集成后才有可能。gRPC 官方文档和 RabbitMQ 文档都没有介绍任何形式的缓存。


负载均衡


除了临时存储响应之外,还有其他技术可以提高服务速度。负载均衡器(例如 mod_proxy)可以高效透明的方式在服务之间分配 HTTP 流量 [1]。这可以实现使用 REST API 的服务的水平扩展。Kubernetes 作为容器编排解决方案,无需任何调整即可对 HTTP/1.1 流量进行负载均衡。另一方面,对于 gRPC,需要在网络上提供另一个服务(linkerd)[3]。异步通信无需进一步的帮助即可支持负载平衡。消息代理本身扮演负载均衡器的角色,因为它能够将请求分发到同一服务的多个实例。消息代理为此目的进行了优化,并且它们的设计已经考虑到它们必须具有特别可扩展性的事实[1]。


实验


为了能够评估各个通信方法对软件质量特性的影响,开发了四个微服务来模拟电子商务平台的订单场景。

b7cc3fcfbe0062849d581fded0a2490e.png

微服务部署在由三个不同服务器组成的自托管 Kubernetes 集群上。服务器通过千兆 (1000 Mbit/s) 网络连接,位于同一数据中心,服务器之间的平均延迟为 0.15 毫秒。每次实验运行时,各个服务都部署在相同的服务器上。这种行为是通过 pod 亲和性来实现的。
所有微服务都是用 GO 编程语言实现的。个别服务的实际业务逻辑,例如与数据库的通信,为了不被选择的通信方法之外的其他影响,故意不实现。因此,收集的结果不能代表这种类型的微服务架构,但可以使实验中的通信方法具有可比性。相反,业务逻辑的实现是通过将程序流程延迟 100 毫秒来模拟的。因此,在通信中,总延迟为 400 毫秒。
开源软件k6用于实现负载测试。

实现


Golang 标准库中包含的 net/http 模块用于提供 REST 接口。使用标准库中也包含的 encoding/json 模块对请求进行序列化和反序列化。所有请求都使用 HTTP POST 方法。
“谈话很便宜。给我看看密码。”

package mainimport ("bytes""encoding/json""fmt""io/ioutil""log""net/http""github.com/google/uuid""gitlab.com/timbastin/bachelorarbeit/common""gitlab.com/timbastin/bachelorarbeit/config"
)type restServer struct {httpClient http.Client
}func (server *restServer) handler(res http.ResponseWriter, req *http.Request) {// only allow post request.if req.Method != http.MethodPost {bytes, _ := json.Marshal(map[string]string{"error": "invalid request method",})http.Error(res, string(bytes), http.StatusBadRequest)return}reqId := uuid.NewString()// STEP 1 / 4log.Println("(REST) received new order", reqId)var submitOrderDTO common.SubmitOrderRequestDTOb, _ := ioutil.ReadAll(req.Body)err := json.Unmarshal(b, &submitOrderDTO)if err != nil {log.Fatalf(err.Error())}checkIfInStock(1)invoiceRequest, _ := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/invoices", config.MustGet("customerservice.rest.address").(string)), bytes.NewReader(b))// STEP 2r, err := server.httpClient.Do(invoiceRequest)// just close the response bodyr.Body.Close()if err != nil {panic(err)}shippingRequest, _ := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/shipping-jobs", config.MustGet("shippingservice.rest.address").(string)), bytes.NewReader(b))// STEP 3r, err = server.httpClient.Do(shippingRequest)// just close the response bodyr.Body.Close()if err != nil {panic(err)}handleProductDecrement(1)// STEP 5res.WriteHeader(201)res.Write(common.NewJsonResponse(map[string]string{"state": "success",}))
}func startRestServer() {server := restServer{httpClient: http.Client{},}http.HandleFunc("/orders", server.handler)done := make(chan int)go http.ListenAndServe(config.MustGet("orderservice.rest.port").(string), nil)log.Println("started rest server")<-done
}

RabbitMQ 消息代理用于异步通信,部署在同一个 Kubernetes 集群上。消息代理和各个微服务之间的通信使用 github.com/spreadway/amqp 库进行。该库是 GO 编程语言官方文档推荐的。

package mainimport ("encoding/json""log""github.com/streadway/amqp""gitlab.com/timbastin/bachelorarbeit/common""gitlab.com/timbastin/bachelorarbeit/config""gitlab.com/timbastin/bachelorarbeit/utils"
)func handleMsg(message amqp.Delivery, ch *amqp.Channel) {log.Println("(AMQP) received new order")var submitOrderRequest common.SubmitOrderRequestDTOerr := json.Unmarshal(message.Body, &submitOrderRequest)utils.FailOnError(err, "could not unmarshal message")checkIfInStock(1)handleProductDecrement(1)ch.Publish(config.MustGet("amqp.billingRequestExchangeName").(string), "", false, false, amqp.Publishing{ContentType: "application/json",Body:        message.Body,})}func getNewOrderChannel(conn *amqp.Connection) (*amqp.Channel, string) {ch, err := conn.Channel()utils.FailOnError(err, "could not create channel")ch.ExchangeDeclare(config.MustGet("amqp.newOrderExchangeName").(string), "fanout", false, false, false, false, nil)queue, err := ch.QueueDeclare(config.MustGet("orderservice.amqp.consumerName").(string), false, false, false, false, nil)utils.FailOnError(err, "could not create queue")ch.QueueBind(queue.Name, "", config.MustGet("amqp.newOrderExchangeName").(string), false, nil)return ch, queue.Name
}func startAmqpServer() {conn := common.NewAmqpConnection(config.MustGet("amqp.host").(string))defer conn.Close()orderChannel, queueName := getNewOrderChannel(conn)msgs, err := orderChannel.Consume(queueName,config.MustGet("orderservice.amqp.consumerName").(string),true,false,false,false,nil,)utils.FailOnError(err, "could not consume")forever := make(chan bool)log.Println("started amqp server:", queueName)go func() {for d := range msgs {go handleMsg(d, orderChannel)}}()<-forever
}

gRPC 客户端和服务器使用 gRPC 文档推荐的 google.golang.org/grpc 库。数据的序列化是使用协议缓冲区完成的。

package mainimport ("log""net""context""gitlab.com/timbastin/bachelorarbeit/common""gitlab.com/timbastin/bachelorarbeit/config""gitlab.com/timbastin/bachelorarbeit/pb""gitlab.com/timbastin/bachelorarbeit/utils""google.golang.org/grpc"
)type OrderServiceServer struct {CustomerService pb.CustomerServiceClientShippingService pb.ShippingServiceClientpb.UnimplementedOrderServiceServer
}func (s *OrderServiceServer) SubmitOrder(ctx context.Context, request *pb.SubmitOrderRequest) (*pb.SuccessReply, error) {log.Println("(GRPC) received new order")if s.CustomerService == nil {s.CustomerService, _ = common.NewCustomerServiceClient()}if s.ShippingService == nil {s.ShippingService, _ = common.NewShippingServiceClient()}checkIfInStock(1)// call the product service on each iteration to decrement the product._, err := s.CustomerService.CreateAndProcessBilling(ctx, &pb.BillingRequest{BillingInformation: request.BillingInformation,Products:           request.Products,})utils.FailOnError(err, "could not process billing")// trigger the shipping job._, err = s.ShippingService.CreateShippingJob(ctx, &pb.ShippingJob{BillingInformation: request.BillingInformation,Products:           request.Products,})utils.FailOnError(err, "could not create shipping job")handleProductDecrement(1)return &pb.SuccessReply{Success: true}, nil
}func startGrpcServer() {listen, err := net.Listen("tcp", config.MustGet("orderservice.grpc.port").(string))if err != nil {log.Fatalf("could not listen: %v", err)}grpcServer := grpc.NewServer()orderService := OrderServiceServer{}// inject the clients into the serverpb.RegisterOrderServiceServer(grpcServer, &orderService)// start the serverlog.Println("started grpc server")if err := grpcServer.Serve(listen); err != nil {log.Fatalf("could not start grpc server: %v", err)}
}

收集数据


检查成功和失败的订单处理的数量,以确认它们所经过的时间。如果直到确认的持续时间超过 900 毫秒,则订单流程被解释为失败。选择此持续时间是因为在实验中可能会出现无限长的等待时间,尤其是在使用异步通信时。每次试验都会报告失败和成功订单的数量。
每种架构总共进行了 12 次不同的测量,每种情况下同时请求的数量不同,传输的数据量也不同。首先,在低负载下测试每种通信方式,然后在中等负载下,最后在高负载下测试。低负载模拟 10 个,中等负载模拟 100 个,高负载模拟 300 个同时向系统发出的请求。在这六次测试运行之后,要传输的数据量会增加,以了解各个接口的序列化方法的效率。数据量的增加是通过订购多个产品来实现的。


结果


gRPC API 架构是实验中研究的性能最佳的通信方法。在低负载下,它可以接受的订单数量是使用 REST 接口的系统的 3.41 倍。此外,平均响应时间比 REST-API 低 9.71 毫秒,比 AMQP-API 低 9.37 毫秒。

本文 :https://architect.pub/performance-comparison-rest-vs-grpc-vs-asynchronous-communication
讨论:知识星球【首席架构师圈】或者加微信小号【ca_cto】或者加QQ群【792862318】
公众号
 
【jiagoushipro】
【超级架构师】
精彩图文详解架构方法论,架构实践,技术原理,技术趋势。
我们在等你,赶快扫描关注吧。
微信小号
 
【ca_cea】
50000人社区,讨论:企业架构,云计算,大数据,数据科学,物联网,人工智能,安全,全栈开发,DevOps,数字化.
 

QQ群
 
【285069459】深度交流企业架构,业务架构,应用架构,数据架构,技术架构,集成架构,安全架构。以及大数据,云计算,物联网,人工智能等各种新兴技术。
加QQ群,有珍贵的报告和干货资料分享。

视频号【超级架构师】
1分钟快速了解架构相关的基本概念,模型,方法,经验。
每天1分钟,架构心中熟。

知识星球【首席架构师圈】向大咖提问,近距离接触,或者获得私密资料分享。 

喜马拉雅【超级架构师】路上或者车上了解最新黑科技资讯,架构心得。【智能时刻,架构君和你聊黑科技】
知识星球认识更多朋友,职场和技术闲聊。知识星球【职场和技术】
领英Harryhttps://www.linkedin.com/in/architect-harry/
领英群组领英架构群组https://www.linkedin.com/groups/14209750/
微博‍‍【超级架构师】智能时刻‍
哔哩哔哩【超级架构师】

抖音【cea_cio】超级架构师

快手【cea_cio_cto】超级架构师

小红书【cea_csa_cto】超级架构师 

网站CIO(首席信息官)https://cio.ceo
网站CIO,CTO和CDOhttps://cioctocdo.com
网站架构师实战分享https://architect.pub   
网站程序员云开发分享https://pgmr.cloud
网站首席架构师社区https://jiagoushi.pro
网站应用开发和开发平台https://apaas.dev
网站开发信息网https://xinxi.dev
网站超级架构师https://jiagou.dev
网站企业技术培训https://peixun.dev
网站程序员宝典https://pgmr.pub    
网站开发者闲谈https://blog.developer.chat
网站CPO宝典https://cpo.work
网站首席安全官https://cso.pub    ‍
网站CIO酷https://cio.cool
网站CDO信息https://cdo.fyi
网站CXO信息https://cxo.pub

谢谢大家关注,转发,点赞和点在看。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/8365.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

宝塔Panel搭建Python环境

服务器安装python环境 找到软件商店 应用搜索 输入&#xff1a;python 安装Python项目管理器2.4 开启首页显示 回到首页 找到python管理器并点击进入 安装对应的python版本 到这里 服务器就可以告一段落了 在本地开发服务端应用并上传服务器 将写好的python应用 导出依赖…

pytorch学习指南

安装anaconda&#xff1a; https://blog.csdn.net/fan18317517352/article/details/123035625 教程&#xff1a;bilibili up主&#xff1a;一只小土堆 构建pytorch空间 pytorch安装 查看cpu 安装命令pytorch&#xff1a;conda install pytorch torchvision torchaudio cpu…

六大排序——(插入、希尔、选择、交换、归并、计数)

目录 一、插入排序 二、希尔排序 三、选择排序 1&#xff09;直接选择排序&#xff1a; 2&#xff09;堆排序 四、交换排序 1&#xff09;冒泡排序 2&#xff09;快速排序 1、Hoare版 2、挖坑法 3、前后指针 快排优化 快速排序非递归来实现 快排总结 五、归并排…

OpenCV转换HDR图像与源码分析

我们常见的图像位深一般是8bit&#xff0c;颜色范围[0, 255]&#xff0c;称为标准动态范围SDR(Standard Dynamic Range)。SDR的颜色值有限&#xff0c;如果要图像色彩更鲜艳&#xff0c;那么就需要10bit&#xff0c;甚至12bit&#xff0c;称为高动态范围HDR(High Dynamic Range…

Access-Control-Allow-Origin跨域解决及详细介绍

重要声明&#xff1a;本文章仅仅代表了作者个人对此观点的理解和表述。读者请查阅时持自己的意见进行讨论。 首先&#xff0c;跨域不是问题。是一种安全机制。 这是你在开发时、上线前就必须提前考虑到的安全问题并且采取合适的手段去避免这个问题带来的程序错误。不过通常情况…

零拷贝详解

目录 一、什么是零拷贝 二、传统的IO执行流程 三、零拷贝相关的知识点回顾 1、内核空间&用户空间 2、用户态&内核态 3、上下文切换 4、虚拟内存 5、DMA技术 四、零拷贝实现的几种方式 1、mmapwrite实现的零拷贝 2、sendfile实现的零拷贝 3、sendfileDMA sc…

【若依】框架搭建,前端向后端如何发送请求,验证码的实现

若依框架 若依框架&#xff08;Ruoyi&#xff09;是一款基于Spring Boot和Spring Cloud的开源快速开发平台。它提供了一系列的基础功能和通用组件&#xff0c;能够帮助开发者快速构建企业级应用。若依框架采用了模块化的设计理念&#xff0c;用户可以选择需要的功能模块进行集…

STM32中static和extern的用法

static&#xff1a; A. static变量 称为静态变量。根据变量的类型可以分为静态局部变量和静态全程变量。 1. 静态局部变量 它与局部变量的区别在于: 在函数退出时, 这个变量始终存在, 但不能被其它 函数使用, 当再次进入该函数时, 将保存上次的结果。其它与局部变量一样。…

观察级水下机器人第一次使用总结2023年6月

最近有个科研项目需要用到ROV&#xff0c;其合同三年之前就签订了&#xff0c;由于疫情的影响&#xff0c;一直没有执行。刚好我们的ROV也验收了&#xff0c;正好派上用场。因为属于ROV使用的菜鸟级&#xff0c;我们邀请厂家无锡智海张工和陈工&#xff0c;中海辉固ROV操作经验…

合理组织安卓活动

本文所有代码均存放于https://github.com/MADMAX110/Starbuzz 开始构建一个应用时&#xff0c;你会考虑这个应用要包含什么&#xff0c;会有各种各样的很多想法&#xff0c;如何组织这些想法来建立一个直观、清晰的应用。 一、活动归类 要组织各种各样的活动&#xff0c;有一种…

详解Ribbon

目录 1.概述 2.使用 2.1.引入 2.2.启用 2.3.切换负载均衡算法 3.负载均衡源码分析 3.1.接口 3.2.抽象类 3.3.选择服务器 3.4.原子性 4.自定义负载均衡算法 1.概述 Ribbon是Netflix开源的一个客户端负载均衡库&#xff0c;也是Spring Cloud Netflix项目的核心组件之…

vue3学习之路

Vue3简介 面临的问题&#xff1a;随着功能的增长&#xff0c;复杂组件的代码变得难以维护&#xff0c;Vue3 就随之而来&#xff0c;TypeScript 使用的越来越多&#xff0c;Vue3就是 TS 写的所以能够更好的支持 TypeScript 在这里介绍就这么简单 vue2 的绝大多数的特性 在 Vu…