CompositeFuture 是一种特殊的 Future,它可以包装一个 Future 列表,从而让一组异步操作并行执行;然后协调这一组操作的结果,作为 CompositeFuture 的结果。本文将介绍 CompositeFuture 的用法以及几种协调方式,掌握这些内容有助于解决多个 Future 的整合问题。

1. 概念

开发人员从两个方面关注一个 Future:状态和结果。CompositeFuture 是 Future,同样要从这两个方面关注它。

CompositeFuture 的结果(消息)类型是 CompositeFuture 本身,也就是说 CompositeFuture#result() 返回值类型固定是 CompositeFuture。它可以用来表示一组结果,可以通过 list() causes() 一次性读取一组结果或异常,也可以通过resultAt(index)cause(index)读取单个结果或异常。

CompositeFuture cf = CompositeFuture.all(future1, future2);
cf.onSuccess(res -> {
CompositeFuture result = cf.result(); // 它的结果也是 CompositeFuture 类型
List<Double> results = result.list(); // 一次性读取一组结果
List<Throwable> cause = result.causes(); // 一次性读取一组异常
Double r1 = result.resultAt(0); // 读取第 0 个 Future 的结果
Throwbale t1 = result.cause(0); // 读取第 0 个 Future 的异常
});

CompositeFuture 的状态随着列表中 Future 状态的变化而变化。例如:当列表中所有 Future 都成功完成时,CompositeFuture 才是成功完成;或者,当列表中只要有一个 Future 成功完成时,CompositeFuture 就是成功完成。

CompositeFuture 作为 Future 协调器,提供了 all, any, join 三种协调方式。所谓协调,就是把一组 Future 包装成一个新的 Future,即 CompositeFuture,它的状态随着所包装列表中 Future 状态的变化而变化。

下面通过几种场景示例详细介绍这几种协调方式。

2. all

假如有一个金融系统,它有一个功能是统计某个客户的银行存款总额,需要向各个不同的银行系统发送余额查询请求,然后把这些请求的返回结果相加作为最终结果。一种高效的方式是并行发送请求,然后在所有请求都有结果时将结果汇总。使用 CompositeFuture 的 all 操作可以很方便地达到这个目的。

Future<Double> f1 = queryBalance("bank_a", "Robothy");
Future<Double> f2 = queryBalance("bank_b", "Robothy");
CompositeFuture future = CompositeFuture.all(f1, f2);
future.map(results -> { // results 是一个 CompositeFuture
Double balance1 = results.resultAt(0);
Double balance2 = results.resultAt(1);
return balance1 + balance2;
}).onSuccess(totalBalance -> System.out.println("Total balance is " + totalBalance))
.onFailure(Throwable::printStackTrace);

其中 queryBalance 的方法签名:

Future<Double> queryBalance(String bank, String username);

queryBalance 返回的是 Future,CompositeFuture#all() 把两个 Future 包装了起来,返回一个 CompositeFuture,当两个 Future 都成功完成时,这个 CompositeFuture 才算成功完成;随后转换操作 map 所设置的同步函数得以执行,通过 resultAt 方法读取结果,并将结果相加,作为总余额返回。此外,两个 queryBalance 并行执行。

上面这个例子只从 2 个银行获取余额,实际上,每个客户开会行的数量是不定的,一个客户通常对应着一个银行列表。对于这种情况,可以将多个 Future 放到一个 List 当中,再通过 CompositeFuture#all(List<Future> futures)方法对一个 Future 列表进行包装。

List<String> banks = Arrays.asList("bank1", "bank2", "bank3", ...);
List<Future> futureList = banks.stream().map(bank -> queryBalance(bank, "Robothy"))
.collect(Collectors.toList());
CompositeFuture.all(futureList)
.map(results -> {
Double totalBalance = 0.0;
List<Double> balanceList = results.list();
for (Double balance : balanceList) {
totalBalance += balance;
}
return totalBalance;
})
.onSuccess(totalBalance -> System.out.println("Total balance is " + totalBalance))
.onFailure(Throwable::printStackTrace);

上面代码中,客户 "Robothy" 对应着一个银行列表,通过流操作把银行列表转化为 List<Future> futureList。随后通过 CompositeFuture#all 对这个 Future 列表进行包装,当列表中的 Future 全部成功完成时,all 返回的 CompositeFuture 才算成功完成。在读取结果的时候,这里使用了 list()方法一次性读取所有 Future 的结果。

CompositeFuture 不仅能够协调结果类型相同的多个 Future,还可以协调结果类型不同的 Future。例如:下面例子以并行的方式获取用户信息和账户信息。

Future<User> f1 = getUserByName("Robothy");
Future<Account> f2 = getAccountByName("Robothy");
CompositeFuture.all(f1, f2)
.onSuccess(results -> {
User user = resuts.resultAt(0);
Account account = results.resultAt(1);
})
.onFailure(Throwable::printStackTrace);

