前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >消息队列面试解析系列之异步编程模式

消息队列面试解析系列之异步编程模式

作者头像
JavaEdge
发布2022-11-30 15:29:24
6260
发布2022-11-30 15:29:24
举报
文章被收录于专栏:JavaEdge

0 前言

线程就是为了能自动分配CPU时间片而生。异步模式设计可显著减少线程等待,在高吞吐量场景中,极大提升系统整体性能,降低时延。因此,像MQ这种需要超高吞吐量和超低时延中间件系统,其核心流程大量采用异步。

异步的本质是为了不占用过多的线程对象。比如一个响应时间是1秒的http1.1请求,并且不考虑http pipeline:

  • 同步模式下,一个请求在未返回前,需要独占一个线程和一个httpconnection
  • 异步模式下,一个请求在未返回前,只需要独占一个httpconnection,那个线程在提交完io任务后就回到线程池了

即QPS 5000时,同步需5000个connection和5000个线程,而异步可以省下5000个线程的内存以及操作系统对这些线程的管理能耗。

1 案例

某转账微服务Transfer有如下参数

  • 转出账户
  • 转入账户
  • 转账金额

调用另外一个微服务Add(account, amount),给账户account增加金额amount,当amount为负值时,就是扣减相应金额。现在要从账户A转账100到账户B:

  1. 先从A的账户减100元
  2. 再给B的账户加100元
  3. 转账完成

2 同步性能瓶颈

、

假设Add平均响应时延60ms,Transfer平均响应时延就是120ms。Transfer每处理一个请求耗时120ms,这过程要独占1个线程。每个线程每s最多可处理约10个请求。假设服务器同时打开线程数量上限为10,000,可计算出这台服务器每s可处理请求上限: 10,000 (个线程)* 10(次请求每秒) = 100,000 次每秒。

若请求速度超过该值,请求就不能被马上处理,只能阻塞或排队,这时Transfer服务响应时延由120ms延长到:排队等待时延 + 处理时延(120ms)。即大量请求时,微服务平均响应时延变长!这就到了服务器极限吗?远没有!若监测服务器指标,会发现无论CPU、内存or网卡流量、磁盘I/O都闲的很,那Transfer服务那10,000个线程在作甚?绝大部分线程都在等待Add服务返回结果!所以采用同步,整个服务器的所有线程大部分时间都没在工作,而是在等待!若能减少或避免这种无意义等待,就能大幅提升服务吞吐量,提升性能。

3 异步方案

TransferAsync只是比Transfer多个参数,一个回调方法OnComplete(Java可传个回调类的实例来实现): 请帮我执行转账,当转账完成后,请调用OnComplete。调用TransferAsync的线程无需等待转账完成,即可立即返回。待转账结束,TransferService自然会调用OnComplete()方法执行转账后续工作。

异步实现相比同步实现,先要定义如下回调方法:

  • OnDebit():扣减账户accountFrom完成后调用
  • OnAllDone():转入to账户完成后调用

异步实现的语义:

  1. 异步从from账户扣减钱数,然后调用OnDebit
  2. 在OnDebit中,异步将减去的钱数加到to账户,然后执行OnAllDone
  3. 在OnAllDone中调用OnComplete

异步的时序流程和同步实现完全一样,只是线程模型由同步调用改为异步和回调。

性能分析

时序和同步实现一样,在少量请求场景下,平均响应时延一样是120ms。在高请求数量场景下,异步不再需线程等待执行结果,只需个位数量的线程,即可实现同步场景需要大量线程同样的吞吐量。

由于无线程数量限制,总体吞吐上限>>同步实现,且在服务器CPU、网络带宽资源达到极限前,响应时延不会随请求数量增加而显著升高,几乎可一直保持约120ms平均响应时延。

4 异步框架: CompletableFuture

Java开发常用异步框架:

  • Java8内置的CompletableFuture CompletableFuture简单实用易理解
  • ReactiveX的RxJava 功能更强大

Java 8中新增的CompletableFuture几乎涵盖异步程序所需的大部分功能,易写出优雅且易维护的异步代码。

接下来用CompletableFuture改造转账服务。

微服务接口:

转账服务:

客户端使用CompletableFuture既可同步调用,也可异步:

