处理接口超时_架构设计|异步请求如何同步处理?

3585a6cc4d5cbf7aa8d74dda6756d275.png

本文创意来自一次业务需求,这次需要接入一个第三方外部服务。由于这个服务只提供异步 API,为了不影响现有系统同步处理的方式,接入该外部服务时,应用对外屏蔽这种差异,内部实现异步请求同步。

全文摘要

  • 异步给现有架构带来的问题
  • Dubbo 异步转同步解决方法
  • 异步转同步架构设计方案

0x00. 前言

现有一个系统,整体架构如下所示:

d75275746436b594c996a32875d44cfe.png

这是一个很常见的同步设计方案,上游系统需要等待下游系统接口返回调用结果。

现在需要接入另外一个第三方服务 B,该服务与服务 A 最大区别在于,这是一个异步 API。调用之后,仅仅返回受理成功,处理结果后续通过异步通知返回。

接入之后,整体架构如下所示:

b9b527035514252ce009db4cb83c5961.png

由于网络隔离策略,通知接收程序与通信服务需要单独分开部署。若没此要求,可以将通信服务 B 与通知接收程序合并成一个应用。

另外图中所有应用采用双节点部署。

为了不影响 OpenAPI 上游系统同步处理逻辑,通信服务 B 调用第三方服务之后,不能立刻返回,需要等待结果通知,拿到具体返回结果。这就需要通信服务 B 内部将异步转为同步。

这就是一个典型的异步转同步问题,整个过程涉及两个问题。

  1. 通信服务 B 业务线程如何进入等待状态?又如何唤醒正确等待线程?
  2. 由于通信服务 B 双节点部署,通知接收程序如何将结果转发到正在等待处理的节点?

问题 1 的解决方案参考了 Dubbo 设计思路。

我们在使用 Dubbo 调用远程服务时,默认情况下,这是一种阻塞式调用方式,即 Consumer 端代码一直阻塞等待,直到 Provider 端返回为止。

由于 Dubbo 底层基于 Netty 发送网络请求,这其是一个异步的过程。为了让业务线程能同步等待,这个过程就需要将异步转为同步。

0x01. Dubbo 异步转同步解决办法

1.1 业务线程同步阻塞

Dubbo 发起远程调用代码位于 DubboInvoker#doInvoke:

Dubbo 版本为:2.6.X 版本。2,7.X 重构 DefaultFuture ,但是本质原理还是一样。

d18a9f0dca4a5ed4adbe6dde3bd321c4.png

默认情况下,Dubbo 支持同步调用方式,这里将会创建 DefaultFuture 对象。

5571cddd967b948bd6dc726510c96f54.png

这里有个非常重要逻辑,每个请求生成一个唯一 ID,然后将 ID 与 DefaultFuture 映射关系,存入 Map 中。

这个请求 ID 在之所以这么重要,是因为消费者并发调用服务发送请求,同时将会有多个业务线程进入阻塞。当收到响应之后,我们需要唤醒正确的等待线程,并将处理结果返回。

通过 ID 这个唯一映射的关系,很自然可以找到其对应 DefaultFuture,唤醒其对应的业务线程。

f99a36e1dbbeede7c3d65804e064518f.png

业务线程调用 DefaultFuture#get方法进入阻塞。这段代码比较简单,通过调用 Condition#await 阻塞线层。

e3c2ec9232d5c927de3d6b85f61391c8.png

1.2 唤醒业务线程

当消费者接收到服务提供者的返回结果,将会调用 DefaultFuture#received 方法。

d841e102ecd07deb549c08fc1a33998f.png
e74a1bdd7b080d521b1b2b99766d885f.png

通过响应对象中的唯一 ID,找到其对应 DefaultFuture 对象,从而将结果设置 DefaultFuture 对象中,然后唤醒的相应的业务线程。

这里实际有个优化点,使用 done#signalAll 代替 done#signal。使用 condition 等待通知机制的时候需要注意这一点。

