【Vert.x初体验1】4行代码实现HTTP Server

4行代码

上古时代的Java程序员如果想写一个HTTP服务,需要按下面的步骤操作:

  • 编写Servlet, 实现doXXX()方法
  • 打成war包
  • 部署Tomcat
  • 将war包copy到指定目录下进行"部署"
  • 访问8080

一顿操作猛如虎,旁边的小弟小妹对你佩服的五体投地。

我可能只是想说一句Hello World而已,需要这么装X么?

到了中古时代,事情变的更麻烦了。虽然不需要直接写servlet, 但为了正确配置好"春天"这个框架,我们要写一箩筐XML文件,一不小心写错Tomcat就会一堆Error。由此就诞生了"面向XML编程"的段子。

到了近代,我们终于可以直接在main()方法里写代码了,但是之前还需要引入一堆xxx-starter和spring boot的打包插件,这样才能做到打出的jar包可以执行运行。不过本质还是Servlet没变,只是框架帮你隐藏了而已,感觉还是少了那么点意思。

来到当下,Vert.x来拯救我们了!来看看一个最简单的HTTP Server长啥样:

Vertx vertx = Vertx.vertx(); // (1)
vertx.createHttpServer() // (2)
      .requestHandler(req -> req.response().end("it works!")) // (3)
      .listen(8080); // (4)

只需要4行代码!而且,你不需要继承xxx-parent, 更不需要xxx-starter, 你只需要在pom.xml中添加一个依赖就搞定了:

<dependency>
    <groupId>io.vertx</groupId>
    <artifactId>vertx-web</artifactId>
    <version>3.5.4</version>
</dependency>

长久以来,很多人都一直在指责Java"又臭又长", 开发速度慢,运行时笨重,Tomcat更重,IDE难用(主要指Eclipse)等槽点,当然,他们说的都没错,错就错在Servlet。Servlet在当时(2001年)的编程环境下确实起到了Web开发大一统的作用,但慢慢的很多躁动的程序员们就会不满这种呆板笨重编程方式。好了扯远了,我们先来看看上面的几行代码都干了什么:

(1): 构造一个Vertx对象,这里暂时没什么好说的

(2): 创建了一个HttpServer对象

(3): 注册一个请求处理器,这里我们无论什么请求,只要来了就返回一个"it works!"字符串

(4): 让Server在8080端口上监听

是不是非常的直观?

我知道,做为一个有上进心的开发者,我们怎么能不关心你这几行代码能抗多少请求呢?业务代码写在哪?错误处理?先不要着急,以后会讲到的,这里只是想告诉大家,用Java写Http Server,其实还有更优雅的方式。

Verticle

Verticle是Vert.x里最重要的概念。之前我们在准备篇《C10K问题与Reactor模式》中讲过什么是Reactor, 这里的Verticle其实就是Reactor的一个实现,即事件循环。 只不过,Vert.x有一个"部署"的概念,每一个Verticle需要先进行部署,且Vert.x保证一个Verticle在整个Vertx生命周期内一定只由同一条线程来执行,这样就避免了线程安全问题。等等,这说的好像跟Netty里的EventLoop有点像?对,事实是,Vert.x底层就是Netty, Verticle直接就是使用Netty的EventLoop执行的。可能有人会问,为什么不直接用Netty? 哦,如果想用Netty的话,可以先去看看官网的Hello World需要多少行代码吧。

下面我们来看一下一个比较"正常"的Vert.x HTTP Server应该怎么写:

VertxOptions options = new VertxOptions();
        options.setEventLoopPoolSize(8);
        Vertx vertx = Vertx.vertx(options); // (1)

        DeploymentOptions depOps = new DeploymentOptions();
        depOps.setInstances(8);
        vertx.deployVerticle(HttpVerticle.class, depOps, ar -> { // (2)
            if (ar.succeeded()) {
                System.out.println("done deployment");

            } else {
                System.out.println(ar.cause());
            }
        });

(1): 我们在创建Vertx对象时设置了一个参数对象,把事件循环线程数设为8

默认值为 CPU核心数 * 2, 我用的是物理4核的macbook pro, 这里如果不设置会是16,因为有超线程技术加持。

(2): 这块可能看起来比较复杂,其实非常简单。前面说过Verticle需要部署,那么这就是编程式部署的代码了。调用deployVerticle()方法,第一参数是我们想要部署的Verticle对象本身(下面会给出代码); 第二个参数是部署参数,这里我们设置部署8个Verticle; 第三个参数用来注册一个回调方式,部署完成或失败时Vert.x会使用NIO线程调用此方法。

在Vert.x里会经常看到这种异步回调的方式,类似于Node.js, 前期需要适应一下。

接下来看看主解HttpVerticle长什么样:

 public static class HttpVerticle extends AbstractVerticle { // (0)
        @Override
        public void start(Future<Void> startFut) throws Exception {
            vertx.createHttpServer()
                    .requestHandler(req -> req.response().end("it works!"))
                    .listen(8080, result -> { // (1)
                        if (!result.succeeded()) { // (2)
                            System.out.println("failed to start server, msg = " + result.cause());
                            startFut.fail(result.cause()); // (3)
                        }
                    });
        }
    }

对,基本上就是之前我们的4行代码。

(0): 我们需要继承AbstractVerticle来编写自己的Verticle

(1): 监听8080端口也需要注册一个回调方法,这样才能知晓是否监听成功。

(2): 判断是否发生了错误

(3): 如果出错,调用方法参数中传过来的Future对象的fail()方法,作用是通知Vertx这个Verticle启动失败了,这样上面deployVerticle()中注册的回调里的失败逻辑才会被调用。

这里有朋友肯定会想,EventLoopPoolSize跟verticle的Instances到底应该设置多少合适呢?前面说了,一个Verticle总是由同一条NIO线程执行,如果NIO线程数与verticle数相同,那么Vertx会保证每一个verticle都会有一条专有线程执行;如果NIO > verticle, 那么会有NIO线程空闲; 如果 NIO < verticle, 那么会出现一个NIO线程负责执行多个verticle的情况。这里要注意的是,第三种情况也不会有线程安全问题,因为一个verticle还是总是由同一条线程执行的。

着急的程序员可能已经不满足于上面这种Hello World玩具代码了,他们想尽快把自己的业务代码塞进去。如果想发挥异步编程的全部实力,那么业务代码必须也要"异步"起来,即所有的阻塞调用,都要通过注册事件-->回调的方式执行,万万不可阻塞NIO线程。说白了,就是争取让NIO线程一直在干需要CPU发力的活,一刻也不能因为等待I/O而停下来。应该怎么办呢,下期再说。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章