1
cheng6563 2022-04-21 10:19:39 +08:00
创建线程池可以限制线程数量:new ThreadPoolExecutor(0, 60, 3, TimeUnit.MINUTES, new LinkedBlockingQueue<>(), new ThreadPoolExecutor.CallerRunsPolicy());
新建线程运行任务,用一个 CountDownLatch 进行任务计数,他可以阻塞直到 30 个任务完成。 没事别用 Future ,烦人的很 |
2
hay313955795 2022-04-21 10:20:21 +08:00
不想异步拿数据,那么 java8 的并行流应该可以满足吧
|
3
Dogod37 OP @cheng6563 没说清楚,Controller 层接到请求后,去调用方法并行执行这些请求,阻塞到任务全部完成后,想要这些任务又返回值,Controller 拿着这些返回值处理并返回给页面,您说的这种方式应该没有返回值?
|
4
Dogod37 OP @hay313955795 30 个 I/O 耗时操作的,并行流应该不太行....
|
5
liuhan907 2022-04-21 10:53:49 +08:00 via Android
既然你不想要异步,那并行流和线程池没有区别呀
|
6
dqzcwxb 2022-04-21 10:55:32 +08:00
并行?还要处理结果?那必然是 Completablefuture 啦
|
7
agzou 2022-04-21 11:00:23 +08:00
线程池+Future+CountDownLatch
|
9
gesse 2022-04-21 11:14:17 +08:00
设计得不好, 用户一刷新就全部 GG 了
|
10
v2orz 2022-04-21 11:14:32 +08:00
既然你有“拆分成最多 30 份”的需求,那这么做看起来也没啥问题
|
11
slomo 2022-04-21 11:21:36 +08:00
@Dogod37 如果每次请求这个接口, 你都开 30 个, 算不合理操作;
如果你把 30 个线程的线程池作为一个 bean 注入, 每次调用这个接口, 都用这个线程池来跑, 就不算. 一般网络 IO 的阻塞系数大概是 0.8~0.9, 也就是说线程处理一个网络请求, 其中等待 remote 返回的时间大概占 80%到 90%, 这时候推荐创建的线程池线程数量是 CPU 核数 /(1 - 阻塞系数). 当然这只是理论上的, 还是得多次操作看具体. 可以用 CompletableFuture 做, 最后用 CompletableFuture.allOf 来阻塞等待完成 |
12
wolfie 2022-04-21 11:26:43 +08:00
平均 5s ,高峰 20s ,最多 30 次请求,期望 30s 内,不用一次性使用 30 个线程。
防止多用户并行请求的话,固定几个令牌,同时超了直接拒绝请求。 用户请求多,第三方数据量不大,能接收一定延时性的话,可以考虑定时拉取。 |
13
adoal 2022-04-21 11:37:13 +08:00
重新设计交互逻辑,用户提交后之后扔到独立的沃克调度器里去做,页面上直接返回,告诉用户去执行了,要到它自己的任务面板里刷新看结果,调度器里看到任务执行完后更新 web 这边的任务状态表。
|
14
cheng6563 2022-04-21 11:40:31 +08:00
@Dogod37 你拿个 CopyOnWriteArrayList 之类的东西存着线程的返回值就行了,CountDownLatch 可以保证你全部线程执行完毕后再继续运行。
|
15
Joker123456789 2022-04-21 11:41:35 +08:00
为什么你觉得异步 会增加复杂性? 这个场景就是适合异步啊, 你用同步 就必然需要多线程,而且线程如果太多 不见得会增加性能。 并且线程也不会全部同步执行啊,要看 CPU 核心数的, 还有上下文切换的负担。 最重要的是,你再怎么优化 也优化不到 5 秒以下的。
最简单的方法就是,提交归提交,响应归响应。 提交后,在表里插一条提交记录,然后直接给页面一个响应,后端异步处理, 单独做一个页面,用来展示 这些提交记录。 后端异步处理完成后,修改对应的记录状态就好了。 如果处理失败了,也可以把异常信息 写入表里(每条 提交记录,都带一个异常信息字段)。 还可以在页面上做一个重试按钮。 |
16
Joker123456789 2022-04-21 11:44:15 +08:00
还有,gesse 说的,等待响应期间 如果用户刷新一下就 GG 了
|
17
Tom7 2022-04-21 11:50:11 +08:00
不清楚具体业务,给一个体验思路,后端全异步,前端提交后可以根据提交 id 之类的循环查询结果,如果异常,通过状态跳过,避免了长时间等待,又可以直观的看到每个任务结果
|
18
golangLover 2022-04-21 12:35:25 +08:00 via Android
可以,我自己就是经常用 completablefuture 做的
|
19
byte10 2022-04-21 12:41:03 +08:00
你可以看看我的答案,完美符合你的需求。https://www.bilibili.com/video/BV1FS4y1o7QB , 用 tomcat 的 NIO ,异步 servlet api ,完全没压力,你个机器单机 1000/s 吞吐量都可以。客户端找一个响应式的,或 vert.x 生态的也有,我代码给出了例子。不要先判断对错,要先看。看完,就会明白。当然 CompletableFuture 。allOf 也得用上,不然不好控制多个任务的同步问题
|
20
byte10 2022-04-21 13:07:25 +08:00
可执行代码:
``` @PostMapping("/test") public void postUrl(HttpServletRequest req) { final AsyncContext ctx = req.startAsync(); List<String> respList = new ArrayList<>(); int taskNum = 10; CountDownLatch countDownLatch = new CountDownLatch(taskNum); for (int i = 0; i < taskNum; i++) { VertxHttpClientUtil httpClientUtil = new VertxHttpClientUtil(); httpClientUtil.post("https://baidu.com", (resp) -> { respList.add(resp); countDownLatch.countDown(); }); } CompletableFuture.supplyAsync(() -> { try { countDownLatch.await(); System.out.println("finish.."); ctx.getResponse().getWriter().print(respList.get(0)); ctx.complete(); } catch (Exception e) { e.printStackTrace(); } return null; }); } ``` CompletableFuture ,请自行设置 50-200 个线程,瓶颈在于 CompletableFuture 的线程数和你第三接口的吞吐量了。 |
21
biubiuF 2022-04-21 13:33:28 +08:00
加个 kafka 异步吧,然后用户请求的时候加个 actionId ,这个 id 1 分钟刷新一次,不然有的用户转圈等待的时候会一直刷新(比如我),可能直接打满 fd
|
23
git00ll 2022-04-21 23:43:13 +08:00
nio ,你想开多少都可以,还不用担心线程太多。不过你调用的三方服务也是够垃的。
|
24
liian2019 2022-05-09 17:24:58 +08:00
量不大,业务不是太重要,用线程池去并发调用还行。量大的话这种场景最好还是别用多线程,技术上很容易实现搞些 CompletableFuture 什么的,对 C 的同步接口使用线程池要考虑的东西太多,如线程池参数怎么配置,下游接口能不能扛得住你这并发调用,线上跑起来线程池满了咋办,现在可能 150S ,请求堵住了,很可能就不止 150S 了,甚至整个系统都会受影响。其实还是提交和结果查询分两个接口来的比较好。
|