详情参考:https://github.com/apache/dubbo/issues/3678

1.3 设计注意点

正常情况下,当消费者接收到响应之后,将会从 FUTURES 这个 Map 移除 DefaultFuture 。

但是在异常情况下,服务提供者若处理缓慢,不能及时返回响应结果,消费者业务线程将会因为超时苏醒。这种情况下 FUTURES 积压了无效 DefaultFuture 对象。如果不及时清理,极端情况下,将会发生 OOM

DefaultFuture 内部将会开启一个异步线程,定时轮询 FUTURES 判断 DefaultFuture 超时时间,及时清理已经无效(超时)的 DefaultFuture。

c3eaecfb7951733ec6f16bd56885be4f.png

0x02. 转发方案设计

根据 Dubbo 解决思路,问题 1 解决办法就比较简单了。具体流程如下:

  1. 通信服务 B 内部生成一个唯一请求 ID ,发给第三方服务
  2. 若请求成功,内部版使用 Map 存储对应关系,并使业务线程阻塞等待
  3. 通信服务 B 收到异步通知结果,通过 ID 查找对应业务线程,唤醒的相应的线程

这个设计过程需要注意设置合理的超时时间,这个超时时间需要考虑远程服务调用耗时,可以参考如下公式:

业务线程等待时间=通信服务 B 接口的超时时间 - 调用第三方服务 B 接口消耗时间

这里就不贴出具体的代码,详细代码参考 Dubbo DefaultFuture。

接下来重点看下通知服务如何将结果转发给正确的通信服务 B 的节点。这里想到两种方案:

  1. SocketServer 方案
  2. MQ 方案

2.1 SocketServer

通信服务 B 使用 SocketServer 构建一个服务接收程序,当通知接收程序收到第三方服务 B 通知时,通过 Socket 将结果转发给通信服务 B。

整个系统架构如下所示:

72d19fbb4e6b3236fea3bd22c5a14ffc.png

由于生产服务双节点部署,通知接收程序就不能写死转发地址。这里我们将请求 ID 与通信服务 B socket 服务地址关系存入 Redis 中,然后通知接收程序通过 ID 找到正确的地址。

这个方案说实话有点复杂。

第一 SocketServer 编码难度较大,编写一个高效 SocketServer 就比较难,一不小心可能产生各种 Bug

第二通信服务 B 服务地址配置在配置文件中,由于两个节点地址不同,这就导致同一应用存在不同配置。这对于后面维护就很不友好。

第三额外引入 Redis 依赖,系统复杂度变高。

2.2 MQ 方案

相对 SocketServer 方案,MQ 方案相对简单,这里采用 MQ 广播消费的方式,架构如图所示:

52b6f3c64ffad06686b808e9412afef1.png

通知接收程序收到异步通知之后,直接将结果发送到 MQ。

通信服务 B 开启广播消费模式,拉取 MQ 消息。

通信服务 B1 拉取消息,通过请求 ID 映射关系,没找到内部等待的线程,知道这不是自己的等待消息,于是 B1 直接丢弃即可。

通信服务 B_2 拉取消息,通过请求 **ID** 映射关系,顺利找到正在等待的线程,然后可以唤醒等待线程,返回最后的结果。

对比 SocketServer 方案,MQ 方案整体流程比较简单,编程难度低,也没用存在特殊的配置。

不过这个方案十分依赖 MQ 消息实时性,若 MQ 消息投递延迟很高,这就会导致通信服务 B 业务线程超时苏醒,业务异常返回。

这里我们选择使用 RocketMQ,长轮询 Pull 方式,可保证消息非常实时,

综上,这里采用 MQ 的方案。

0x03. 总结

异步转同步我们需要解决同步阻塞,以及如何唤醒的问题。

阻塞/唤醒可以分别使用 Condition#await/signalAll。不过这个过程我们需要生成一个唯一请求 ID,并且保存这个 ID 与业务线程映射关系。后续等到结果返回我们才能通过唯一 ID 唤醒正确等待线程。