通过 CompositeFuture#all 包装得到 Future 列表都已成功完成时,all() 返回的 CompositeFuture 才算完成;当其中一个 Future 失败时,CompositeFuture 立即失败。

3. join

join 是另一种协调方式,当它包装的所有 Future 都已成功完成时,join() 返回的 CompositeFuture 才算成功完成。当其中一个 Future 失败,CompositeFuture 在等待所有 Future 都(成功或失败)完成之后失败。

下面这个例子的目的是找到网络延迟最小的服务器。

List<String> hosts = Arrays.asList("10.0.1.123", "10.0.1.124", "10.0.1.125", ...);
// 异步函数 ping 将 host 映射为 Future<Long>
List<Future> futures = hosts.stream().map(host -> ping(host)).collect(Collectors.toList());
CompositeFuture cf = CompositeFuture.join(futures);
cf.onComplete(res -> { // 这里是 onComplete,无论 cf 成功还是失败,都将执行。
CompositeFuture delays = res.result();
Long minDelay = Long.MAX_VALUE;
int minDelayIdx = -1;
for (int i=0; i<delays.size(); i++) {
Long delay = delays.resultAt(i);
if (null != delay) { // 第 i 个 Future 成功完成(如果失败, delay 为 null)
if (delay < minDelay) {
minDelay = delay;
minDelayIdx = i;
}
}
} if (minDelayIdx != -1) {
System.out.println("Min delay: " + minDelay + ", server: " + hosts.get(minDelayIdx));
} else {
System.out.println("All servers are unreachable.");
}
})
.onFailure(Throwable::printStackTrace);

join 和 all 的区别在于:当包装的 Future 列表中有 1 个失败时,all() 得到的 CompositeFuture 立即失败,而join()所包装的 CompositeFuture 会等待列表中的所有 Future 都完成时才失败。

此外,CompositeFuture 包装了多个 Future,意味着可能会有多个失败的 Future,而 Future#onFailure 只能够处理一个异常对象。这个异常对象是失败 Future 中,索引号最小的 Throwable 对象,并非最先失败的 Future 对应的 Throwable。例如下面这个例子总是输出 "f1 error"。

CompositeFuture.join(
Future.future(promise -> promise.fail(new RuntimeException("f1 error"))),
Future.future(promise -> promise.fail(new RuntimeException("f2 error")))
)
.onFailure(cause -> System.err.println(cause.getMessage())); // 总是输出 f1 error
}

要处理每个异常对象,需要在 onComplete 设置的处理器中进行操作。

CompositeFuture.join(f1, f2, ...)
.onComplete(res -> {
CompositeFuture results = res.result();
for (int i=0; i<results.size(); i++) {
Throwbale cause = results.cause(i);
if (null != cause) { // 第 i 个 Future 失败了
cause.printStackTrace();
}
}
});

4. any

all 和 join 都需要在列表中所有的 Future 都成功的情况下,CompositeFuture 才算成功,而 any 只要有一个 Future 成功了,CompositeFuture 就会立即成功完成;当所有 Future 都失败了,CompositeFuture 才是失败。

下面这个例子表示客户端向一个分布式系统的多个副本节点发送相同的消息,只要有一个节点返回成功,则表示消息发送成功。

String msg = "Hello";
List<String> hosts = Arrays.asList("10.0.0.1", "10.0.0.2", ...);
List<Future> futures = hosts.stream().map(host -> send(host, msg)).collect(Collectors.toList());
CompositeFuture.any(futures)
.onSuccess(results -> {
for (int i=0; i<results.size(); i++) {
if (null != results.resultAt(i)) {
System.out.println(hosts.get(i) + " received message.");
}
}
})
.onFailure(Throwable::printStackTrace);

5. 小结

CompositeFuture 作为 Future 的子接口,和普通 Future 一样可以处理消息和转化为另一个 Future 的能力。特殊的是,它的消息类型也是 CompositeFuture,它的状态随着所包装的若干 Future 状态的变化而变化。

CompositFuture 包装 Future 的方式或者说协调方式有三种:all, join, any。需要根据不同的应用场景来选择不同的包装方式。

  • all: 所有的 Future 都成功完成时,才算成功完成;只要有一个 Future 失败,则立即失败;
  • join: 所有的 Future 都成功完成时,才算成功完成;有 Future 失败时,等待所有的 Future 都完成才完成;
  • any: 只要有一个 Future 成功完成,则立即成功完成;所有 Future 都失败时,才算失败

