事件总线
一件总线是Vert.x中令人兴奋的系统。在每个Vert.x实例中都有一个单线程的事件总线,可以使用evenBus方法包括进来。通过事件总线,应用程序中的不同部分可以彼此通迅,也不用关心用什么语言编写,是否在同一个Vert.x实例或在不同的Vert.x实例。甚至可以在浏览器的客户端javaScript中被桥接到同一个事件总中进行通迅。事件总线构建了一个点对点的分布式消息系统驱动着多个服务器节点和多个浏览器。事件总线支持发布/定阅,点对点,请求响应得模式。事件总线API非常简单。只涉及注册或解注册处理器和发送,定阅消息。
首要的一此概念:
理论部分
寻址
消息通过事件总线发往一个地址。Vert.x不会因为一些奇特的寻址方式而困惑。在Vert.x中,一个地址是一个简单字符串。任意字符串都有效。然而Vert.x会明智地使用一些总类的方案,例如会使用周期去说明某个命名空间。一些有效的例子如 Europe.news.feed1,acme.games.pacman,sausages, X.
处理器
消息在处理器中被收到。处理器被注册到特定地址。一些不同的处理器可以注册到同一个地址。一个单一的处理器能被注册到多个不同的地址。
发布/定阅消息
事件总线支持发布消息。消消被发布到一个地址。发布意味着传递消息给所有注册到那个地址的处理器。这是一个常见的发布/定阅消息模式。
点对点和请求-响应消息
事件总线也支持点对点消息。消息被发送到某个地址。Vert.x仅将其路由给注册到那个地址的一个处理器。如有多个处理器注册到这个地址,将会采用非严格的Round-robin算法选择一个处理器。在发送点对点消息时,可以指定一个可选的应答处理器。当某个消息被接收者收到,并被处理,接收者可以选择决定是否应答此消息。如果选择应答,应答处理器将被调用。当应答被发送者收到,也可以再次应答。这样可以无线重复,并可以在两个不同的verticles中建立对话。这就是被称之为请求-响应的一般消息模式。
最卖力的投送
Vert.x尽其最大努力投递消息,不用担心消息被丢弃。这就称之为最大努力投送。然而,在消息总线部分失效或者全部失效的情况下,消息可能会丢失。如果应用很在意消息丢失,那就应编写幂等的处理器,并且在事件总线恢复抬发送者偿试重发。
消息的类型
Vert.x创新地允许基本/简单类型,字符串,或者buffers作为消息被发送。在Vert.x中,一个简例和通常实践是发送JSON消息。JSON对象在Vert.x支持的有语言中很容易创建,读取和解析,JSON已经变成Vert.x的一种通行语。事件总线非常灵活并且支在其之上发送任意对象。可以通过给对象定义编/解码器,就可以发送这样对象了。
事件总线API
让我们投入API
获取事件总线
你可以向下面这样获取事件总线引用:
EventBuseb = vertx.eventBus();
每个Vert.x实例,有一个唯一事件总线实例。
注册处理器
注册一个处理器的最简单方法是使用consumer方法。下面是一个例子:
EventBuseb = vertx.eventBus();
eb.consumer("news.uk.sport",message -> {
System.out.println("I have received amessage: " + message.body());
});
当一个消息到达处理器时,处理器将被调用并传入message参数。一个MessageConsumer类的实例将在调用consumer()方法时被返回。MessageConsumer对象可以用于顺序地解注册处理器,或者将处理器用作流。作为替代,可以使用consumer方法获取一个没有处理器的MessageConsumer对象,然后通过MessageConsumer的handler方法设置处理器。例如:
EventBus eb = vertx.eventBus();
MessageConsumer
consumer.handler(message-> {
System.out.println("I have received amessage: " + message.body());
});
在一个集群的事件总线上注册一个处理器时,Vert.x会需要一些时间将处理器注册到集群中的每个节点。如想在注册完成后得到通知,可以在MessageConsumer对象上注册一个完成处理器:
consumer.completionHandler(res-> {
if (res.succeeded()) {
System.out.println("The handlerregistration has reached all nodes");
} else {
System.out.println("Registrationfailed!");
}
});
解注册处理器
为了解注册处理器,必须调用unregister。如是一个集群的事件总线,集群中跨节点解注册将会需要一点时间,也可以注册完成处理器获取成功通知。
consumer.unregister(res-> {
if (res.succeeded()) {
System.out.println("The handlerun-registration has reached all nodes");
} else {
System.out.println("Un-registrationfailed!");
}
});
发布消息
发布消息是简单。指定要发送到的地址,例用publish方法发送。
eventBus.publish("news.uk.sport","Yay! Someone kicked a ball");
这个消息将会被传递给注册到news.uk.sport地址的所有处理器。
发送消息
发送消息将导至注册到地址上的一个处理器收到消息。这就是点对点消息模式。处理器按非严格的round-robin方式选择。可以用send方法发送消息。
eventBus.send("news.uk.sport","Yay! Someone kicked a ball");
给消息设置头
通过消息总线发送消息也可以设置消息头。在发送或发部消息时,通过DeliveryOptions对象设定消息头。
DeliveryOptionsoptions = new DeliveryOptions();
options.addHeader("some-header","some-value");
eventBus.send("news.uk.sport","Yay! Someone kicked a ball", options);
消息排序
Vert.x将按特定的消息发送者发送消息的顺序将消息传递给特定的处理器。
消息对象
消息处理器的收的对象就是消息(Message). 消息体相当于被发送或发布的以象。通过headers可以获取消息头。
确认消息或发送应答
在使用send方法时,事件总线试图将消息传递给注册在事件总线上的MessageConsumer对象。在发送者想了解什么时间消费者已经收到并处理了消息这种情况下,这非常有用。为了确认消息被处理了,消费者可以调用reply方法发送应答消息。这发生在,应答发回发送者并且应答处理器处理应答的时候。
一个例子能清晰说明:
接收者:
MessageConsumer
consumer.handler(message-> {
System.out.println("I have received amessage: " + message.body());
message.reply("how interesting!");
});
发送者:
eventBus.send("news.uk.sport","Yay! Someone kicked a ball across a patch of grass", ar -> {
if (ar.succeeded()) {
System.out.println("Received reply:" + ar.result().body());
}
});
应答可以包含有用信息的消息体。“处理”实际上意味着应用程序定义和完全取决于消息消者做什么而不是Vert.x事件总线自身了解或关心什么。
一些例子:
· 一个简单的消息消费者,实现了获取时当天时间的服务,此服务调用时将返回带有时间的应答体。
· 如果一个消息持久化成功,这个实现持久对列的消息消费者返回一个true或false确认
· 当定单被成功处理并从数据库中删除,一个带有true的确认消息会被处理订单的消息消费者返回。
发送超时
在指定定应答处理器时发送消息,可以通过DeliveryOptions指定超时时间。如果在超时时间内没有收到应答,失几将会在应答处理器调用时进行处理。默认的超时时间是30秒。
发送失败
消息因为其他原因会发送失败,包括:
· 没有可获取的消息处理器
· 接收者显示地使用fail方法产生失败消息。
在这些情况,失败将在处理器被调用时处理。
消息编解码器
只要你定义并注册了特定编/解码器,你可以在事件总线上发送任何消息。在发送或发布消息时,必须在DiveryOptions中指定消息编解码器名子:
eventBus.registerCodec(myCodec);
DeliveryOptionsoptions = new DeliveryOptions().setCodecName(myCodec.name());
eventBus.send("orders",new MyPOJO(), options);
如果你总想使用相同的编解码器发送特殊的类型消息,你可以为它注册一个默认的编解码。器。然后不必每次发送消息时在DeliveryOptions中指定编解码器了。
eventBus.registerDefaultCodec(MyPOJO.class,myCodec);
eventBus.send("orders",new MyPOJO());
集群事件总线
事件总线不仅存在于单一的Vert.x实例中,在集群环境下,网络中的不同的Vert.x实例构成一个单一,分布的事件总线。
集群编程
如果通过程序创建Vert.x实例,可能通过配置集群的Vert.x实例获取集群的事件总线;
VertxOptionsoptions = new VertxOptions();
Vertx.clusteredVertx(options,res -> {
if (res.succeeded()) {
Vertx vertx = res.result();
EventBus eventBus = vertx.eventBus();
System.out.println("We now have aclustered event bus: " + eventBus);
} else {
System.out.println("Failed: " +res.cause());
}
});
必须确定在类数径中有ClusterManager实现,例如默认的HazelcastClusterManager.
命令行方式集群
也可以命令行方式运行Vert.x的集群
vertx runmy-verticle.js –cluster
verticle中自动清除
如果在verticle内部注册事件总线处理器,这些处理器在verticle御载时自动解注册。
配置事件总线
事件总线是可配置的。在事件总线被集群时,这特别有用。在事件总下之下使用TCP连接发送和接收消息时,使用EventBusOptions让你可以配置TCP连接的所有方面。因为事件总线扮演了一个服务器与客户端的角色。配置与NetClientOptions和NetServerOptions类似。
VertxOptionsoptions = new VertxOptions()
.setEventBusOptions(new EventBusOptions()
.setSsl(true)
.setKeyStoreOptions(newJksOptions().setPath("keystore.jks").setPassword("wibble"))
.setTrustStoreOptions(newJksOptions().setPath("keystore.jks").setPassword("wibble"))
.setClientAuth(ClientAuth.REQUIRED)
);
Vertx.clusteredVertx(options,res -> {
if (res.succeeded()) {
Vertx vertx = res.result();
EventBus eventBus = vertx.eventBus();
System.out.println("We now have aclustered event bus: " + eventBus);
} else {
System.out.println("Failed: " +res.cause());
}
});
前面的代码片断说明了在事件总线中如何使用SSL连接代替普通TCP连接。
警示:为了集群模式中的安全,必须配置集群管理器使用加密和强制安全。可以能看集群管理器文档获取更多信息。事件总线的配置需要在所有的集群的节点上一至。EventBusOptions也让你设置事件总线是否被集群。通过setClustered,getClusterHost,getClusterPort方法可以配置主机和端口。
当在容器中使用时,也可以配置公共的主机和端口:
VertxOptionsoptions = new VertxOptions()
.setEventBusOptions(new EventBusOptions()
.setClusterPublicHost("whatever")
.setClusterPublicPort(1234)
);
Vertx.clusteredVertx(options,res -> {
if (res.succeeded()) {
Vertx vertx = res.result();
EventBus eventBus = vertx.eventBus();
System.out.println("We now have aclustered event bus: " + eventBus);
} else {
System.out.println("Failed: " +res.cause());
}
});
JSON
与其他语言不同,Java没有对JSON一等的类支持。所以Vert.x提供了两个处理JSON类。
JSON对象
一个JSON对象仅是一个map,键是一个字符串,value是JSON支持的类型(string,number,Boolean).JSON对象也支持null值。
创建JSON对象
默认构造方法可被用来创建空的JSON对象。也可以从一个JSON格式的字符串创建JSON对象:
StringjsonString = "{\"foo\":\"bar\"}";
JsonObjectobject = new JsonObject(jsonString);
添加实体到JSON对象
使用put方法将值添加到JSON对象。因为流式API,方法可链式调用。
JsonObjectobject = new JsonObject();
object.put("foo","bar").put("num", 123).put("mybool", true);
从JSON对象获取值
可用getXXX方法从JSON对象获取值,例如:
Stringval = jsonObject.getString("some-key");
intintVal = jsonObject.getInteger("some-other-key");
将JSON字符串编码成字符串
可以使用encode去将对象编码成字符串格式。
JSON数组
JsonArray类表示JSON数据。一个JSON数组是一个值的序列(string,number,boolean).JSON数组可以包含null值。
创建JSON数组
用默认构造方法可以创建空的JSON数组。也可从符JSON格式的字符串创建JSON数组,例如:
StringjsonString = "[\"foo\",\"bar\"]";
JsonArrayarray = new JsonArray(jsonString);
向JSON数组中添加数据
可以使用add方法几JSON数组中添加数据实体:
JsonArrayarray = new JsonArray();
array.add("foo").add(123).add(false);
从JSON数组中获取人值
可以使用getXXX方法从JSON数组中获取值。例如:
Stringval = array.getString(0);
IntegerintVal = array.getInteger(1);
BooleanboolVal = array.getBoolean(2);
将JSON数组编码成字符串
可以使用encode将JSON数组编码成字符串格式。