只要了解上面几点,异步转同步的问题就就可以迎刃而解。

另外,如果你也有碰到异步转同步问题,本文的方案希望对你有帮助。如果你有其他设计方案,欢迎留言,一起讨论~

参考资料

  1. http://dubbo.apache.org/zh-cn/docs/sourcecodeguide/service-invoking-process.html
  2. http://dubbo.apache.org/zh-cn/blog/dubbo-invoke.html

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

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

相关文章

使用Spring Boot和MongoDB构建一个React式应用程序

“我喜欢编写身份验证和授权代码。” 〜从来没有Java开发人员。 厌倦了一次又一次地建立相同的登录屏幕? 尝试使用Okta API进行托管身份验证,授权和多因素身份验证。 如果您要处理大量流数据,React式应用程序可让您更好地扩展。 它们是非阻塞…

jax-ws和jax-rs_JAX-RS和OpenAPI对Hypermedia API的支持:任重而道远

jax-ws和jax-rs或早或晚,大多数积极使用REST(ful) Web服务和API的开发人员偶然发现了这种真正的外星事物,即HATEOAS : 超文本作为应用程序状态的引擎 。 对HATEOAS是什么及其与REST的关系的好奇最终将导致发现Richards…

如何下载MySQL的JDBC驱动包

1.打开 MySQL 官网:https://www.mysql.com/ 2.点击 DOWNLOADS,把页面滚动到最下面,点击 MySQL Community (GPL) Downloads 3.点击 Connector/J 4.点击 General Availability(GA) Releases,在 Select Operating System 下拉列表选…

IntelliJ IDEA for Mac如何添加jar包/如何导入jar包/导包

文章目录如何将 jar 包添加到已存在的资源库中将 jar 包添加到项目级别的资源库中将 jar 包添加到全局级别的资源库中将 jar 包添加到模块级别的资源库中创建资源库创建模块级别的资源库创建项目级别的资源库创建全局级别的资源库给某个模块单独添加 jar 包模块的依赖标签页&am…

r2dbc_使用Spring Data R2DBC进行异步RDBMS访问

r2dbc不久之前,发布了JDBC驱动程序的React性变体。 称为R2DBC。 它允许将数据异步流传输到已预订的任何端点。 通过将R2DBC之类的React性驱动程序与Spring WebFlux结合使用,可以编写一个完整的应用程序,以异步方式处理数据的接收和发送。 在本…

python中布尔类型是特殊的_Python中的特殊方法以及应用详解

前言Python 中的特殊方法主要是为了被解释器调用的,因此应该尽量使用 len(my_object) 而不是 my_object.__len__() 这种写法。在执行 len(my_object) 时,Python 解释器会自行调用 my_object 中实现的 __len__ 方法。除非有大量的元编程存在,直…

IntelliJ IDEA 自动补全变量名称和变量类型(自动补全变量的声明内容)

文章目录varOption EnterOption Command Vvar // 如下声明定义一个字符串变量 String s new String(); // 1 先编写 new String() // 2 在 new String() 后面输入 .var 直接回车,即可得到我们想要的变量了 // 3 上面生成的变量名可能不是我们想要的&#xff0c…

jdk11 jdk12_JDK 12附带紧凑数字格式

jdk11 jdk12JDK 12 Early Access Build 24引入了对紧凑数字格式的支持 。 JDK-8188147(紧凑数字格式支持)CSR的“摘要”是简单的句子,“添加了对JDK中的紧凑/短数字格式的支持。” 同一CSR还提供了详细的“解决方案”部分,该部分提…

全连接层 时间复杂度_神经网络全连接层(3)

CNN网络基础结构神经网络-全连接层(3)上一回我们聊完了算法,这回我们正式开始写代码。上回在做公式推导的时候,我们实际上只是针对一个数据样本进行推导,而实际中,计算和训练都是一批一批完成的。大多数机器学习训练都有batch的概…