Vert.X CompositeFuture 用法的更多相关文章

  1. Vert.x Core 文档手册

    Vert.x Core 文档手册 中英对照表 Client:客户端 Server:服务器 Primitive:基本(描述类型) Writing:编写(有些地方译为开发) Fluent:流式的 Reac ...

  2. matlab中patch函数的用法

    http://blog.sina.com.cn/s/blog_707b64550100z1nz.html matlab中patch函数的用法——emily (2011-11-18 17:20:33) ...

  3. sass基本用法(转载)

    SASS入门教程及用法指南 2014年8月27日 8489次浏览 作为前端开发人员,你肯定对css很熟悉,但是你知道css可以自定义吗?大家都知道,js中可以自定义变量,css仅仅是一个标记语言,不是 ...

  4. SASS用法指南-转

    作者: 阮一峰  日期: 2012年6月19日  原文地址:http://www.ruanyifeng.com/blog/2012/06/sass.html 艹,没想到sass 2012年就有了.现在 ...

  5. CSS预处理器之SASS用法指南

    CSS预处理器之SASS用法指南 一.什么是SASS Sass是是一种基于ruby编写的CSS预处理器,提供了许多便利的写法,大大节省了设计者的时间,使得CSS的开发,变得简单和可维护. 诞生于200 ...

  6. sass基本用法

        什么是SASS SASS是一种CSS的开发工具,提供了许多便利的写法,大大节省了设计者的时间,使得CSS的开发,变得简单和可维护. 本文总结了SASS的主要用法.我的目标是,有了这篇文章,日常 ...

  7. scss初学小结(转阮一峰老师SASS用法指南http://www.ruanyifeng.com/blog/2012/06/sass.html)

    1.安装 SASS是Ruby语言写的,但是两者的语法没有关系.不懂Ruby,照样使用.只是必须先安装Ruby,然后再安装SASS. 假定你已经安装好了Ruby,接着在命令行输入下面的命令: gem i ...

  8. [转]SASS用法指南

    [转]SASS用法指南 转自阮一峰 SASS用法指南 一.什么是SASS SASS是一种CSS的开发工具,提供了许多便利的写法,大大节省了设计者的时间,使得CSS的开发,变得简单和可维护. 本文总结了 ...

  9. Scss基础用法

    Scss基础用法 一.注释用法: (1)//comment:该注释只是在.scss源文件中有,编译后的css文件中没有. (2)/! /:重要注释,任何style的css文件中都会有,一般放置css文 ...

随机推荐

  1. Kafka 分区的目的?

    分区对于 Kafka 集群的好处是:实现负载均衡.分区对于消费者来说,可以提高并发度,提高效率.

  2. spring集成mongodb简单使用和测试方式

    @EnableMongoRepositories @ComponentScan(basePackages = "cn.example") @Configuration public ...

  3. matlab中fmincon函数求解非线性规划问题

    Matlab求解非线性规划,fmincon函数的用法总结 1.简介 在matlab中,fmincon函数可以求解带约束的非线性多变量函数(Constrained nonlinear multivari ...

  4. 单总线协议DS1820

    一. DS18B20简介 DS18B20数字温度传感器接线方便,封装后可应用于多种场合,如管道式,螺纹式,磁铁吸附式,不锈钢封装式.主要根据应用场合的不同而改变其外观.封装后的DS18B20可用于电缆 ...

  5. 判断1~n有多少个1

    判断1~n有多少个1 例如:1~12中 1 2 3 4 5 6 7 8 9 10 11 12 有1,10,11,12中含有1,则共有5个1: 算法如下: #include<stdio.h> ...

  6. css预编译--sass进阶篇

    基础篇中主要介绍了一些sass的基本特性,进阶篇中,主要是写一些我们常用的sass控制命令,函数和规则. 控制命令 可能看过基础篇的朋友会发现在有些代码中出现@if @else @each等,熟悉JS ...

  7. JS练习实例--编写经典小游戏俄罗斯方块

    最近在学习JavaScript,想编一些实例练练手,之前编了个贪吃蛇,但是实现时没有注意使用面向对象的思想,实现起来也比较简单所以就不总结了,今天就总结下俄罗斯方块小游戏的思路和实现吧(需要下载代码也 ...

  8. 微信小程序答题,怎么设计页面渲染,答完一题,跳到下一题

    想要的效果 1.第一页只显示第一道题的内容,如图红框2.答题后,点击下一题,内容显示第二道题的内容 代码 answer.wxml <!--pages/answer/answer.wxml--&g ...

  9. Codepen 每日精选(2018-3-31)

    按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以打开原始页面. 制作像素画的画板https://codepen.io/abeatrize/... 纯 css 画的晚上的风 ...

  10. HTML5 Canvas学习之路(六)

    一个炫酷的计时器 在慕课网看到一个canvas的课,感觉很炫酷,就把它看完了,然后记下来.http://www.imooc.com/learn/133 第一步:绘制要显示的时间 拿小球来绘制具体的数字 ...