在近几年的开源Java容器市场上,Tomcat依旧保持在龙头老大的位置,其地位丝毫没有被撼动的迹象。与此同时Tomcat也因为架构臃肿结构复杂而饱受批评。作为Tomcat的另一款替代性的Java容器Jetty要比Tomcat简单很多,Jetty作为内嵌容器被开源社区广泛使用。基于Jetty之上有很多轻量级Java Web框架,比如著名的JFinal就是基于Jetty开发出来的。
Jetty的线程架构模型非常简单,分为acceptors,selectors和workers三个线程池。acceptors负责接受新连接,然后交给selectors处理HTTP消息协议的解包,最后由workers处理请求。前两个线程池采用非阻塞模型,一个线程可以处理很多socket的读写,所以线程池数量较小。
大多数项目,acceptors线程只需要1个,selectors线程配置2~4个足矣。workers是阻塞性的业务逻辑,往往有较多的数据库操作,需要的线程数量较多,具体数量随应用程序的QPS和IO事件占比而定。QPS越高,需要的线程数量越多,IO占比越高,等待的线程数越多,需要的总线程数也越多。
这张图的上半部分,被称之为ServerConnector连接器,一般是一个ServerSocket对应一个ServerConnector。如果服务器要监听多个端口,就会有多个ServerSocket,相应也会有多个ServerConnector。这上半部分Jetty已经给我们做好了,无需操心其内部实现。需要关心的是业务逻辑,在下半部分的Worker线程里运行,服务器启动前将URL的路由规则以及相应的业务处理器注册进去,然后服务器就可以跑起来了。多个ServerConnector共享同一个Server实例。
下面我们写一个最简单的Hello World
import java.io.IOException;
import java.io.PrintWriter;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.NetworkTrafficServerConnector;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
class BlockChainHandler extends AbstractHandler {
private String name;
public BlockChainHandler(String name) {
this.name = name;
}
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
response.setContentType("text/html; charset=utf-8");
response.setStatus(HttpServletResponse.SC_OK);
PrintWriter out = response.getWriter();
out.printf("<h1>hello %s</h1>", this.name);
baseRequest.setHandled(true);
}
}
public class JettyHello {
public static void main(String[] args) throws Exception {
int w = 4; // worker threads
int a = 1; // acceptor threads
int s = 2; // selector threads
QueuedThreadPool workers = new QueuedThreadPool(w);
Server server = new Server(workers);
Executor executors = Executors.newFixedThreadPool(a + s);
ServerConnector connector = new NetworkTrafficServerConnector(server, executors, null, null, a, s,
new HttpConnectionFactory());
connector.setHost("127.0.0.1");
connector.setPort(7777);
server.addConnector(connector);
ContextHandler btcHandler = new ContextHandler("/btc");
btcHandler.setHandler(new BlockChainHandler("bitcoin"));
ContextHandler ethHandler = new ContextHandler("/eth");
ethHandler.setHandler(new BlockChainHandler("ethereum"));
ContextHandlerCollection handlers = new ContextHandlerCollection();
handlers.addHandler(btcHandler);
handlers.addHandler(ethHandler);
server.setHandler(handlers);
server.start();
server.join();
}
}
在这个例子中我们提供了两个子路由/btc和/eth,分别映射到不同的处理器实例。我们定义了一个连接器,监听本地7777端口。连接器参数中有个HttpConnectionFactory,表示改端口适用HTTP协议,如果要走HTTPS协议,需要使用SslConnectionFactory。除此之外还有ProxyConnectionFactory,在编写代理服务器时需要使用。
注意例子中的线程配置,executors线程池的大小必须大于等于acceptors和selectors数量之和,否则请求会卡住。如果不提供executors参数,acceptors和selectors需要的线程会和workers线程共享线程池,最终实际运行的workers线程数将会偏小。
我们使用eclipse看一下服务器的线程分配情况
可以看到除了main线程之外,还有1个acceptor线程,2个selectors[pool-1-thread-x]线程和4个workers[qtp]线程,其中qtp是QueuedThreadPool的首字母缩写。
Jetty提供了非常多的Handler处理器,可以让我们方便的处理各种请求
- ResourceHandler 静态资源处理器
- Redirector 重定向处理器
- ErrorHandler 错误处理器
- InetAccessHandler IP地址黑名单白名单限制处理器
- BufferedResponseHandler 输出缓冲处理器
- ThreadLimitHandler 限制单个IP的线程数,防止Dos攻击