3.3 黄金法则
这里的黄金法则不是说按照这个法则你就能得到好的性能,不按照这个法则你就只能得到正常的性能,而是类似于“铁律”的意思,使用 Vert.x,你一定要遵照这一点。这条黄金法则是:
不要阻塞事件循环。
开发过客户端程序的同事应该都知道,执行耗时操作的代码应当放到 Worker线程 中执行,用户的 UI线程 应该永远保持响应。Vert.x 中的事件循环就像客户端中的 UI线程 一样,不允许执行阻塞或者耗时操作(包括有可能阻塞或耗时的操作),包括但不限于:
Thread.Sleep
- 从 Socket 读数据
- 发送一个HTTP请求并等待响应
- 等待一个锁
- 执行了一个显著耗时的内存操作
我们前面说过,一个 Verticle 里的所有事件处理函数都是运行在一个 事件循环 中,是单线程的,想象一下本来一秒钟可以处理10000个请求(每个 请求 都是一个 事件)的网站服务器程序被一个耗时操作卡了2秒钟是什么感觉,这比用户 UI线程 卡2秒严重多了,这可能是一万个用户同时多等待了2秒,按照某些老师的思路,你浪费了大家5个多小时的时间。
不过这个 黄金法则 并不会影响我们实现下面的业务逻辑:
- 等待一段时间,再运行后续代码
- 从 Socket 读数据,再运行后续代码
- 发送一个 HTTP POST 请求并收到响应,再运行后续代码
- 等待一个锁,再运行后续代码
- 执行了一个显著耗时的内存操作,再运行后续代码
——只要你调用 Vert.x 提供的异步无阻塞的 API:
Vertx.setTimer
NetSocket.handler
HttpClient.post
SharedData.getLock
Vertx.executeBlocking
它们会完成这些耗时操作并通知你结果,你只需要在 处理函数 中处理这个结果就可以了:
这样设计有什么好处呢?具体有以下两点:
- 通过线程复用提高系统资源利用率,避免高并发启动过多用户线程。如果网络服务器对于每个请求都新开一个用户线程,那么高并发时线程数会显著上升,线程上下文切换将会消耗大量的CPU资源。而 Vert.x 通过使用线程池,复用特定数量的线程,将阻塞的 IO操作 交由操作系统或其他线程异步执行,使用户线程只需要运行无阻塞的 事件处理函数,保证了及时响应以及高性价比的并发支持。
- 减少了资源竞争,天然的线程安全。因为每个 Verticle 中的用户代码都是单线程的,所以我们可以在 Verticle 中放心的寄存状态变量而不用担心线程安全问题,这使 Vert.x 非常适合开发 有状态 的服务。