当前位置: 首页 > 知识库问答 >
问题:

如何在微服务/事件驱动体系结构中处理HTTP请求?

巫懿轩
2023-03-14

背景:

我正在构建一个应用程序,建议的体系结构是基于微服务体系结构的事件/消息驱动的。

做事情的单一方式是,我有一个User/HTTP请求,它操作一些具有直接同步响应的命令。因此,响应相同的用户/HTTP请求是“无忧无虑的”。

问题:

用户向UI服务(有多个UI服务)发送一个HTTP请求,该服务向队列(Kafka/RabbitMQ/any)触发一些事件。N个服务拾取该事件/消息,并在此过程中发挥一些魔力,然后在某个时刻,相同的UI服务应拾取该响应并将其返回给发起HTTP请求的用户。请求处理是异步的,但是用户/HTTP请求-

问:如何向在这个不可知/事件驱动的世界中发起操作的同一个UI服务(通过HTTP与用户交互的服务)发送响应?

我的研究到目前为止,我一直在环顾四周,似乎有些人正在使用WebSocket解决这个问题。

但复杂的一层是需要有一些映射(RequestId)的表-

所以我不太愿意认为WebSocket是一种选择。但即使WebSocket适合面向Web的应用程序,连接到外部系统的API也不是一个好的体系结构。

** ** ** 更新: ** ** **

即使长轮询是WebService API的一个可能的解决方案,它具有202接受的一个位置头和一个重试后头,对于高并发性来说,它也不会是高性能的

但最重要的是,与我的情况相关的是,我有第三方API,例如支付系统,其中3DS系统具有由支付提供商系统处理的自动重定向,并且他们期望典型的请求/响应流,因此此模型对我不起作用,套接字模型也不起作用。

由于这个用例,HTTP请求/响应应该以典型的方式处理,我有一个哑客户机,它希望在后端处理过程的复杂性。

因此,我正在寻找一个解决方案,其中外部有一个典型的请求-

这是一个长轮询的示例,但此模型不适用于第三方API,例如3DS重定向上的支付提供商,这些API不在我的控制范围内。

 POST /user
    Payload {userdata}
    RETURNs: 
        HTTP/1.1 202 Accepted
        Content-Type: application/json; charset=utf-8
        Date: Mon, 27 Nov 2018 17:25:55 GMT
        Location: https://mydomain/user/transaction/status/:transaction_id
        Retry-After: 10

GET 
   https://mydomain/user/transaction/status/:transaction_id


共有3个答案

督德泽
2023-03-14

问:如何向在这个不可知/事件驱动的世界中发起操作的同一个UI服务(通过HTTP与用户交互的服务)发送响应?

因此,我正在寻找一个解决方案,其中外部我有一个典型的要求-

我认为重要的是要注意以下几点。

总之:

