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. Vue-Router在当前UR不刷新的Debug调试

    如果使用vue-router在当前页面刷新,则会中断此操作,没有反应,错误信息是: Error: Avoided redundant navigation to current location: & ...

  2. Spring Cloud 解决了哪些问题?

    在使用 Spring Boot 开发分布式微服务时,我们面临的问题很少由 Spring Cloud解决.与分布式系统相关的复杂性 – 包括网络问题,延迟开销,带宽问题,安 全问题.处理服务发现的能力 ...

  3. memcached 的 cache 机制是怎样的?

    Memcached 主要的 cache 机制是 LRU(最近最少用)算法+超时失效.当您存 数据到 memcached 中,可以指定该数据在缓存中可以呆多久 Which is forever, or ...

  4. 全页缓存FPC?

    除基本的会话 token 之外,Redis 还提供很简便的 FPC 平台.回到一致性问题, 即使重启了 Redis 实例,因为有磁盘的持久化,用户也不会看到页面加载速度的 下降,这是一个极大改进,类似 ...

  5. 构造器注入和 setter 依赖注入,那种方式更好?

    每种方式都有它的缺点和优点.构造器注入保证所有的注入都被初始化,但是 setter 注入提供更好的灵活性来设置可选依赖.如果使用 XML 来描述依赖, Setter 注入的可读写会更强.经验法则是强制 ...

  6. 构造器constructor是否可被重写override?

    构造器不能被继承,因此不能被重写,但可以被重载.

  7. isNotEmpty 与 isNotBlank 的区别

    isNotEmpty(str)等价于 str != null && str.length > 0 isNotBlank(str) 等价于 str != null &&am ...

  8. Demo示例——Bundle打包和加载

    Unity游戏里面的场景.模型.图片等资源,是如何管理和加载的? 这就是本文要讲的资源管理方式--bundle打包和加载. 图片 Unity游戏资源管理有很多方式: (1)简单游戏比如demo,可以直 ...

  9. C语言常用字符串函数

    string.h头文件中常用的函数 C 库函数 - strcat() char *strcat(char *dest, const char *src) 把 src 所指向的字符串追加到 dest 所 ...

  10. SpringMVC 配置和请求方式

    SpringMVC 总结内容 一.什么是 Spring MVC ? Spring MVC 是 Spring 对 MVC 思想的实现(三层架构) 优点: 二.前端控制器 Spring MVC 中的前端控 ...