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

news2024/11/25 0:38:52

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 main

import (
    "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 / 4
    log.Println("(REST) received new order", reqId)

    var submitOrderDTO common.SubmitOrderRequestDTO

    b, _ := 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 2
    r, err := server.httpClient.Do(invoiceRequest)
    // just close the response body
    r.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 3
    r, err = server.httpClient.Do(shippingRequest)
    // just close the response body
    r.Body.Close()
    if err != nil {
        panic(err)
    }

    handleProductDecrement(1)
    // STEP 5
    res.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 main

import (
    "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.SubmitOrderRequestDTO
    err := 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 main

import (
    "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.CustomerServiceClient
    ShippingService pb.ShippingServiceClient
    pb.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 server
    pb.RegisterOrderServiceServer(grpcServer, &orderService)

    // start the server
    log.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.coloradmin.cn/o/715206.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Element-Plus select选择器-下拉组件错位bug(有高度滚动时)

1. bug重现 由于项目不便展示&#xff0c;因此在官网复现bug https://element-plus.org/zh-CN/component/select.html#基础用法 2. 调试 源码调试时发现下拉菜单是直接放在body 元素里&#xff0c;这时候希望它不要直接放在body里&#xff0c; 查阅文档看到这两个属性&#x…

直线模组怎样保养才合理?

直线模组简单来说就是自动领域中做直线来回往返运动的传动部件&#xff0c;被广泛应用于自动化领域市场当中&#xff0c;可实现二轴、三轴、龙门等多轴搭建的形式&#xff0c;也可用于水平使用、垂直移载使用&#xff0c;能满足自动化领域中大多数的用户。 至于直线模组的保养&…

基于Java+控制台+Mysql实现图书管理系统

基于Java控制台Mysql实现图书管理系统 一、系统介绍二、功能展示1.主页2.添加图书3.图书列表4.根据图书名称号查询图书信息5.根据编号删除图书信息6.根据编号编辑图书信息7.退出系统 三、数据库四、其它1.其他系统实现2.获取源码 一、系统介绍 使用控制台Mysql完成一个图书管理…

实现使用语音控制myCobot机械臂运动

基于语音识别技术的机器人手臂控制智能化尝试 介绍&#xff1a; 在电影《钢铁侠》中&#xff0c;我们看到托尼斯塔克在建造设备时与人工智能贾维斯交流。托尼向贾维斯描述了他需要的零件&#xff0c;贾维斯控制机械臂协助托尼完成任务。随着当今技术的发展&#xff0c;这种实现…

Redis内存策略

Redis内存回收 Redis之所以性能强&#xff0c;最主要的原因就是基于内存操作。然而单节点的Redis其内存大小不宜过大&#xff0c;否则会影响持久化或主从同步性能。 可以通过修改配置文件来设置Redis的最大内存&#xff1a; # 格式&#xff1a; # maxmemory <bytes> #…

[PyTorch][chapter 44][时间序列表示方法2]

前言 bag of words 技术里面除了上面我们讲的&#xff0c;还包括 word2Vec TF-IDF,Glove, co-occurrence matrix 等技术 论文总览 1 Abstract: 摘要 2 Introduction: 前人工作&#xff0c;本文目标 3 Model Architectures: LSA LDA 4 New Log-Linear model 5 Result…

STM32面试知识点总结分析

一、STM32F1和F4的区别&#xff1f; 内核不同&#xff1a;F1是Cortex-M3内核&#xff0c;F4是Cortex-M4内核&#xff1b; 主频不同&#xff1a;F1主频72MHz&#xff0c;F4主频168MHz&#xff1b; 浮点运算&#xff1a;F1无浮点运算单位&#xff0c;F4有&#xff1b; 功能性…

【推荐】win 安装 rust 1.70

目录 一、下载二、安装先决条件MinGW三、安装Rust四、配置国内镜像五、检查是否安装成功五、参考文章 一、下载 官网地址&#xff1a;https://www.rust-lang.org/zh-CN/ 二、安装先决条件MinGW win 安装 C运行环境 - MinGW 三、安装Rust 3.1首先设置安装路径和环境变量 配…

容器常用操作命令概述

容器镜像就可以说是一个“样板间”&#xff0c;把运行进程所需要的文件系统、依赖库、环境变量、启动参数等所有信息打包整合到了一起。之后镜像文件无论放在哪里&#xff0c;操作系统都能根据这个“样板间”快速重建容器&#xff0c;应用程序看到的就会是一致的运行环境了。 …

EMC学习笔记(十四)射频PCB的EMC设计(一)

射频PCB的EMC设计&#xff08;一&#xff09; 1.板材1.1 普通板材1.2 射频专用板材 2.隔离与屏蔽2.1 隔离2.2 器件布局2.3 敏感电路和强辐射电路2.4 屏蔽材料和方法2.5 屏蔽腔的尺寸 近十年来&#xff0c;移动通信飞速发展&#xff0c;在移动通信设备的设计、测试、安装和操作维…

你有几种实现点击锁定屏慕,输入密码解锁的效果的思路?

文章目录 点击锁定&#xff0c;密码解锁1 需求解析2 思路2.1输入密码时回显2.1.1 利用prompt的输入值来判断 2.2 输入密码时不回显2.2.1实现思路 点击锁定&#xff0c;密码解锁 1 需求解析 点击指定的按钮&#xff0c;当前的屏幕变为空白&#xff0c; 弹出对话框&#xff0c;当…

ChatGPT付费创作系统V2.0.8独立版 +WEB端+ H5端 + 小程序端+新增PC端绘画

ChatGPT付费创作系统V2.0.8提供单独的升级包&#xff0c;升级前先更新至2.0.7版本&#xff0c;再上传升级包替换&#xff0c;同是导入升级包数据库。ChatGPT2.0.8独立版核心功能增加了PC端绘画功能&#xff0c;绘画功能采用其他绘画接口-意间AI&#xff0c;后台一些小细节的优化…

电脑c盘哪些文件可以删除?这4种文件可以删!

我的电脑使用了快两年了&#xff0c;现在经常很卡顿&#xff0c;不知道是不是因为电脑c盘空间快满了导致的。电脑c盘哪些文件可以删除呢&#xff1f;请大家帮我看看&#xff01; 电脑的c盘一般会存储很多的临时文件和系统的文件。然而这些文件并不是都有用的。有些文件我们可以…

[golang 微服务] 9.go-micro + gorm实现商品微服务的分页查询

一.引入 上一节讲解 go-micro的负载均衡操作&#xff0c;go Web框(Gin,Beego)调用go-micro微服务等技术&#xff0c;这一节来看看 go-micro gorm实现 商品微服务的 分页查询操作,go-micro微服务中使用GORM和go web框架gin、beego中使用 GORM都是一样的 二.创建goodsinfo服务端…

git学习使用技巧

目录 关于版本控制 本地版本控制系统 集中化的版本控制系统 分布式版本控制系统 Git 是什么 直接记录快照&#xff0c;而非差异比较 近乎所有操作都是本地执行 Git 保证完整性 Git 一般只添加数据 三种状态 起步 - 初次运行 Git 前的配置 初次运行 Git 前的配置 用户…

2023年7月1日【青书学堂】考试 统计学(高起专)

2023年7月1日【青书学堂】考试 统计学(高起专) 注意:答案仅供参考 成绩:84.34 分 第1题 单选题 下列哪两个变量之间的相关程度高 ( ) A: 商品销售额和商品销售量的相关系数是0.9 B: 商品销售额与商业利润率的相关系数是0.84 C: 平均流通费用率与商业利润率的相关系数是-0…

ACL2023 | Multi-CLS BERT:传统集成的有效替代方案

进NLP群—>加入NLP交流群 在本文中&#xff0c;介绍了 Multi-CLS BERT&#xff0c;这是传统集成方法的有效替代方案。 这种基于 CLS 的预测任务的新颖方法旨在提高准确性&#xff0c;同时最大限度地减少计算和内存需求。 通过利用具有不同参数化和目标的多个 CLS token&…

数据结构-查找

数据结构-查找 1 知识框架2 顺序查找和折半查找2.1 顺序查找2.2折半查找2.3 分块查找 3 树型查找3.1 二叉排序树3.2 平衡二叉树3.3 B树和B树3.3.1 B树3.3.2 B树 4 散列表4.1 构造方法4.2 处理冲突的办法4.3性能分析 1 知识框架 2 顺序查找和折半查找 2.1 顺序查找 1.一般线性…

vue 基础入门

目录 vue 简介 vue 的特性 数据驱动视图 双向数据绑定 MVVM vue 的基本使用 vue 的调试工具 vue 的指令 1、内容渲染指令 2、属性绑定指令 3、事件绑定 4、双向绑定指令 v-model 5、 条件渲染指令 6 、列表渲染指令 vue过滤器 定义过滤器基本使用 私有过滤器和全…

web安全php基础_phpstudy pro安装

phpstudy pro是什么 phpstudy是一个php运行环境的集成包&#xff0c;用户不需要去配置运行环境&#xff0c;就可以使用&#xff0c;phpstudy不仅是一款比较好用的php调试环境工具&#xff0c;并且还包括了开发工具和常用手册&#xff0c;对于新手是有很大帮助的 windows下ph…