>

  • 系统的边缘可以是同步的,与此同时,它以异步的基于事件的方式在内部处理东西。参见:插图和背景。

    UI服务(Web服务器或API网关)可以直接启动一个异步/等待功能(在Node.js中编写),然后继续处理其他请求。然后,当UI服务从后端的另一个微服务接收到“OrderConfirmed”事件(例如,通过侦听Kafka日志)时,它将再次接收用户请求(回调),并将指定的响应发送给客户端。

    试试反应性交互网关(RIG),它会为你处理它。

    在长:

    >

    由于客户端发送同步请求,所以它被阻塞(用户必须在用户界面中等待)。但是,这并不意味着UI服务(Web服务器或API网关)必须被阻塞。它只需要触发一个异步/等待函数(用Node.js编写),然后继续处理其他请求。然后,当UI服务接收到OrderConfirmed事件时,它将再次拾取该函数(回调)并将指定的响应发送到客户端。

    后端边缘与第三方系统交互的任何(微)服务都可以通过典型的HTTP请求/响应流同步执行(尽管通常您也希望在此异步/等待,以便在第三方处理时释放自己的微服务资源)。当它同步接收到来自第三方的响应时,它可以向后端的其余部分发送一个异步事件(例如“StockResupplied”事件)。如果UI服务被设计为在给出响应之前等待此类事件,则它可以传播回客户端。

    灵感来源:Brad Irby对相关问题的回答。

    上述措施的替代办法是:

    以这样一种方式设计客户端的用户界面,用户不必阻塞/等待(可能已经得到了一个乐观的成功响应,符合“乐观用户界面”原则),但随后异步接收服务器发送事件(SSE)。

    >

    SSE在后台“只使用一个长期存在的HTTP连接”。

    您可能还想知道:

    >

    "与SSE相比,WebSocket的设置要复杂得多,任务要求也高。"

    资料来源:https://www.telerik.com/blogs/websockets-vs-server-sent-events

    此外:

    与Service Worker一起使用Web Push API是SSE的另一个选项,但它更复杂,会给用户带来烦人的弹出窗口,并且更适合在浏览器外部和/或Web应用关闭时发送通知。

    您可能还想看看:

    反应交互网关(Reactive Interaction Gateway,RIG),提供一些漂亮的架构图,以及说明此答案要点的自述文件。RIG是一个可扩展的免费开源API网关,用于微服务,处理SSE,WebSocket,长轮询等。Reactive Interaction Gateway(RIG)订阅Kafka主题,同时保持与所有活动前端的连接,将事件转发给它们所针对的用户,所有这些都以可扩展的方式进行。最重要的是,它还处理授权,所以你的服务也不必关心这个。"

  • 闾丘成双
    2023-03-14

    从更一般的角度来看,在接收请求时,您可以在当前请求上下文(即当请求对象在范围内时)中的队列上注册一个订阅者,该订阅者在责任服务完成其任务时接收来自责任服务的确认(类似于维护操作总数进度的状态机)。当达到终止状态时,它返回响应并删除侦听器。我认为这将适用于任何发布/订阅样式的消息队列。下面是我建议的一个过于简化的演示。

    // a stub for any message queue using the pub sub pattern
    let Q = {
      pub: (event, data) => {},
      sub: (event, handler) => {}
    }
    // typical express request handler
    let controller = async (req, res) => {
      // initiate saga
      let sagaId = uuid()
      Q.pub("saga:register-user", {
        username: req.body.username,
        password: req.body.password,
        promoCode: req.body.promoCode,
        sagaId: sagaId
      })
      // wait for user to be added 
      let p1 = new Promise((resolve, reject) => {
        Q.sub("user-added", ack => {
          resolve(ack)
        })
      })
      // wait for promo code to be applied
      let p2 = new Promise((resolve, reject) => {
        Q.sub("promo-applied", ack => {
          resolve(ack)
        })
      })
    
      // wait for both promises to finish successfully
      try {
        
        var sagaComplete = await Promise.all([p1, p2])
        // respond with some transformation of data
        res.json({success: true, data: sagaComplete})
    
      } catch (e) {
        logger.error('saga failed due to reasons')
        // rollback asynchronously
        Q.pub('rollback:user-added', {sagaId: sagaId})
        Q.pub('rollback:promo-applied', {sagaId: sagaId})
        // respond with appropriate status 
        res.status(500).json({message: 'could not complete saga. Rolling back side effects'})
      }
      
    }
    

    正如您可能知道的那样,这看起来像一个通用模式,可以抽象成一个框架来减少代码重复和管理交叉问题。这就是传奇模式的本质。客户端只会等待完成所需操作所需的时间(即使全部是同步的,也会发生这种情况),加上服务间通信带来的额外延迟。如果使用NodeJS或Python Tornado等基于事件循环的系统,请确保不要阻塞线程。

    简单地使用基于web套接字的推送机制并不一定能提高系统的效率或性能。但是,建议您使用套接字连接将消息推送到客户机,因为它使您的体系结构更通用(即使您的客户机的行为与您的服务类似)、一致,并允许更好地分离关注点。它还允许您独立扩展推送服务,而不必担心业务逻辑。可以扩展saga模式,以便在出现部分故障或超时时启用回滚,并使系统更易于管理。

    周朗
    2023-03-14

    正如我所期待的——人们试图把所有东西都融入一个概念中,即使它不符合这个概念。这不是批评,这是根据我的经验和阅读了你的问题和其他答案后得出的结论。

    是的,您是对的,微服务架构是基于异步消息传递模式的。然而,当我们谈论UI时,我脑海中有两种可能的情况:

    >

  • UI需要立即响应(例如读取操作或用户期望立即响应的命令)。这些不一定是异步的。如果屏幕上立即需要响应,为什么要增加消息传递和异步的开销?没有意义。微服务架构应该解决问题,而不是通过增加开销来创建新问题。

    可以对UI进行重组,以允许延迟响应(例如,UI可以在准备响应时提交命令、接收确认并让用户执行其他操作,而不是等待结果)。在这种情况下,可以引入异步。网关服务(UI直接与之交互)可以协调异步处理(等待完成事件等),准备好后,它可以与UI通信。我见过UI在这种情况下使用信号器,网关服务是一个接受套接字连接的API。如果浏览器不支持套接字,则最好回退到轮询。无论如何,重要的一点是,这只能在偶然情况下起作用:UI可以容忍延迟回答。

    如果微服务确实与您的情况相关(案例2),那么相应地构造UI流,并且后端的微服务不应该存在挑战。在这种情况下,您的问题归结为将事件驱动架构应用于服务集(edge是连接事件驱动和UI交互的网关微服务)。这个问题(事件驱动服务)是可以解决的,您知道这一点。您只需要决定是否可以重新思考UI的工作方式。

  •  类似资料:
    • 我是事件驱动微服务的新手,也是微服务本身的新手。我正在开发的系统并不大,它处理一堆文件,然后根据调用不同服务的数据。所以在我看来,一个好主意是,不要让服务调用其他服务来完成这项工作,而是将这些消息发送到发布/订阅队列,处理它们并将它们发送到相关主题,然后每个服务将订阅其中一个或几个主题,每隔几分钟,每个服务都会提取它们订阅的消息并发挥它们的魔力。这些服务也可以通过Rest来公开,以防您想强制执行它

    • 在我们公司,我们正在从一个巨大的单体应用程序过渡到微服务架构。这一决定的主要技术驱动因素是能够独立扩展服务的需求和开发的可扩展性——我们有十个Scrum团队在不同的项目(或“微服务”)中工作。 过渡过程很顺利,我们已经开始受益于这种新的技术和组织结构的优势。现在,另一方面,我们正在努力解决一个主要的痛点:如何管理这些微服务之间依赖关系的“状态”。 让我们举一个例子:其中一个微服务处理用户和注册。该

    • 我最近开始使用Node.js,我必须构建一个应该使用多个Express.js服务的体系结构。其中一些服务必须位于一台服务器上,另一台则位于其他服务器上。我想构建一个基础服务(像API网关),但是我不知道在这个网关和微服务之间,或者在两个微服务之间进行通信的合适方式是什么。 目前我正在研究一个基于此的解决方案:

    • 我们正在尝试将我们的单片核心拆分为微服务,并使用消息系统(如Kafka)添加一些相互连接的新服务。 下一阶段是创建APIendpoint,以便通过Api网关在移动应用程序和微服务之间进行通信。 开发API网关向微服务传输数据或从微服务传输数据的好解决方案是什么? 使用消息系统作为请求-应答系统(将API网关上的请求转换为消息命令,等待消息系统的响应,并提供状态或必要的数据) 在必要的微服务(例如使

    • 我是web服务新手,正在阅读Martin Kalin的《Java Webservices》一书。我已经了解了它最初的基本概念,有一个问题: 假设将HTTP请求(包含SOAP消息信封)发送到JavaWeb服务()。该请求是否由Servlet内部处理,Servlet提取SOAP消息并将其转换为相应Java域对象,然后调用服务实现bean? 无论Metro和Axis等现成框架如何,这个问题都是通用的。只

    • 总而言之,我们有: 产品类别: 产品ID、产品名称 订单类别: 订单ID、产品ID、用户ID、订单日期 这些方法中的任何一种可以被认为是最佳实践吗?还是有不同的解决方案?