关于 SpringBoot 中的并发请求外部接口的需求解惑

98 天前
Koril  Koril

前言

老哥们,现在碰到一个需求,希望大家帮忙看看,有什么方案。 我是 Java 菜鸡,可能提到的某些点很傻很无知,望见谅。


需求

后台有这样一个接口 /demo ,前端请求到 /demo 后,代码需要按照顺序访问多个外部 HTTP 接口

比如外部接口有三个:

  1. /api-a
  2. /api-b
  3. /api-c

前端请求 /demo 后,后端直接返回 response 200 "ok" 就行,不用阻塞。

每个外部接口的一些传参都依赖于前一个请求的返回值(/api-c 的传参依赖于/api-b ),所以顺序是一定的,只能一个个请求。

最终结果(最后一个接口 /api-c 响应后),返回结果存入数据库。

另外,中间可能会出现 timeout 或者其他异常,这些信息也需要存入数据库。

问题在于,请求的并发量略微大了些,大概一秒钟有上百个请求进来(可以简化成每一秒就有 120 个请求进入该接口)。


机器环境、语言、框架、数据库

机器:单个虚拟机,CPU 和内存都可以按需求往上调大,目前是 18 核 48GB 的配置。

语言:Java 21 (抱歉,其他语言不会,只能用这个)

框架:SpringBoot3

数据库:PG 、Redis 、Mongo (随意使用)


我自己的方案

我的方法很直接,把请求的外部方法的代码放在一个 service 函数里,然后加 @Async 注解。

然后配置 ThreadPoolTaskExecutor (就是网上都能搜到的那些配置)。

另外,为了追踪每一个任务线程的结果,在线程里,一开始就生成一个 UUID ,然后构造一个对象,每一步都把相应的信息(成功或者失败)存入这个对象,最后以这个 UUID 为主键存储到数据库里。


有更好的解决方案么

按照我自己的观察,如果线程数量给小了,就容易产生队列堆积,给大了,又不确定该给多大,难道只能测试?

我的理解大概是 100 个请求进来,假设外部 3 个接口,每个需要 5 秒,那么全部请求完就是 15 秒(忽略其他时延),100 * 15 = 1500 个线程,如果小于这个值,就会堆积在队列中。

我想知道是否能根据以下的变量,通过某种方法推算出这个接口的理论的上限?

  1. 机器配置( CPU 个数,内存大小,上下行带宽等)
  2. 请求外部接口的个数,平均每个外部接口的响应时间
  3. 其他参数

怎么计算,并且达到这个上限?有什么更好的方法么?

4283 次点击
所在节点   程序员  程序员
56 条回复
Koril
Koril
98 天前
使用 MQ 会不会堆积呢,需求是前端对 /demo 发起请求后,后端逻辑(就是顺序请求外部接口的方法)必须要立即执行,引入中间件会不会增加中间的时延。
yangyaofei
yangyaofei
98 天前
如果不想把程序写很大(MQ + 消费者之类的), 直接请求进来存数据库, 对应某个字段为任务是否执行, 然后 scheduler 去找没有执行的数据, 执行并把结果添上, scheduler 后面想加多少线程就随意了.

其实就是相当于用数据库直接当 mq 用, 前台请求只是插入挺简单的速度回很快
wu00
wu00
98 天前
这还不上队列么
每秒都进来 120 了,上游就是 360 ,就算上游不限流,响应要是慢一点不得干爆你
diantongren
diantongren
98 天前
java21 ,springboot3.2 使用虚拟线程
xrzxrzxrz
xrzxrzxrz
98 天前
@Koril 用 MQ 主要是为了解耦,可以更灵活。如果写在一起,如果请求外部接口出问题了,可能拖垮对前端接口的响应。所以单独拎出来消费者。担心中间延迟,就多些消费者,保证消息不堆积,基本就不会太多的延迟。(因为你异步请求外部接口,本身就已经是默认有延迟了,不是同步操作)
pangdundun996
pangdundun996
98 天前
基本就大家说的 MQ 或者虚拟线程,瓶颈其实在外部接口,不在机器配置上
pangdundun996
pangdundun996
98 天前
@Koril 100 的 qps ,别说 MQ 了,你就是直接落库也没事啊
brianinzz
brianinzz
98 天前
还有就是一点你要搞清楚挤压不完全时因为你的 线程设置
还要看你调用接口的 策略 好比你现在是 5 秒每个*3 就算你无限资源往上加 人家接口扛不住了变 10 秒每个或者全挂了也不是你想要的结果
mq 更多是解耦 有了 mq 你也要确认消费的并发来调整 mq 消费者的数量或者并发数
wupher
98 天前
- 有段时间会使用 spring flow ,每个请求是个 Mono 或者 Flow ,多个请求将这些流 zip / combine 在一起,再根据业务要求返回不同接口。但无论是响应式还是 ReactiveX ,都有一些自己的问题。

- 现在更多会用 Ktor

- 如果你的这种请求特别多,且大,我觉得纯异步队列方案可能是最好的
notejava
98 天前
请求进来,丢给线程池去异步处理,如果任务数超出了线程池的队列长度,就暂存数据库,再起一个定时任务,定时消费数据库中的任务。
git00ll
98 天前
前面用个 mq 接受数据,
消费数据可以看下 Nio 相关工具,比如说 spring 的 webclient 。把自己当成网关压力给到后方 abc 服务
ala2008
98 天前
这么高的配置不至于顶不住啊,多几个实例
gerefoxing
98 天前
mq 异步处理+定时任务扫描处理失败的
z1829909
98 天前
@Koril 只要是 mq 不管什么形式的都可能会堆积,具体是看你的业务,能不能忍受堆积,堆积了一段时间的任务是继续处理还是扔掉。
Karte
98 天前
请求量这么大肯定需要 MQ 进行存放, 至于后续你可以使用虚拟线程, 也可以试试 CompletableFuture 的函数编程方式.
Koril
98 天前
非常非常感谢大家的回复,我正在开始了解 Java 21 的虚拟线程,希望能用上
Koril
98 天前
@ala2008 之前碰到了 OOM 的问题:java.lang.OutOfMemoryError: unable to create native thread: possibly out of memory or process/resource limits reached

因为我看程序根本没吃满内存,所以我改了 Xms Xmx Xss ,似乎都没啥用,然后我就在一个虚拟机开了俩实例,nginx 负载均衡,结果就再也没报这个错误了。
ala2008
98 天前
@Koril 你是用 tomcat 吗?可以搜索一下 tomcat 配置优化
litchinn
98 天前
Spring webclient 响应式请求
Jdk21 的话虚拟线程也是不错的,这是发挥虚拟线程能力的典型场景
感觉这个 100qps 应该用不上 mq
asmoker
98 天前
我只关心顺序调用的中间有失败的,前面成功的接口怎么回滚?🤨
能队列的还是队列,万一重启异步进程的请求信息不是丢了都?🤨

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1090698

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX