You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

144 lines
14 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 13优化番外篇Vertx介绍及快速入门
你好,我是志东,欢迎和我一起从零打造秒杀系统。
经过前面课程的学习我们知道Nginx和Tomcat都可以做网关服务并且从理论出发做了分析比对也从实践上做了相应服务的开发那么今天我们将学习一款优秀的、可开发网关服务的技术即Vertx。该技术的总体性能要优于Tomcat但弱于Nginx。其在国内的普及度相对国外来说还是比较低的但已经有些公司开始尝试使用了比如京东的PC商详页服务、秒杀Web服务都是用它来开发的并且线上实际效果也很不错。
接下来我们将对Vertx做个简单的介绍并实际搭建一个Vertx项目来替换demo-web的角色重新构造秒杀Web系统的一环。
## **Vertx简介**
我们先了解一下Vertx可以用来干什么这样我们才能在已知的技术栈中找出一个和其相对应的技术来帮助理解。
首先它可以开发Web服务这点Nginx、Tomcat也能做。
其次它也有点像Spring它提供了完整的生态包括Vertx Web、Vertx Core、Vertx Unit等模块但它和Spring也不是互斥关系我们待会搭建的项目中就会使用Vertx+Spring它们可以配合使用。
然后它还提供了很多其他的能力比如EventBus的消息机制实现服务间通信同时它还可以支持多种语言的开发。
**所以我们很难直接给它做一个精确的定义,但其总体的设计思路就是异步非阻塞。**这里不仅仅是针对我们熟悉的一些请求消息的读写还包括下游服务的调用甚至是数据库的读写都可以实现异步支持你去编写全链路的非阻塞代码。这样可以使用有限的线程来充分利用机器的CPU资源当然这并不能缩短业务接口的执行时间这点你得清楚。
那么从以上这几个方面来看的话我们其实很难用一节课把Vertx技术介绍全面所以这里我们定个更加好实现的小目标那就是用Vertx+Spring来开发一个Web服务。你可以在这个过程中和Nginx还有Tomcat做个简单的对比。
## **Vertx对比Nginx****和****Tomcat**
首先Nginx由于多进程、单线程、多协程的模式其占用资源更少并发处理能力更强再加上其事件驱动、异步非阻塞等特性总体性能也是三者中最好的并且比Vertx和Tomcat要强出很多。
Nginx还可以支持使用Lua、C等语言做业务开发但大部分以Java为主的公司能够支持的SDK、中间件等相对较少所以一般都是用它来做反向代理和负载均衡器。因为秒杀的特殊业务场景故而我们启用了Nginx来做前置网关。
Tomcat的话大部分公司都用它来部署服务不管是HTTP服务还是RPC服务。虽然底层线程模型一直在迭代更新新的NIO模式也大大提高了并发处理能力但由于其历史更久、内部结构更重所以整体性能也是三者中最差的但好在普及度高绝大部分的公司技术栈里都有它。所以在秒杀系统中我们暂时只用其来启动RPC服务不再用它来部署Web服务了。
最后是Vertx就像上面介绍的那样其在诞生之初就是以纯异步化的思想来设计的。其内部提供的几乎所有接口都支持异步并且所有的操作都是基于事件来实现程序的非阻塞运行。再加上它的网络通信框架集成了Netty其内部的零拷贝机制、基于内存池的缓冲区重用机制还有高性能的序列化框架以及它本身的Multi-Reactor模式总结来看它的性能是要优于Tomcat的。但由于它仍使用线程池来处理业务请求所以整体性能是弱于Nginx的。
**它的底层线程模型,大概是这样的,如下图所示:**
![图片](https://static001.geekbang.org/resource/image/a5/8f/a525b96dd2cfd7cdc0e59f464fef988f.jpg?wh=1091x753)
简单来说Vertx在启动时可以部署多个Verticle实例每个Verticle实例都是Actor模型的一种具体实现其内部定义好了整个流程的处理顺序当然我们可以针对可扩展项做自定义开发
它接受请求然后将其封装成事件并交给EventLoop线程来处理。当然在EventLoop线程中我们也可以决定是否将后续事件交给Worker线程来处理比如像调用外部RPC接口等IO操作。
这里有个黄金原则即在EventLoop线程里运行的程序一定不能阻塞该线程否则这将直接影响其处理后续请求的效率。正是因为其有着不错的性能表现所以在秒杀系统中我们使用它代替了demo-web来开发新的Web服务。
那在了解了Vertx的一些基本概念之后咱们来实际搭建一个Vertx Web项目使其具备demo-web的能力同时上下游系统demo-nginx和demo-support不用做任何的改动。下面咱们就正式开始吧
## **Vertx项目搭建**
**第一步:**新建一个项目demo-vertx-web并包含两个module整体项目结构和demo-web类似如下图所示
![图片](https://static001.geekbang.org/resource/image/3c/cc/3cfbc9eaa68e8b8c0bc5be5acc85c9cc.png?wh=764x598)
其中demo-vertx-gateway模块用来做一些文件的配置并且也是用来打包部署的demo-vertx-service主要用来做业务聚合这块的代码到时候可以直接将demo-web中的service模块代码拷过来即可。所以我们主要的改动点也都是在demo-vertx-gateway这一块。
**第二步**由于没了Tomcat来启动服务所以我们需要自己写个main方法来加载对应的配置完成项目的启动由于我们仍然使用Spring来管理类所以这里也通过Spring提供的机制来完成对应配置的加载。如下图所示
![图片](https://static001.geekbang.org/resource/image/42/16/4254467264d63ebc102187208d884e16.png?wh=1920x823)
那我们继续看下InitialModule类都配置了些什么
![图片](https://static001.geekbang.org/resource/image/5f/b9/5f4c6a996b938b90a5de67eacd6f70b9.png?wh=1920x1064)
这里首先是导入Spring的配置文件spring-config同时配置Spring的包扫描路径其次是初始化Vertx然后开始部署Verticle实例这里Vertx的初始化配置和部署实例的配置如下
![图片](https://static001.geekbang.org/resource/image/c6/8a/c63a5950d369238b7bc8c11148b8568a.png?wh=1920x893)
vertxOptions主要配置了两个属性即EventLoop线程池大小和Worker线程池大小而deploymentOptions主要配置了部署的实例数。
这里要注意一下一个Verticle实例会被指定到一个EventLoop线程处理一个EventLoop线程可以处理多个Verticle实例所以这里的EventLoop线程数最好和CPU核数相等而部署的实例数最好小于等于EventLoop线程数这样可以确保一个线程只对应处理一个Verticle实例。比如你是8核机器那就配置8个EventLoop线程和8个Verticle实例。
**第三步:**从上面可以看到我们部署的实例是MainVerticle在这里我们可以编排请求的处理流程先看下代码如下图所示
![图片](https://static001.geekbang.org/resource/image/52/39/5220cb6cdff8c2f0a7d3fc38fd59ca39.png?wh=1740x1668)
这里我们首先是创建了一个Router即路由这个类主要是用来设置请求的处理逻辑包括哪个URL请求由哪个类的方法来处理是让EventLoop线程来执行还是让Worker线程来执行处理的过程是什么样的等等。
其次我们就按上面说的对Router进行配置了。这里分了两种情况一种是处理动态请求的一种是处理静态资源请求的。动态请求的配置咱们先放一下等会细说这里先说下配置静态资源请求的情况。
这里统一都是由StaticHandler来处理同时设置了启用静态资源的缓存这样就不用每次都读文件了大大地提高了响应的速度。
StaticHandler处理静态资源的逻辑是直接根据你请求的URL然后加上配置的WebRoot来读取文件。所以比如我们想获取结算页H5我们的请求URL是/settlement/page这个时候需要我们reroute一下重新路由到对应要返回的HTML文件路径这块也会在下面说到。
设置好路由Router之后我们就可以配置HTTP服务的启动配置了包括各种HTTP相关的设置如连接超时配置、端口号设置等。
**第四步:**这块再详细说下动态路由的配置我们是通过RouterHelper来做相应配置的。这块的主要功能是将请求URL与对应的处理方法做绑定并注册到Router这样请求进来后就知道该执行哪段业务逻辑了。
![图片](https://static001.geekbang.org/resource/image/d0/0d/d0fd1ab2byy795abccd9035710531b0d.png?wh=1920x968)
这里首先是从Spring管理的Bean中找出添加了我们自定义注解RequestMapping的类然后遍历将请求URL与对应的类和方法注册到Router中具体的注册方法如下
![图片](https://static001.geekbang.org/resource/image/c1/69/c151ef7027ac0e2ebb1fd427fc3eb369.png?wh=1920x1204)
这里我们指定了业务逻辑由Worker线程来执行那是怎么指定的呢
我们是通过Router的blockingHandler()方法来指定的。如果想在EventLoop线程里执行则使用route.handler()方法。关于如何合理分配任务我们有个简单的基本原则比如你想每秒处理1000个请求那么理论上每个请求的处理时间不能超过1ms如果超过了1ms那就不应该放到EventLoop线程里执行否则会影响后续请求的处理所以一般为了简单起见我们都是将业务逻辑直接分配给Worker线程来处理。
另外在blockingHandler中我们可以在执行目标方法之前增加像监控、限流之类的公共逻辑有点像拦截器的概念。然后执行目标方法的的调用这里是通过Java的反射来做的。
![图片](https://static001.geekbang.org/resource/image/ca/c7/ca76e89abd8aaee1df8f7c9b07f7cfc7.png?wh=1920x787)
这里目标方法的入参暂时只适配了2种一个是RoutingContext另一个是HttpServerRequest。
这是Vertx的一个弊端它没有提供直接的功能无法像SpringMVC一样方便能够支持在Controller的方法入参里直接定义我们想要的变量来接受入参的值得需要我们自己去扩展。所以这里为了简便我们直接先将原始的RoutingContext或HttpServerRequest往下传其中RoutingContext是一次请求的上下文HttpServerRequest是将HTTP请求的相关信息都封装进去了包括入参、Header等。
另外这里要重点关注一下blockingHandler()方法的第二个入参可以设置Hander是顺序执行还是并发执行。这个是指同一个Verticle实例下同一个适配URL默认是true但在高并发场景下需要设置成false否则吞吐量上不去。
最后在执行完目标方法之后需要将响应结果返回。我们根据响应类型也做了区分处理目前支持两种一个是JSON返回另一个是HTML返回。对于HTML的返回我们reroute到了真实的静态文件路径如下图所示
![图片](https://static001.geekbang.org/resource/image/35/53/359807dyyb6a64d6136179592cff5153.png?wh=1920x986)
并且在异常时,也做同样的区分处理。
**第五步:**到这里一些核心逻辑的编写就完成了。下面再看一个业务Handler的示例为了让你适应SpringMVC的使用习惯所以这里的写法风格上也是类似的
![图片](https://static001.geekbang.org/resource/image/b4/38/b41d3e40600ec4409451c772bc9af038.png?wh=1920x732)
这里要记得加上Spring的@Component注解这样Spring才能将其管理起来。同时也要加上自定义的@RequestMapping注解这样才能被我们自己的逻辑扫到。
那下面再展示下两种响应类型的写法,如下图所示:
![图片](https://static001.geekbang.org/resource/image/2c/00/2c1f6e5bb3e45a6f8b40444ff64e9100.png?wh=1920x943)
一个是默认返回JSON另一个是返回HTML真实的HTML文件路径HTML的返回需要指定RT的类型。
其他的如Dubbo相关配置都是和demo-web一样的剩下的我们只需要将demo-web中的业务代码拷过来即可。唯一有个变动的点就是在活动查询接口有个请求跨域问题需要在Nginx中设置响应结果的Header其他的都没有变化。
那这样咱们的demo-vertx-web项目就算搭建完成了之后可以按照以前的测试方式从商详页进入到结算页然后完成下单测试整个秒杀流程这里就不再复述了。
## 总结
这节课我们初步认识了Vertx这是一种以异步化思想来设计的技术。其内部提供的几乎所有接口都是异步的并且像传统的RPC调用、数据库读写等阻塞操作Vertx也都提供了异步化、非阻塞的解决方案。
这和我们传统的开发方式有所不同你可以借助Vertx习惯这种新的开发思想。当然异步化编程势必会增加代码的复杂度这也是其弊端。而对于开发Web服务这块我们横向对比分析了Vertx与Nginx、Tomcat的优劣势其性能介于Tomcat与Nginx之间而这三种技术都有其自身的优缺点并且我们在秒杀系统中都有使用到。
像Nginx性能最优我们用其来做前置网关直面大流量的冲击Vertx性能次之所以我们用其来开发业务Web服务Tomcat使用起来最方便也最普及可以用来发布RPC服务或者是像ERP这种小流量的Web服务。
同时在今天这节课中我们也用了较多的篇幅来讲解Vertx开发Web服务的详细过程帮助你更直观地学习Vertx并投入到实际的使用中。
虽然如此但今天介绍的也仅仅是Vertx的冰山一角这里只是起到抛砖引玉的作用Vertx所具备的能力绝对不止这些。如果你感兴趣可以自己做进一步的学习当然更多的相关开发使用也可以关注我的GitHub我们一起交流学习。
## **思考题**
这节课我们实际开发的Vertx项目处理业务逻辑这块仍然使用的是Worker线程池这块和Tomcat没有太大区别。如果我们使用Vertx提供的异步化API该如何改造现有代码呢又会带来怎样的影响
期待你的思考,欢迎你来评论区和我讨论问题,交流经验!我们下节课再见。