Java 异步非阻塞编程框架 Vert.x

Vert.x项目起始于2011年,最开始的名字叫做 node.x,是一个仿照Node.js以Java开发的,基于EventLoop的异步非阻塞编程框架。虽然从时间上来说也算有一定年头了,但是一直没有大火,从3.x开始才稍微流行了一点。

Vert.x的底层IO基于 Netty4 实现,核心模块 Vertx Core,支持非阻塞 文件IO,TCP,UDP,DNS,HTTP,HTTPS,HTTP2(包括h2和h2c)。比较基础的模块 Vert.x-Web ,提供了包括 URL 路由,模板引擎在内的开发web server所需的API,Vert.x Web Client,提供一个非阻塞Http Client实现。

对于JDBC,MongoDB,Redis,Kafaka,Java Mail,Apache Shiro等都提供了封装集成,还提供了服务发现,断路器,配置中心等微服务所需要的设施,以及基于Hazelcast/Apache Ignite/Zookeeper 的Cluster功能。

Vertx Core 与线程模型

Vert.x里面基础的执行单位是Verticle,Verticle是继承自AbstractVerticle的类,在EventLoop线程里执行。一个Verticle实例总是绑定到固定的一个线程,其中的代码都在同一个线程里执行。因而虽然是异步程序,但通常不用考虑线程同步的问题,也可使用mutable的对象。一个Vert.x程序,可以有很多Verticle实例,如果Verticle实例数量多于线程数量,则多个实例会被绑定到同一个线程;只使用一个Verticle实例的话,就只能使用一个线程,这个时候跟Node.js是一样的。

在EventLoop线程里面的代码,不能有阻塞操作,比如使用阻塞IO,加锁,长时间占用CPU的代码等。Vert.x提供了单独的worker线程池来运行阻塞的代码,典型的比如JDBC操作,因为并没有非阻塞的实现,Vert.x就是使用了单独的woker Verticle来封装。

Verticle 之间可以通过EventBus通信,可以用发布-订阅的方式发送消息,可以发送点对点的消息。如果使用了Cluster,则集群中的不同进程之间的Verticle也可以发送信息。

Vertx Web Server 示例

下面代码创建了一个非阻塞的web server,提供提供了一个功能,使用HTTP请求网易新闻头条内容,然后抽取其中的文章标题,并以Json格式返回给客户端。

import io.vertx.core.AbstractVerticle;
import io.vertx.core.DeploymentOptions;
import io.vertx.core.Vertx;
import io.vertx.core.VertxOptions;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServer;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.client.HttpResponse;
import io.vertx.ext.web.client.WebClient;
import io.vertx.ext.web.codec.BodyCodec;

public class Server extends AbstractVerticle {
    private WebClient client;

    @Override
    public void start() {
        client = WebClient.create(vertx);

        HttpServer httpServer = vertx.createHttpServer();
        Router router = Router.router(vertx);
        router.route(HttpMethod.GET, "/headline_title").handler(context -> {
            client.get("c.m.163.com", "/nc/article/headline/T1348647853363/0-20.html")
                    .as(BodyCodec.jsonObject()).send(asyncResult -> {
                if (asyncResult.succeeded()) {
                    // 请求成功
                    HttpResponse<JsonObject> response = asyncResult.result();
                    JsonObject body = response.body();
                    JsonArray array = body.getJsonArray("T1348647853363");
                    JsonArray titles = new JsonArray();
                    for (int i = 0; i < array.size(); i++) {
                        JsonObject item = array.getJsonObject(i);
                        String title = item.getString("title");
                        titles.add(title);
                    }

                    HttpServerResponse serverResponse = context.response();
                    serverResponse.putHeader("content-type", "application/json");
                    serverResponse.end(titles.toBuffer());
                } else {
                    // 请求失败
                    context.fail(asyncResult.cause());
                }
            });
        });

        httpServer.requestHandler(router::accept).listen(8080);
    }

    @Override
    public void stop() throws Exception {
        client.close();
    }

