Vert.x入坑须知(1)

一直以来早有将这些年用Vert.x的经验整理一下的想法,奈何天生不是勤快人,直到最近扶墙老师问起,遂成此文。前端

选择理由

如今想一想,咱们应该算是国内用Vert.x的最先一批人,版本大概是1.2.x吧,当时Vert.x内置了一个比较坑爹的模块系统,看似不错,但其实很坑爹。但即便这样,咱们当时仍是在技术选型上采用了它。理由大体以下:java

  • 性能,它的底层是netty,而且编程模型跟node.js一模一样,可算得上是“node on JVM”。同时,性能评测上比node还高出很多
  • 简单,它比netty更简单,并且能够轻易的支持cluster。
  • Actor模型,Verticle + Eventbus,下降了并发编程的难度。
  • WebSocket,刚好当时的项目须要这样的方案,服务器主动向前台推。而且Vert.x提供的EventbusBridge让前端js的组织更好。
  • 支持Groovy,用过的都知道,这里就不展开了。
  • 轻量级,部署简单。

因而乎,它顺利成章地成为了咱们当时系统接入层的中流砥柱,在实践中也确实发挥了很好的做用。node

踩坑指南

鉴于Vert.x当前的版本是3.3.3,所以本文的内容也主要针对这个版本而言,一些咱们遇到而且已经修复的bug,也就不会也没有必要在此啰嗦了。git

此外,本文也不是入门文档,而是为了预防陷坑而给出的指导意见,故在阅读本文以前还请先仔细阅读Vert.x的文档程序员

编程语言

虽然Vert.x的一大亮点号称是支持“多语言”,即同一个工程内能够同时用Java、Groovy、Javascript等不一样语言编写Verticle,但我仍是建议采用Java为主,最多辅以Groovy。缘由是:我发现不少新出的Vert.x模块仍是对Java支持最好,对于其余的则就至关通常了,起码不会让你感受特地针对这个语言而开发的。加上原本Java 8以后支持lambda,Java程序员的苦逼生活其实已经改善很多。github

dgate中,我主要采用Java + Groovy的方式,二者分工也很明确:前者用于数据处理,后者则用于DSL和数据类。apache

此时,因为混用了二者,而且可能会出现Groovy类要用到程序中Java类的状况,那么就要用到joint compile。在build.gradle中须要配置以下:编程

sourceSets.main.java.srcDirs = []
sourceSets.main.groovy.srcDirs += ["src/main/java"]

即,将Java类也交由Groovy编译器来编译。服务器

工程结构

虽然Vert.x能够内嵌到其余框架中,但在实际项目上我仍是偏心单独部署,项目的构建方式则为:gradle + fatjar。具体例子,能够参见这个build.gradle文件并发

我在Vert.x邮件组中常常看到有新人问关于Vert.x的组织方式,其实这是没有理解Vert.x的本质:Verticle。Verticle可视做Vert.x的一个最小部署和运行单元,简单的说,可类比为Servlet。所以,整个应用能够这样来划分:

  • Launcher,程序入口,负责调起Vert.x的环境。
  • MainVerticle,主Verticle,负责部署程序中其余的Verticle。
  • Verticle,程序处理逻辑,调用其余POJO/POGO。
  • POJO/POGO,普通类,供Verticle使用。

前二者负责初始化,Verticle则相似Servlet同样等待被触发(来自TCP/Eventbus/HTTP的Request),在实际处理时会调用到其余类。

这也就是为什么在上面的build.gradle中有这样关键的两行的缘由:

manifest {
    attributes 'Main-Class': '……'
    attributes 'Main-Verticle': '……'
}

Logging

Vert.x默认支持JUL,对于其余Logging框架也有支持。但我嫌每次运行要敲那么多命令很烦,那么能够在Launcher中强制设置环境变量:

System.setProperty("vertx.logger-delegate-factory-class-name",
                "io.vertx.core.logging.SLF4JLogDelegateFactory");

可参见dgate的Launcher代码

部署Verticle

跟Servlet相似,多个Verticle之间也会有依赖关系,存在前后部署的须要。

对于单个Verticle之间的依赖,如A依赖B,很简单,利用deployVerticle的回调就很好解决。由于代码简单,这里就再也不单独列出,仍是那句话,看文档。

对于依赖多个Verticle,如A依赖B和C,则须要有点技巧了:

  • 第一也是最差的方式,就是采用callback hell方式,层层递进。
  • 第二种方法采用rxJava,利用Observable的运算来完成。
  • 第三种方式,利用Java的Atom对象,示例代码(Groovy)以下:
private void deployVerties(List<Map> verticles, Closure completeHandler = null) {
    AtomicInteger count = new AtomicInteger(0)
    verticles.each { verticle ->
        vertx.deployVerticle(verticle.name, verticle.option ?: [:]) { result ->
            if (result.succeeded()) {
                if (count.incrementAndGet() == verticles.size()) {
                    if (completeHandler) {
                        completeHandler.call()
                    }
                }
            } else {
                exit(verticle.name, result.cause())
            }
        }
    }
}

看到Atom对象,你是否以为也能够采用CountDownLatch对象?很不幸,不行。我当时作过尝试,整个代码立马被Block住,直到我按了Ctrl-C。缘由在于:Block住了EventLoop。

至于deployVerticle(),它能够接受字符串和类实例。当使用字符串时,如果非Java类,如Groovy,须要采用这样的格式:"语言前缀:类全限定名"。如:

'groovy:hawkeyes.rtds.processor.MailMan'

此外,部署的Verticle实例并不是越多越好,还跟CPU的核数相关。

Block操做

Vert.x应用最忌讳Blocking操做,对此有多种处理:

  • 采用Worker Verticle
  • 使用executeBlocking函数

凡是涉及IO的操做,都请考虑一下。

EventBus

EventBus至关于Vert.x应用的神经系统,但有几点须要注意:

  • 若想给部署在另外一台机器上的Verticle发消息,这两个Verticle必需是在一个集群中。
  • 拦截EventBus的消息须要注意一下这个小地方

严格来说,3.2以后,上述第一点并不彻底正确。这两个Verticle之间能够采用TCP EventBusBridge来进行通讯,具体参见这篇文章

Cluster和内存计算

Cluster是当时我选择Vert.x的一个重要考量,并且将Vert.x应用单独打成fatjar还有一个附带好处就是Vert.x的cli均可以直接使用,其中就包括cluster命令。

Vert.x的集群创建在Hazelcast之上,除了集群调度,它自己还能作内存存储,即具有了Redis的主要功能。而且查询语法也比Redis(2.x)的要灵活,支持类SQL语法。更重要的是,其ReadThrough特性让人欲罢不能,简化了编程。固然,还包括其余如分布式锁、队列、任务等等。

所谓ReadThrough,即“若内存中没有,则查询将下传到下一级(一般是DB)”。Hazelcast的ReadThrough可经过实现MapLoader接口来实现。这个例子很简单,故可查看Hazelcast的文档了解。这里重点讲一下如何在Vert.x中去配置,由于Vert.x没有对此提供直接支持。

首先,cluster.xml即为一个标准的Hazelcast配置文件,故可在此配置相应的MapLoader便可:

<map name="map_name">
  <map-store enabled="true">
    <class-name>xxxLoader</class-name>
  </map-store>
</map>

在从未给集群Map赋过值且第一次运行下列代码时,注意两个名字要相同,则触发ReadThrough:

vertx.sharedData().getClusterWideMap("map_name") {……}

若是想在Vert.x中得到Hazelcast实例,则能够直接使用下面代码:

Set<HazelcastInstance> instances = Hazelcast.getAllHazelcastInstances()
hz = instances.first()

这样即可利用Hazelcast的其余功能。在3.3.3以后,Vert.x集群支持Ignite,它是比Hazelcast更强大的内存计算工具。并且,在Vert.x 3.4-beta1中已经再也不是技术预览版,往后我确定会全面拥抱它。

Ignite/Hazelcast不像Redis那样曝光率那么高,但鉴于其自己都是老牌内存计算软件,且在开源以前都在高强度生产环境(没记错的话是银行系统)实战演练过,同时对比一下二者之间的功能列表,你会发现这些工具其实更强大,尤为是Ignite。它们的文档都不错,值得一看。

Handler

最后说一说Handler中须要注意的地方,它很是适合写Restful API。

以前用Vert.x写接入层代码,主要集中在Core、Groovy和Shell部分。此次写dgate,算是扎扎实实用了一下Web部分。至于历史,我就不详细说了,总之一句话:哥是看着它长大的,;)。

Handler其实很简单,只须要注意几点:

  • Vert.x request Handler除了处理功能,还兼具Filter的功能。若处理完毕,请求不想让下一个request handler处理,则直接返回便可;不然,须要调用:routingContext.next()。
  • 对于同一个URL能够注册多个handler,以调用顺序为准。故,想先处理的,如验证,往前放。

至于其余,没啥可说的,都很简单。

写在最后

最后,来句鸡汤:遇坑不可怕,还得敢于尝试方能有所收获,但愿对各位有帮助!

相关文章
相关标签/搜索