java异步编程

Q:为何要采用异步编程

A:异步编程首先不会节约线程,因为异步操作都会重新开一个线程。异步编程是提高了CPU的使用率,采用同步编程的方式,整个服务器的所有线程大部分都没有在工作,而是在等待。因为线程同步操作

要等整个事件处理完成才能提交,所以CPU的利用率很低;当采用异步编程,线程不需要等待,减少时间分片的占用,提高系统的吞吐量。在超高请求数量场景下,异步的实现不再需要线程等待执行结果,只需要个位数量的线程,即可实现同步场景大量线程一样的吞吐量。

 

举例:

   实现一个转账的微服务 Transfer( accountFrom, accountTo, amount),这个服务有三个参数:

分别是转出账户、转入账户和转账金额。

  实现过程也比较简单,我们要从账户 A 中转账 100 元到账户 B 中:先从 A 的账户中减去 100 元;再给 B 的账户加上 100 元,转账完成。

  采用同步操作:

  


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

 

  同步操作我们很容易想到的,那同步操作的效率怎么样呢? 

  假设内部add方法执行效率在50ms,那一个线程执行烧水泡茶的时延在100ms,按照这种实现,每个线程每分钟最多处理10个请求,假设我们使用的服务器同时打开的线程数量上限是 10,000,
可以计算出这台服务器每秒钟可以处理的请求上限是: 10,000 (个线程)* 10(次请求每秒) = 100,000 次每秒。如果请求速度超过这个值,那么请求就不能被马上处理,只能阻塞或者排队,
这时候 Transfer 服务的响应时延由 100ms 延长到了:排队的等待时延 + 处理时延 (100ms)。也就是说,在大量请求的情况下,我们的微服务的平均响应时延变长了。 这已经到CPU的极限了吗?
其实不然,如果我们监测一下服务器的各项指标,会发现无论是 CPU、内存,还是网卡流量或者是磁盘的 IO 都空闲的很,多大部分线程都在等待每一个服务返回结果。

  
  

  采用异步操作:

   是在线程模型上由同步顺序调用改为了异步调用和回调的机制。

  接下来,我们来看下,如何用 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);
}

/**
 * 转账服务的实现
 */
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.add() 方法从 fromAccount 扣减响应的金额,因为 add() 方法返回的就是一个 CompletableFeture 对象,可以用 CompletableFeture 的 thenCompose() 方法将下一次调用 accountService.add() 串联起来,实现异步依次调用两次账户服务完整转账。

posted @ 2020-02-09 17:51  以梦为码  阅读(944)  评论(0编辑  收藏  举报