写点什么

异步编程的魔力:如何显著提升系统性能

作者:刘祥
  • 2024-06-29
    陕西
  • 本文字数:1968 字

    阅读完需:约 6 分钟

异步编程的魔力:如何显著提升系统性能

今天我们来聊聊一个对开发者非常重要的话题——异步编程。异步编程是提升系统性能的一种强大手段,尤其在需要高吞吐量和低时延的场景中,异步设计能够显著减少线程等待时间,从而提升整体性能。

异步设计如何提升系统性能?

我们通过一个简单的例子来理解异步设计是如何提升系统性能的。假设我们要实现一个转账的微服务 Transfer(accountFrom, accountTo, amount),它有三个参数:转出账户、转入账户和转账金额。实现过程如下:


  1. 从账户 A 中减去 100 元。

  2. 给账户 B 加上 100 元,转账完成。


对应的时序图如下:



在这个例子中,假设我们调用了另一个微服务 Add(account, amount),它的功能是给账户增加金额,负值表示扣减金额。为了简化问题,我们忽略了错误处理和事务管理。

1. 同步实现的性能瓶颈

首先,我们来看同步实现的伪代码:


Transfer(accountFrom, accountTo, amount) {  // 先从 accountFrom 的账户中减去相应的钱数  Add(accountFrom, -1 * amount);  // 再把减去的钱数加到 accountTo 的账户中  Add(accountTo, amount);  return OK;}
复制代码


假设 Add 服务的平均响应时延是 50ms,那么 Transfer 服务的平均响应时延大约是 100ms。随着请求量增加,会出现什么情况呢?


在这种实现中,每处理一个请求需要耗时 100ms,并且在这 100ms 过程中需要独占一个线程。假设服务器线程数量上限是 10,000,那么每秒最多处理 100,000 次请求。如果请求速度超过这个值,请求就会阻塞,导致响应时延增加。


尽管服务器的 CPU、内存、网卡流量和磁盘 IO 都很空闲,但大部分线程都在等待 Add 服务返回结果。这种情况下,采用同步实现的方式,服务器资源被浪费在无意义的等待上。

2. 采用异步实现解决等待问题

接下来,我们用异步的思想来解决这个问题,实现同样的业务逻辑:


TransferAsync(accountFrom, accountTo, amount, OnComplete()) {  // 异步从 accountFrom 的账户中减去相应的钱数,然后调用 OnDebit 方法。  AddAsync(accountFrom, -1 * amount, OnDebit(accountTo, amount, OnAllDone(OnComplete())));}// 扣减账户 accountFrom 完成后调用OnDebit(accountTo, amount, OnAllDone(OnComplete())) {  //  再异步把减去的钱数加到 accountTo 的账户中,然后执行 OnAllDone 方法  AddAsync(accountTo, amount, OnAllDone(OnComplete()));}// 转入账户 accountTo 完成后调用OnAllDone(OnComplete()) {  OnComplete();}
复制代码


异步实现的时序图如下:



异步化实现后,整个流程的时序和同步实现完全一样,但线程模型改为了异步调用和回调机制。在高请求数量场景下,异步实现不再需要线程等待执行结果,只需少量线程即可实现高吞吐量。

简单实用的异步框架:CompletableFuture

在实际开发中,异步框架如 Java 8 内置的 CompletableFuture 可以简化异步编程。下面是使用 CompletableFuture 实现的转账服务:


public interface AccountService {    CompletableFuture<Void> add(int account, int amount);}
public interface TransferService { CompletableFuture<Void> transfer(int fromAccount, int toAccount, int amount);}
public class TransferServiceImpl implements TransferService { @Inject private AccountService accountService;
@Override public CompletableFuture<Void> transfer(int fromAccount, int toAccount, int amount) { return accountService.add(fromAccount, -1 * amount) .thenCompose(v -> accountService.add(toAccount, amount)); }}
复制代码


客户端使用 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("转账完成!")); }}
复制代码

小结

异步编程的核心思想是:当我们要执行一项耗时操作时,不去等待操作结束,而是给这个操作一个命令:“当操作完成后,接下来去执行什么。” 使用异步编程模型,可以减少或避免线程等待,从而提升系统的吞吐能力。


尽管异步编程能显著提升性能,但它也增加了代码的复杂度。异步模型适用于业务逻辑简单且需要高吞吐量的场景,而在复杂业务逻辑下,采用易于开发和维护的同步模型可能是更明智的选择。


希望这篇文章能帮助你更好地理解异步编程的魔力,并在合适的场景下应用它来提升系统性能。

用户头像

刘祥

关注

个人公众号|码上代码 2020-03-06 加入

码上代码 |CSDNjava领域优质创作者分享

评论

发布
暂无评论
异步编程的魔力:如何显著提升系统性能_异步_刘祥_InfoQ写作社区