最开始觉得这个系列也就最多 3 篇了不起了(因为事不过三嘛),没曾想居然迎来了第四篇!
由于最近决定投身到区块链的学习当中的缘故,出于更好的理解它的基本概念,自己动手参考文章写了一个迷你区块链的例子。采用了 kotlin + vertx 的工具选择。这次尝试再次验证了我在本系列一开篇所说:建议以 Java 语言开发为主。原因很简单,因为这个是基础,所以各方面支持(包括文档和功能方面)肯定是 Java 语言优先。
在做这个区块链的例子时,Vertx Kotlin 的文档让我有极为糟糕的体验:从整篇文档中,你找不到一个完整的用 kotlin 书写 Verticle 的例子,阅读的时候就感觉内容有跳跃。虽然你可以猜出应该是继承 AbstractVerticle,但你肯定还是希望文档中明确指出来。
当然啦,尽管有这样的问题,写代码的体验还是不错的。就 build.gradle 而言,跟 Java + Groovy 组合的差别不大。唯一需要注意的是,你可能需要将 kotlin 的 jvmTarget 设置为“1.8”。具体的配置可以参考工程的build.gradle。
总之,发现文档有问题,就先查 Java 文档。
上面的区块链的例子由两部分组成:前端静态页面 + 后端的 Verticle,前端静态页面通过 Ajax 请求与后端的 Verticle 交互。这其实就是通过 Vertx-Web 的 StaticHandler 来实现的,很简单。这里只提两个需要留心的小地方。
首先,静态资源的根路径默认情况下是:src/main/resources/webroot。即当你请求“http://localhost:8080/index.html”时,其实对应的是:src/main/resources/webroot/index.html。
其次,目前的 Web 应用的 URL 很少会直接出现“……/xxx.html”。按照向 Spring MVC 或 Grails 这里框架的做法,一般是经过一个 action,然后将浏览器导向某个页面。在做这个例子时,其实没有这么复杂的逻辑,让浏览器直接去加载某个页面(如 configure.html)就可以了。但这样会出现一个让人很不爽的 Path:“/configure.html”,而其他的路径因为主要是负责处理 Ajax 请求,都是形如“/mine”这样的路径。
为了统一路径风格,这里采用了一个小技巧:RoutingContext.reroute。参考代码如下:
router.get("/configure").handler({ rc: RoutingContext -> rc.reroute("/configure.html") })
Vert.x JDBC client缺省的连接池提供者是 c3p0,但它也支持其他其他的连接池,比如大名鼎鼎的 Hikari。但遗憾的是,文档中没有给出一个完整的代码示例。对于想换用其他连接池的同学,可以参考下面的代码:
JDBCClient.createShared(vertx, new JsonObject()
.put('provider_class', 'io.vertx.ext.jdbc.spi.impl.HikariCPDataSourceProvider')
.put('driverClassName', 'org.postgresql.Driver')
.put('jdbcUrl', jdbcUrl)
.put('username', username)
.put('password', password)
.put('maximumPoolSize', maximumPoolSize)
.put('minimumIdle', minimumIdle)
.put('cachePrepStmts', true)
.put('prepStmtCacheSize', 250)
.put('prepStmtCacheSqlLimit', 2048));
对于用 Postgresql 的同学,还可以看看Reactive Postgres Client,一个高性能的轻量级 jdbc client 同时自带连接池,作者也是 vertx 的贡献者。
使用 Vert.x 的最大好处就是极大简化了多线程编程的复杂性,大部分时候你几乎不需要去操心,这部分内容分别在文档的Standard verticles和Worker verticles有描述。
但文档中并没有专门阐述这一原则是否对于回调函数也适用,毕竟回调函数执行的时机不确定并且典型的 Vert.x 程序充斥着回调。对于这个问题,简单地说:同样适用。下面的示例代码可以验证这一点:
public class Vert1 extends AbstractVerticle {
long count = 0;
@Override
public void start() {
HttpClient httpClient = vertx.createHttpClient();
for (int i = 0; i < 20; i++) {
httpClient.getAbs("http://www.baidu.com/", response -> {
count++;
System.out.println(count);
}).end();
}
}
}
从输出来看,完全正确。作为对比,你可以在 groovyConsole 中运行下面的代码(多按几次 ctrl - r):
int count = 0
def c = {
10.times {
count++
println count }
}
def t1 = new Thread(c)
def t2 = new Thread(c)
t1.start()
t2.start()
并且,通过调研 Vert.x 源代码,你可以(在 HttpClientRequestBase 中)发现:
void handleResponse(HttpClientResponseImpl resp) {
synchronized (getLock()) {
// If an exception occurred (e.g. a timeout fired) we won't receive the response.
if (exceptionOccurred == null) {
long timeoutMS = currentTimeoutMs;
cancelOutstandingTimeoutTimer();
try {
doHandleResponse(resp, timeoutMS);
} catch (Throwable t) {
handleException(t);
}
}
}
}
很明显,Vert.x 内部已经为你提前预防了,这就是框架的力量!如果你还在用 Netty,不妨考虑 Vert.x 这个建构于它之上的高层工具吧。
Subrouter是个好东东,可 API 设计有个问题:只有 mount,没有 unmount!一般情况下,unmount 的确用不上,但你一旦想实现动态路由时,它就是万万不可缺少的了。
好在我自己摸索出了下面的方法:
public static void unMountSubRouter(Router router, String root) {
router.getRoutes().stream()
.filter(route -> route.getPath() != null && route.getPath().startsWith(root))
.forEach(route -> route.remove());
}
有趣的是,Vert.x 的开发者曾经觉得 subrouter 用处不大,并动了把它在未来拿掉的念头。当这个想法被提出来征求社区意见时,立马有人跳出来说:“subrouter 的设计非常好,哥的程序严重依赖它,请继续保留。”
我曾经不止一次看到初学者在问类似这样的问题:
要回答这些问题,需要首先搞清楚几个事实。
并且,通过观察其他人写的 Vert.x 代码(包括 Vert.x 自己的那些子项目),可以总结出来几个套路。
这是最常见的结构:
标准 Verticle 和 Worker Verticle 之间通过 eventbus 进行交互,整个架构其实也很简单:
request <—> standard verticles <—> worker verticle
这里的一个典型反模式,尤其是初学者会大概率犯的错误:将本该 worker 干的活,交给了标准 verticle,即将图中后两个组件合二为一。这种情况在写 Vertx Web 时非常容易出现,尤其受传统 MVC 框架的影响,无意识地将原来的编程套路给照搬过来了:在 Handler 中进行了大量操作。我自己也不例外,走过这段弯路。
Don’t block me!
以 Vert.x Web 应用为例,由于 Handler 实际上是在 eventloop 上执行,若它被阻塞,即导致后续请求全部无法得到处理。因此,最合适的做法就是:
利用 Vert.x 的特点,将 IO 操作封装成异步库。
用 Vert.x 将业务功能封装成微服务,然后利用现成的基础设施与其他应用交互:
这也是我最喜欢用的模式,轻量,简单,部署方便。我不太喜欢在一个本来就已经含有复杂业务逻辑的 Grails 应用中再包含一个 Vert.x Verticle 了。
或许有同学对于上面的最后一项,感到疑惑。其实这个很简单,以 Postgresql 为例,可以采用两种模式:
行了,本篇写到这里也差不多了。最后给大家推荐一个网页:Awesome Vert.x,上面有不少不错的资源。
觉得有帮助的话,不妨考虑购买付费文章来支持我们 🙂 :
付费文章