调用异步方法获得返回的CompletableFuture对象后:

  • 既可调用CompletableFuture#get,像调用同步方法样等待调用的方法执行结束并获得返回值
  • 也能像异步回调,调用CompletableFuture#thenXXX,为CompletableFuture定义异步方法结束之后的后续操作 比如上例,调用thenRun()方法,参数就是将转账完成打印在控台上这个操作,这样就可以实现在转账完成后,在控制台打印“转账完成!”了。

FAQ

异步实现中,若调用账户服务失败,如何将错误报告给客户端?在两次调用账户服务的Add方法时,若某一次调用失败了,该如何处理才能保证账户数据是平的?
  • 调用账户失败,可以在异步callBack里执行通知客户端的逻辑
  • 若是第一次失败,后面那步就不用执行了,所以转账失败;若第一次成功但是第二次失败,首先考虑重试,若转账服务幂等,可考虑一定次数的重试,若不能重试,考虑采用补偿,回滚第一次的转账操作。
异步实现中,回调方法OnComplete()在什么线程运行的?是否能控制回调方法的执行线程数?

CompletableFuture默认在ForkjoinPool commonpool里执行,也可指定一个Executor线程池执行,借鉴guava的ListenableFuture的时间,回调可以指定线程池执行,这样就能控制这个线程池的线程数。

异步实现中,回调方法 OnComplete()在执行OnAllDone()回调方法的那个线程,可通过一个异步线程池控制回调方法的线程数,如Spring中的async就是通过结合线程池来实现异步。

CompletableFuture回调底层还是forkjoin框架,forkjoin对I/O这种操作会阻塞线程且CompletableFuture默认线程数=cpu核数。在容器化场景下,CPU核数都不会很多,那使用CompletableFuture时,执行I/O操作会不会更早得无响应?因为个位数的线程很快就都被阻塞完了。

CompletableFuture不完全同于ForkJoin,可简单理解为:

  • CompletableFuture.then() 等于 Fork
  • CompletableFuture.get() 等于 Join

但并非所有场景下,CompletableFuture都要用get()结束,有时无需调用阻塞的get()方法。而且CompletableFuture默认使用 ForkJoinPool,但也支持给它提供一个自定义执行器。

异步是可以解决请求超时的问题,但是像文中举例这种转账操作,转出转入两个操作是前后依赖的没法并行,那么这种前后依赖的任务使用异步跟同步又有什么区别呢?另外,当10万请求过来之后,虽然用了异步可以瞬间返回,但是其实几万个请求对象在CompletableFuture内部线程池内部还是排队啊,所以最后来的请求还是要等很久才能被执行到。那么既然同步or异步都需要排队,异步究竟快在哪里了呢?

第一个问题,转入转出这两个操作不需要串行,是可以并行的。甚至执行顺序都没什么要求。我们唯一要保证的是这两个操作在一个事务中执行, “要么都成功,要么都失败”,就可以了。

你这个场景是在调用方(转账服务)异步,而服务提供方(账户服务)还是同步服务的情况下,才会出现。

你仔细看一下我们的异步设计,服务提供方提供的也是异步服务,那调用账户服务也是一瞬间就完成了,这样就不会出现你说的“几万个请求对象在CompletableFuture内部线程池内部还是排队”的情况了。

5 总结

异步思想就是,当要执行很耗时的操作时,不去等待操作结束,而是给该操作一个命令:“当ooo操作完成后,然后执行xxx”

使用异步编程,本身并不能加快程序本身的速度,但能减少或避免线程等待,只用很少线程就得到高吞吐。

异步性能虽好,切勿滥用,只有类似MQ这种业务逻辑简单且需超高吞吐量场景,或须长时等待资源,才考虑使用异步模型。 若业务逻辑复杂,在性能足够满足业务需求情况下,采用易于开发维护的同步模型更适合。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-01-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 0 前言
  • 1 案例
  • 2 同步性能瓶颈
  • 3 异步方案
    • 性能分析
    • 4 异步框架: CompletableFuture
      • FAQ
      • 5 总结
      相关产品与服务
      消息队列 TDMQ
      消息队列 TDMQ (Tencent Distributed Message Queue)是腾讯基于 Apache Pulsar 自研的一个云原生消息中间件系列,其中包含兼容Pulsar、RabbitMQ、RocketMQ 等协议的消息队列子产品,得益于其底层计算与存储分离的架构,TDMQ 具备良好的弹性伸缩以及故障恢复能力。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档