消息队列面试解析系列(六)- 异步编程妙用(下)

简介: 消息队列面试解析系列(六)- 异步编程妙用

异步实现的性能

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


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


异步框架: CompletableFuture

开发时可使用异步框架和响应式框架,解决一些异步编程问题。

Java中比较常用的异步框架有Java8内置的CompletableFuture和ReactiveX的RxJava。


  • CompletableFuture简单实用易理解
  • RxJava功能更强大

Java 8中新增的异步编程类:CompletableFuture,几乎囊获了开发异步程序的大部分功能,使用CompletableFuture很容易编写优雅且易维护的异步代码。


接下来用CompletableFuture实现的转账服务。


CompletableFuture定义2个微服务的接口:

/**
 * 账户服务
 */
public interface AccountService {
    /**
     * 变更账户金额
     * @param account 账户ID
     * @param amount 增加的金额,负值为减少
     */
    CompletableFuture<Void> add(int account, int amount);
}
/**
 * 转账服务
 */
public interface TransferService {
    /**
     * 异步转账服务
     * @param fromAccount 转出账户
     * @param toAccount 转入账户
     * @param amount 转账金额,单位分
     */
    CompletableFuture<Void> transfer(int fromAccount, int toAccount, int amount);
}


接口中定义的方法的返回类型都是泛型CompletableFeture,泛型类型就是真正方法需要返回数据的类型,这俩服务无需返回数据,所以用Void。

实现转账服务:

/**
 * 转账服务的实现
 */
public class TransferServiceImpl implements TransferService {
    @Inject
    private  AccountService accountService; // 使用依赖注入获取账户服务的实例
    @Override
    public CompletableFuture<Void> transfer(int fromAccount, int toAccount, int amount) {
      // 异步调用add方法从fromAccount扣减相应金额
      return accountService.add(fromAccount, -1 * amount)
      // 然后调用add方法给toAccount增加相应金额
      .thenCompose(v -> accountService.add(toAccount, amount));    
    }
}

先定义一个AccountService实例,这个实例从外部注入进来,至于怎么注入不是我们关心的问题,就假设这个实例是可用的就好了。


看transfer()方法,先调用一次账户服务accountService.add()方法从fromAccount扣减相应金额,因add()方法返回的就是个CompletableFeture对象,可用CompletableFeture的thenCompose()方法将下一次调用accountService.add()串联起来,实现异步依次调用两次账户服务完整转账。


客户端使用CompletableFuture也灵活,既可同步调用,也可异步。

public class Client {
    @Inject
    private TransferService transferService; // 使用依赖注入获取转账服务的实例
    private final static int A = 1000;
    private final static int B = 1001;
    public void syncInvoke() throws ExecutionException, InterruptedException {
        // 同步调用
        transferService.transfer(A, B, 100).get();
        System.out.println("转账完成!");
    }
    public void asyncInvoke() {
        // 异步调用
        transferService.transfer(A, B, 100)
                .thenRun(() -> System.out.println("转账完成!"));
    }
}

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


既可调CompletableFuture#get,像调用同步方法样等待调用的方法执行结束并获得返回值

亦可如异步回调,调用CompletableFuture那些then开头方法,为CompletableFuture定义异步方法结束之后的后续操作

比如上例,调用thenRun()方法,参数就是将转账完成打印在控台上这个操作,这样就可以实现在转账完成后,在控制台打印“转账完成!”了。


总结

异步的思想就是,当要执行一项比较耗时的操作时,不去等待操作结束,而是给这个操作一个命令:“当操作完成后,接下来去执行什么。”


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


同时我们也需注意异步模型问题:相比同步,异步实现的复杂度大很多,代码可读性和可维护性都显著下降。

虽然使用一些异步编程框架会在一定程度上简化异步开发,但是并不能解决异步模型高复杂度的问题。


异步性能虽好,勿滥用,只有类似MQ这种业务逻辑简单且需超高吞吐量场景,或须长时等待资源,才考虑使用异步模型。

如果系统的业务逻辑比较复杂,在性能足够满足业务需求的情况下,采用符合人类自然的思路且易于开发和维护的同步模型是更加明智的选择。


面试场景题快问快答

