vertx高级指南(一)
注:本文译自Julien Viet的Advanced Vert.x Guide
本文旨在解释和讨论以下内容
- Vert.x的设计
- 内部API
- 与Netty集成
当你阅读本指南时你将:
- 更深入了解Vert.x
- 了解如何将Vert.x与其他第三方库集成
- 了解如何使用Netty和Vertx.x编写网络应用
这是一个实时指南,你可以做出贡献,只需提交PR或者提issuus
本指南中公开了一些内部Vert.x API,但谨记,这些API可能会在需要时进行更改。
Vert.x中的上下文
io.vertx.core.Context
接口是Vert.x的一个重要组件。
上下文(Context)可以被认为是应用程序如何执行事件(或者handler创建的任务)。
绝大部分的事件是通过上下文(Context)下发的, 当应用程序消费事件时,往往存在与事件调度相关联的上下文。
Verticle上下文
部署verticle的实例时,Vert.x会创建一个上下文并将其与该实例关联。你可以通过AbstractVerticle
的context
字段在verticle中访问此上下文
1 | public class MyVerticle extends AbstractVerticle { |
当MyVerticle
部署后,Vert.x 发送一个 start 事件, Verticle上下文调用start
方法:
默认情况下,上下文始终是事件循环(event-loop)上下文,调用线程是事件循环 (event loop)
当vertile部署为worker时,调用线程是Vert.x的worker池之一
上下文(Context)的特殊之处
从 Vert.x 3开始支持不通过Verticle使用Vert.x的API,这引出了一个有意思的问题:到底使用哪个上下文(Context)?
当一个Vert.x的API被调用,Vert.x关联当前线程到一个特殊的event-loop context,Vertx#getOrCreateContext()
在第一次被非Vertx线程调用时创建上下文,然后在随后的调用中返回此上下文。
因此,异步Vert.x API上的回调在同一上下文中发生:
1 | public class Main { |
上下文(Context)的传递
大多数Vert.x API都包含上下文的存在(原文aware of明白上下文的存在)。
在上下文中执行的异步操作将调用具有相同上下文的应用程序。
同样,事件处理程序也在同一上下文上调度。
1 | public class MyVerticle extends AbstractVerticle { |
处理上下文(Context)
大多数应用程序不需要与上下文进行紧密的交互,但有时访问它们会很有用,例如,你的应用程序使用另一个库,该库在自己的线程上执行回调,并且你希望在原始上下文中执行代码。
上文我们看到,verticle可以通过context
字段访问其上下文,但这意味着使用verticle并在verticle上有一个引用可能并不总是方便的。
你可以通过 getOrCreateContext()
获取当前上下文:
1 | Context context = vertx.getOrCreateContext(); |
你也可以使用静态方法Vertx.currentContext()
:
1 | Context context = Vertx.currentContext(); |
如果当前线程没有与上下文关联,则后者可能返回null,而前者将在需要时创建一个上下文,因此永远不会返回null。
在获取了上下文之后,你可以在这个上下文中执行代码:
1 | public void integrateWithExternalSystem(Handler<Event> handler) { |
在实践中,很多Vert.x的API和第三方库就是这样实现的。
事件循环上下文(Event-loop context)
事件循环上下文使用事件循环(Event-loop)来运行代码:处理程序直接在IO线程上执行,因此:
- 处理程序将始终使用同一个线程执行
- 处理程序决不能阻塞线程,否则它将会导致该事件循环关联的所有IO任务阻塞(原文是starvation)
这种行为通过保证关联的处理程序总是在同一个线程上执行,从而消除了同步和其他锁机制的需要,从而大大简化了线程模型。
事件循环上下文是默认和最常用的上下文类型,在没有worker标志的情况下部署的verticle将始终使用事件循环上下文进行部署。
Worker上下文(Worker context)
Worker上下文被分配给在启用worker选项的情况下部署的verticle上。Worker上下文与标准事件循环上下文的区别在于,工作线程在单独的工作线程池上执行。
这种与事件循环线程的分离允许Worker上下文执行阻塞事件循环的阻塞操作类型:阻塞这样的线程除了阻塞一个线程之外不会影响应用程序。
正如事件循环上下文一样,Worker上下文确保处理程序在任何给定时间只在一个线程上执行,也就是说,Worker作上下文上执行的处理程序将始终按顺序执行,一个接一个,但不同的操作可能在不同的线程上执行。
上下文异常处理器
你可以在上下文上设置异常处理程序,用以捕获在上下文上运行的任务引发的任何未检查的异常,如果未设置异常处理程序,则默认改为调用Vertx
异常处理器。
1 | context.exceptionHandler(throwable -> { |
如果未设置任何处理程序,则异常将作为错误记录,并显示消息_Unhanded exception_你可以使用reportException报告上下文中的异常
1 | context.reportException(new Exception()); |
发射事件
runOnContext
是在上下文上执行一段代码的最常见方式,尽管它非常适合将外部库与Vert.x集成,但它并不总是最适合将在事件循环级别执行的代码(如Netty事件)与应用程序代码集成。
Vert.x有一些内部方法可以根据情况实现类似的行为 :
ContextInternal#dispatch(E, Handler<E>)
ContextInternal#execute(E, Handler<E>)
ContextInternal#emit(E, Handler<E>)
Dispatch
dispatch
假定调用线程是上下文线程,它将当前执行线程与上下文关联起来:
1 | assertNull(Vertx.currentContext()); |
该处理器也被阻塞线程检查器检测。
最后,处理程序抛出的任何异常都会报告给上下文:
1 | context.exceptionHandler(err -> { |
Execute
execute
在上下文上执行任务,当调用线程已经是上下文线程时,直接执行任务,否则安排此任务计划执行(实际是提交给背后的event-loop执行,补充源码如下)。
没有上下文关联也可以。
1 | protected <T> void execute(ContextInternal ctx, Runnable task) { |
Emit
emit
是 execute
和 dispatch的
组合
1 | default void emit(E event, Handler<E> eventHandler) { |
emit
可以用于从任何线程上发射事件到处理器上:
- 在任何线程中,它的行为都类似于
runOnContext
- 如果是上下文线程,它通过上下文中的本地线程关联关系、阻塞线程检查器运行事件处理器,并报告上下文上的失败
在大多数情况下,emit
方法是让应用程序处理事件的方法,dispatch
和execute
方法的主要目的是赋予代码更多的控制权,以实现非常具体的事情。
上下文感知的futures
在 Vert.x 4 之前,Future 都是静态创建的对象,与上下文没有特定关系。 Vert.x 4 提供了一个基于 future 的 API,它遵循了与 Vert.x 3 相同的语义:future 上的任何回调都应该可预测地在相同的上下文上运行。
Vert.x 4 的API 创建绑定到调用者上下文的 future,在上下文上运行回调:
1 | Promise<String> promise = context.promise(); |
任何回调都会在创建 Promise 的上下文中发出,上面的代码大概率会长这样:
1 | Promise<String> promise = Promise.promise(); |
此外,该 API 允许创建成功和失败的 future:
1 | Future<String> succeeded = context.succeededFuture("OK usa"); |
上下文追踪(Contexts and tracing)
从 Vert.x 4 开始,Vert.x 集成了流行的分布式跟踪系统。
追踪库通常依赖于thread local来传播跟踪数据,例如,处理 HTTP 请求时收到的跟踪信息应该在整个 HTTP 客户端中传播。Vert.x 以类似的方式集成追踪,但依赖于上下文而不是thread local,上下文由 Vert.x API 传播,因此为实现追踪提供了可靠的存储。
由于给定服务器处理的所有HTTP请求都使用创建HTTP服务器的相同上下文,因此服务器上下文对于每个HTTP请求,是 duplicated _(重复的)_,以授予每个HTTP请求的唯一性。
1 | public class MyVerticle extends AbstractVerticle { |
这种重复(复制)操作共享原始上下文的大部分特性并提供特定的本地存储。
1 | vertx.createHttpServer() |
然后应用就可以使用它了
1 | Context context = vertx.getOrCreateContext(); |
ContextInternal#duplicate()
复制当前上下文,它可用于确定追踪行动的范围
关闭钩子函数(Close hooks)
Close hooks 是 Vert.x 的一项内部功能,在 Verticle 或 Vertx 实例关闭时可通知到组件, 它可用于实现 verticle 中的自动清理功能,例如 Vert.x HTTP 服务器。
io.vertx.core.Closeable
接口及其 close(Promise<Void> closePromise)
方法定义了接收关闭通知的实现
1 |
|
ContextInternal#addCloseHook
注册了一个Closeable
的实例用于通知上下文什么时候关闭:
1 | context.addCloseHook(closeable); |
当 Verticle 实例停止时,Verticle 部署创建的上下文会调用该钩子。否则,当 Vertx 实例关闭时会调用该钩子。
Context#removeCloseHook
取消注册关闭钩子,并会在调用关闭钩子函数之前资源即将关闭时使用。
1 | context.removeCloseHook(closeable); |
钩子函数为避免泄漏是用弱引用实现的,但是不论如何你也应该取消注册钩子。
在重复上下文上添加钩子,会将钩子添加到原始上下文。
同样,VertxInternal
也暴露了相同的方法来在 Vertx 实例关闭时接收通知。