line和spline_探索适用于Apache Spark的Spline Data Tracker和可视化工具(第1部分)

line和spline最近引起我注意的一个有趣且很有希望的开源项目是Spline ,它是由Absa维护的Apache Spark数据沿袭跟踪和可视化工具。 该项目由两部分组成:一个在驱动程序上工作的Scala库,该库通过分析Spark执行计划来捕获数据沿袭,以…

MacBook如何快速显示桌面

1.触控板中张开拇指和其它三指 2.通过触发角来快速显示桌面

怎么把word里面虚线变成实线_弱电不会制作cad图,花3分钟看完,只要会用WORD保证你能画出来...

今天我要给你介绍的就是Microsoft Office Visio是Microsoft Office 套件之一。安装Visio之后,可以类比Word的操作方法一样来使用,不过,就是比在Word里画图、修改更方便,功能更强大。特别是在做技术路线图、各种图表的绘图&#xf…

MacBook如何设置分屏浏览的快捷键

MacBook的系统自身无法设置,必须安装第三方软件才能设置,例如:BetterAndBetter、Magnet、BetterSnapTool 等。 BetterAndBetter 的设置,如下图所示:

api自动化测试_API测试和自动化101:基本指南

api自动化测试API代表A pplication P AGC软件我覆盖整个院落。 通常,API用于通过使用任何通信方式来促进两个不同应用程序之间的交互。 在网络上使用API​​时,我们将其称为“ Web服务”。 近年来,API已成为编程的Struts。 与在应用程序中一样…

web.config连接mysql_web.config中配置数据库连接的方式

在网站开发中,数据库操作是经常要用到的操作,ASP.NET中一般做法是在web.config中配置数据库连接代码,然后在程序中调用数据库连接代码,这样做的好处就是当数据库连接代码需要改变的时候,我们只要修改web.config中的数据…

BetterAndBetter(BAB)的使用详解

文章目录多指轻点时防止左键点击规则管理重置全部设置和规则多指轻点时防止左键点击 在正常情况下按下触控板的左键,使用鼠标选择好文本后,松开触控板的左键,就已经退出文本选择模式了,此时移动鼠标应该是不会影响到已经选择的文…

gradle使用maven_使用Gradle – 2019版从Travis可靠发布到Maven Central

gradle使用maven得益于在2018年和2019年末实现的显式登台存储库创建功能集,使您(自动)从Travis(不仅是)发布到Maven Central更加可靠。 背景 如果您仅想获取有关如何使工件从Travis发行的信息更可靠的信息&#xff0c…

mysql 事件 day hour_Mysql事件调度器(Event Scheduler)

Mysql中的事件调度器Event Scheduler类似于linux下的crontab计划任务的功能,它是由一个特殊的时间调度线程执行的一、查看当前是否开启了event scheduler三种方法:1) SHOW VARIABLES LIKE ‘event_scheduler’;2) SELECT event_scheduler;3) SHOW PROCESSLIST;(是否有State为&a…

2020年全国儿童青少年总体近视率为52.7%,比上年上升2.5%播

2021年7月13日,国家卫健委召开新闻发布会介绍儿童青少年近视防控和暑期学生健康有关情况。国家卫健委疾控局副局长再那吾东玉山介绍,2020年上半年全民居家抗疫减少了户外活动和放松眼睛的时间,对近视防控工作带来了挑战。为全面评估近视率的情…

精简jdk包_在JDK 12精简数字格式中使用最小分数数字

精简jdk包帖子“ 紧凑数字格式出现在JDK 12中 ”演示了对JDK 12中 NumberFormat的支持,以支持紧凑数字格式 。 该帖子中显示的示例仅使用NumberFormat的实例,这些实例是通过调用NumberFormat的新重载getCompactNumberInstance(-)方法返回的,因…