    public static void main(String[] args) {
        VertxOptions vo = new VertxOptions();
        vo.setEventLoopPoolSize(Runtime.getRuntime().availableProcessors() * 2);
        Vertx vertx = Vertx.vertx(vo);
        DeploymentOptions options = new DeploymentOptions();
        options.setInstances(Runtime.getRuntime().availableProcessors() * 8);
        vertx.deployVerticle(Server.class.getName(), options);
    }
}

代码里启动了4倍cpu数量的Verticle instance,EventLoop 线程池数量为2倍物理CPU数量。Router里注册了Handler,处理/headline_title这个Path上的请求,主要的逻辑在这个Handler里。

可以看到,Vert.x是一个合格的学生,主要API模仿传统的Node.js而来,都是采用Callback的形式,复杂逻辑写起来会掉进Callback Hell。

Verticle还提供了方面的Json操作,这也是对Node.js的高度仿真,其底层使用的是Jackson。

Vert.x 配合RxJava

为了解决Callback方式的复杂性,Vert.x提供了RxJava的绑定Vert.x Rx,以Managed Callback的方式来写代码,以及Quasar的绑定Vert.x Sync,以协程的方式来写代码。Quasar这个东西现在在项目中使用还不是让人太放心,勇敢且有智慧的用户可以去试试。

这里提供一个例子,使用Vert.x Rx完成同样的功能。

import io.vertx.core.DeploymentOptions;
import io.vertx.core.VertxOptions;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.rxjava.core.AbstractVerticle;
import io.vertx.rxjava.core.Vertx;
import io.vertx.rxjava.core.http.HttpServer;
import io.vertx.rxjava.core.http.HttpServerResponse;
import io.vertx.rxjava.ext.web.Router;
import io.vertx.rxjava.ext.web.client.WebClient;
import io.vertx.rxjava.ext.web.codec.BodyCodec;

public class RxServer extends AbstractVerticle {
    private WebClient client;

    @Override
    public void start() {
        client = WebClient.create(vertx);

        HttpServer httpServer = vertx.createHttpServer();
        Router router = Router.router(vertx);
        router.route(HttpMethod.GET, "/headline_title").handler(context -> {
            client.get("c.m.163.com", "/nc/article/headline/T1348647853363/0-20.html")
                    .as(BodyCodec.jsonObject()).rxSend()
                    .map(hr -> {
                        JsonArray array = hr.body().getJsonArray("T1348647853363");
                        JsonArray titles = new JsonArray();
                        for (int i = 0; i < array.size(); i++) {
                            JsonObject item = array.getJsonObject(i);
                            String title = item.getString("title");
                            titles.add(title);
                        }
                        return titles;
                    }).subscribe(v -> {
                        HttpServerResponse response = context.response();
                        response.putHeader("content-type", "application/json");
                        response.end(v.toString());
                    },
                    context::fail);
        });

        httpServer.requestHandler(router::accept).listen(8080);
    }

    @Override
    public void stop() throws Exception {
        client.close();
    }

    public static void main(String[] args) {
        VertxOptions vo = new VertxOptions();
        vo.setEventLoopPoolSize(Runtime.getRuntime().availableProcessors() * 2);
        Vertx vertx = Vertx.vertx(vo);
        DeploymentOptions options = new DeploymentOptions();
        options.setInstances(Runtime.getRuntime().availableProcessors() * 8);
        vertx.deployVerticle(RxServer.class.getName(), options);
    }
}

需要注意的是,上面虽然很多类看起来名字一样,其实是使用的Vert.x Rx对应的封装类。

总结

Vert.x是Java生态里一个比较完善的异步非阻塞框架,在实现上高度的模仿了Node.js,通过绑定线程来避免使用者维护数据一致性的负担。

Vert.x基础的API都是采用Callback的方式来做异步编程,对于复杂的业务逻辑编写困难。为此,Vert.x提供了Vert.x Rx和Vert.x Sync模块以简化编程。

编辑于 2018-04-24

文章被以下专栏收录