实现转账服务时,未考虑处理失败情况。异步实现中,若调用账户服务失败,如何将错误报告给客户端?在两次调用账户服务的Add方法时,如果某一次调用失败了,该如何处理才能保证账户数据是平的?


  1. 调用账户失败,可以在异步callBack里执行通知客户端的逻辑
  2. 若是第一次失败,后面那步就不用执行了,所以转账失败;如果是第一次成功但是第二次失败,首先考虑重试,如果转账服务是幂等的,可以考虑一定次数的重试,如果不能重试,可以考虑采用补偿机制,undo第一次的转账操作。


在异步实现中,回调方法OnComplete()是在什么线程中运行的?我们是否能控制回调方法的执行线程数

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

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


目录
相关文章
|
7天前
|
SQL 分布式计算 监控
Sqoop数据迁移工具使用与优化技巧:面试经验与必备知识点解析
【4月更文挑战第9天】本文深入解析Sqoop的使用、优化及面试策略。内容涵盖Sqoop基础,包括安装配置、命令行操作、与Hadoop生态集成和连接器配置。讨论数据迁移优化技巧,如数据切分、压缩编码、转换过滤及性能监控。此外,还涉及面试中对Sqoop与其他ETL工具的对比、实际项目挑战及未来发展趋势的讨论。通过代码示例展示了从MySQL到HDFS的数据迁移。本文旨在帮助读者在面试中展现Sqoop技术实力。
21 2
|
7天前
|
监控 负载均衡 Cloud Native
ZooKeeper分布式协调服务详解:面试经验与必备知识点解析
【4月更文挑战第9天】本文深入剖析ZooKeeper分布式协调服务原理,涵盖核心概念如Server、Client、ZNode、ACL、Watcher,以及ZAB协议在一致性、会话管理、Leader选举中的作用。讨论ZooKeeper数据模型、操作、会话管理、集群部署与管理、性能调优和监控。同时,文章探讨了ZooKeeper在分布式锁、队列、服务注册与发现等场景的应用,并在面试方面分析了与其它服务的区别、实战挑战及解决方案。附带Java客户端实现分布式锁的代码示例,助力提升面试表现。
28 2
|
7天前
|
数据采集 消息中间件 监控
Flume数据采集系统设计与配置实战:面试经验与必备知识点解析
【4月更文挑战第9天】本文深入探讨Apache Flume的数据采集系统设计,涵盖Flume Agent、Source、Channel、Sink的核心概念及其配置实战。通过实例展示了文件日志收集、网络数据接收、命令行实时数据捕获等场景。此外,还讨论了Flume与同类工具的对比、实际项目挑战及解决方案,以及未来发展趋势。提供配置示例帮助理解Flume在数据集成、日志收集中的应用,为面试准备提供扎实的理论与实践支持。
21 1
|
2天前
|
调度 Python
Python多线程、多进程与协程面试题解析
【4月更文挑战第14天】Python并发编程涉及多线程、多进程和协程。面试中,对这些概念的理解和应用是评估候选人的重要标准。本文介绍了它们的基础知识、常见问题和应对策略。多线程在同一进程中并发执行,多进程通过进程间通信实现并发,协程则使用`asyncio`进行轻量级线程控制。面试常遇到的问题包括并发并行混淆、GIL影响多线程性能、进程间通信不当和协程异步IO理解不清。要掌握并发模型,需明确其适用场景,理解GIL、进程间通信和协程调度机制。
17 0
|
2天前
|
API Python
Python模块化编程:面试题深度解析
【4月更文挑战第14天】了解Python模块化编程对于构建大型项目至关重要,它涉及代码组织、复用和维护。本文深入探讨了模块、包、导入机制、命名空间和作用域等基础概念,并列举了面试中常见的模块导入混乱、不适当星号导入等问题,强调了避免循环依赖、合理使用`__init__.py`以及理解模块作用域的重要性。掌握这些知识将有助于在面试中自信应对模块化编程的相关挑战。
17 0
|
8天前
|
分布式计算 资源调度 监控
Hadoop生态系统深度剖析:面试经验与必备知识点解析
本文深入探讨了Hadoop生态系统的面试重点,涵盖Hadoop架构、HDFS、YARN和MapReduce。了解Hadoop的主从架构、HDFS的读写流程及高级特性,YARN的资源管理与调度,以及MapReduce编程模型。通过代码示例,如HDFS文件操作和WordCount程序,帮助读者巩固理解。此外,文章强调在面试中应结合个人经验、行业动态和技术进展展示技术实力。
|
13天前
|
自然语言处理
大型语言模型(LLMs)面试常见问题解析
大型语言模型(LLMs)面试常见问题解析
27 4

推荐镜像

更多