前言
在上一章我们介绍了Thrift非阻塞型服务的父类AbstractNonblockingServer,因为内部的逻辑较复杂,且源码过多,所以主要以贴源码、添加注解的形式讲解。而本章在探索TThreadedSelectorServer源码时,我们按照AbstractNonblockingServer的serve()方法中的调用顺序来梳理整个过程。
回顾
研究源码前,我们先回顾下TThreadedSelectorServer的模型图,并对每个模块的功能加以说明,从而更易理解源码。
可以看到,整个服务框架分为以下几个部分:
- 一个AcceptThread线程对象,专门用于处理监听socket上的新连接。
- 若干个SelectorThread对象专门用于处理业务socket的网络I/O操作,所有网络数据的读写均是由这些线程来完成。
- 一个负载均衡器SelectorThreadLoadBalancer对象,主要用于AcceptThread线程接收到一个新socket连接请求时,决定将这个新连接请求分配给哪个SelectorThread线程。
- 一个ExecutorService类型的工作线程池,在SelectorThread线程中,监听到有业务socket中有调用请求过来,则将请求读取之后,交个ExecutorService线程池中的线程完成此次调用的具体执行。
TThreadedSelectorServer源码
TThreadedSelectorServer.Args
首先还是先看看Args类的变化 :
public static class Args extends AbstractNonblockingServerArgs<Args> {
public int selectorThreads = 2;//用于读写IO操作的线程数
private int workerThreads = 5;//执行业务的线程池的线程数
private int stopTimeoutVal = 60;//等待服务停止的检查间格
private TimeUnit stopTimeoutUnit = TimeUnit.SECONDS;//线程池参数
private ExecutorService executorService = null;//执行业务的线程池
private int acceptQueueSizePerThread = 4;//读写IO线程接收请求的队列大小
//接收 客户端新请求的策略类型
public static enum AcceptPolicy {
//已接收的连接请求,需要在线程池中注册,如果线程池已经满了,将立即关闭连接,由于调度将会稍微增加延迟
FAIR_ACCEPT,
//忽略线程池的状态,尽可能快的处理 客户端连接
FAST_ACCEPT
}
private AcceptPolicy acceptPolicy = AcceptPolicy.FAST_ACCEPT;//默认使用快速模式
public Args(TNonblockingServerTransport transport) {
super(transport);
}
//对线程池、读写IO的参数进行校验
public void validate() {
if (selectorThreads <= 0) {
throw new IllegalArgumentException("selectorThreads must be positive.");
}
if (workerThreads < 0) {
throw new IllegalArgumentException("workerThreads must be non-negative.");
}
if (acceptQueueSizePerThread <= 0) {
throw new IllegalArgumentException("acceptQueueSizePerThread must be positive.");
}
}
//省略get set方法
}
TThreadedSelectorServer的变量
了解了Args参数后,我们需要清楚TThreadedSelectorServer的成员变量与构造方法,方法的作用这里我们不列出来,调用时再讲解。
private AcceptThread acceptThread;//用于接收客户端连接的线程
private final Set<SelectorThread> selectorThreads = new HashSet<SelectorThread>();//处理IO读写操作的线程集合
private final ExecutorService invoker;//线程池两个功能1.将accept的连接添加给SelectorThread 2.执行业务
private final Args args;参数对象,
//构造方法中不多讲,如果没有传入线程池,会创建默认线程池,有兴趣的朋友自己看createDefaultExecutor的实现
public TThreadedSelectorServer(Args args) {
super(args);
args.validate();
invoker = args.executorService == null ? createDefaultExecutor(args) : args.executorService;
this.args = args;
}
在介绍了Args和TThreadedSelectorServer的参数和作用后,我们来开始研究一个服务是如何启动的。
AbstractNonblockingServer.Serve()
这里还是先回顾下,我们启动服务调用的serve()方法内部是如何调用的。
public void serve() {
//启动IO线程
if (!startThreads()) {
return;
}
//开始监听端口,接收客户端请求
if (!startListening()) {
return;
}
//修改服务状态为正在服务中
setServing(true);
//启动服务后的阻塞方法,服务停止后通过
waitForShutdown();
//修改服务状态为结束服务
setServing(false);
//停止监听端口
stopListening();
}
TThreadedSelectorServer.startThreads
源码如下:
protected boolean startThreads() {
try {
//根据读写IO线程数的设置,创建相应的读写IO线程放入集合
for (int i = 0; i < args.selectorThreads; ++i) {
selectorThreads.add(new SelectorThread(args.acceptQueueSizePerThread));
}
//根据serverTransport创建监控线程,来接收客户端的请求,createSelectorThreadLoadBalancer方法这里不算过多讲解,详情见AcceptThread
acceptThread = new AcceptThread((TNonblockingServerTransport) serverTransport_,
createSelectorThreadLoadBalancer(selectorThreads));
//启动所有的读写IO线程
for (SelectorThread thread : selectorThreads) {
thread.start();
}
//启动监控线程
acceptThread.start();
return true;
} catch (IOException e) {
LOGGER.error("Failed to start threads!"&e);
return false;
}
}
看完源码,我们进一步研究AcceptThread和SelectorThread的功能与联系。
AcceptThread与SelectorThread
protected class AcceptThread extends Thread {
private final TNonblockingServerTransport serverTransport;//监听端口的Socket
private final Selector acceptSelector;//Java Nio selector
private final SelectorThreadLoadBalancer threadChooser;//该类内部拥有一个selectorThreads集合的引用,主要就是每次可以从中获取一个SelectorThread对象
public AcceptThread(TNonblockingServerTransport serverTransport,
SelectorThreadLoadBalancer threadChooser) throws IOException {
this.serverTransport = serverTransport;
this.threadChooser = threadChooser;
this.acceptSelector = SelectorProvider.provider().openSelector();
this.serverTransport.registerSelector(acceptSelector);//会将serverTransport中的serverSocketChannel对象注册到Selector中
}
//线程运行方法
public void run() {
try {
//忽略
if (eventHandler_ != null) {
eventHandler_.preServe();
}
//服务未停止时,不断调用select()
while (!stopped_) {
select();
}
} catch (Throwable t) {
LOGGER.error("run() on AcceptThread exiting due to uncaught error", t);
} finally {
try {
acceptSelector.close();//关闭selector
} catch (IOException e) {
LOGGER.error("Got an IOException while closing accept selector!", e);
}
TThreadedSelectorServer.this.stop();//这一步是为了唤醒SelectorThreads中的读写IO线程
}
}
//唤醒selector
public void wakeupSelector() {
acceptSelector.wakeup();
}
//不断监测客户端请求连接事件,并处理请求
private void select() {
try {
//等待请求事件
acceptSelector.select();
//执行接收到的事件
Iterator<SelectionKey> selectedKeys = acceptSelector.selectedKeys().iterator();
while (!stopped_ && selectedKeys.hasNext()) {
SelectionKey key = selectedKeys.next();
selectedKeys.remove();
//非法的忽略
if (!key.isValid()) {
continue;
}
//处理连接请求
if (key.isAcceptable()) {
handleAccept();
} else {
LOGGER.warn("Unexpected state in select! " + key.interestOps());
}
}
} catch (IOException e) {
LOGGER.warn("Got an IOException while selecting!", e);
}
}
//接收一个连接
private void handleAccept() {
final TNonblockingTransport client = doAccept();//获取连接
if (client != null) {
// Pass this connection to a selector thread
final SelectorThread targetThread = threadChooser.nextThread();//选出一个SelectThread线程
//FAST_ACCEPT或无线程池时,直接将连接添加给SelectThread
if (args.acceptPolicy == Args.AcceptPolicy.FAST_ACCEPT || invoker == null) {
doAddAccept(targetThread, client);
} else {
// FAIR_ACCEPT模式下将连接作为一个任务提交给线程池,稍后处理
try {
invoker.submit(new Runnable() {
public void run() {
doAddAccept(targetThread, client);
}
});
} catch (RejectedExecutionException rx) {
LOGGER.warn("ExecutorService rejected accept registration!", rx);
client.close();
}
}
}
}
//从serverTransport中接收一个请求
private TNonblockingTransport doAccept() {
try {
return (TNonblockingTransport) serverTransport.accept();
} catch (TTransportException tte) {
// something went wrong accepting.
LOGGER.warn("Exception trying to accept!", tte);
return null;
}
}
//将新连接添加到SelectorThread的队列中
private void doAddAccept(SelectorThread thread, TNonblockingTransport client) {
if (!thread.addAcceptedConnection(client)) {
client.close();
}
}
}
SelectorThread是继承了AbstractSelectThread,所以AbstractSelectThread的方法可调用,忘记AbstractSelectThread方法作用的同学请回顾上一篇文章。
protected class SelectorThread extends AbstractSelectThread {
private final BlockingQueue<TNonblockingTransport> acceptedQueue;//用于保存AcceptThread传过来的 连接
public SelectorThread() throws IOException {
this(new LinkedBlockingQueue<TNonblockingTransport>());
}
public SelectorThread(int maxPendingAccepts) throws IOException {
this(createDefaultAcceptQueue(maxPendingAccepts));
}
public SelectorThread(BlockingQueue<TNonblockingTransport> acceptedQueue) throws IOException {
this.acceptedQueue = acceptedQueue;
}
//将连接添加到队列中
public boolean addAcceptedConnection(TNonblockingTransport accepted) {
try {
acceptedQueue.put(accepted);
} catch (InterruptedException e) {
LOGGER.warn("Interrupted while adding accepted connection!", e);
return false;
}
selector.wakeup();//selector唤醒,这步很重要,因为selectorThread会阻塞在select()方法,而一开始就没有任务时会永远阻塞,所以这里要唤醒
return true;
}
//工作循环,处理 IO读写事件,管理连接
public void run() {
try {
//服务未停止时无限循环
while (!stopped_) {
select();//处理IO读写
processAcceptedConnections();//处理队列中的新连接
processInterestChanges();//处理现有连接 注册的事件修改请求
}
//关闭时的清理工作
for (SelectionKey selectionKey : selector.keys()) {
cleanupSelectionKey(selectionKey);
}
} catch (Throwable t) {
LOGGER.error("run() on SelectorThread exiting due to uncaught error", t);
} finally {
try {
selector.close();
} catch (IOException e) {
LOGGER.error("Got an IOException while closing selector!", e);
}
TThreadedSelectorServer.this.stop();//唤醒其他accept thread 和 selector threads
}
}
//处理IO事件
private void select() {
try {
//等待IO事件
selector.select();
//处理接收到的IO事件
Iterator<SelectionKey> selectedKeys = selector.selectedKeys().iterator();
while (!stopped_ && selectedKeys.hasNext()) {
SelectionKey key = selectedKeys.next();
selectedKeys.remove();
//非法key忽略
if (!key.isValid()) {
cleanupSelectionKey(key);
continue;
}
if (key.isReadable()) {
handleRead(key);//处理读事件,父类实现的方法
} else if (key.isWritable()) {
handleWrite(key);//处理写事件,父类实现的方法
} else {
LOGGER.warn("Unexpected state in select! " + key.interestOps());
}
}
} catch (IOException e) {
LOGGER.warn("Got an IOException while selecting!", e);
}
}
//处理队列中的连接请求,将他们注册到本地selector中
private void processAcceptedConnections() {
// Register accepted connections
while (!stopped_) {
TNonblockingTransport accepted = acceptedQueue.poll();
if (accepted == null) {
break;
}
registerAccepted(accepted);
}
}
//将一个连接封装为一个FrameBuffer
protected FrameBuffer createFrameBuffer(final TNonblockingTransport trans,
final SelectionKey selectionKey,
final AbstractSelectThread selectThread) {
return processorFactory_.isAsyncProcessor() ?
new AsyncFrameBuffer(trans, selectionKey, selectThread) :
new FrameBuffer(trans, selectionKey, selectThread);
}
//将新连接注册到selector中,并设置为读事件,同时创建一个FrameBuffer与SelectionKey进行绑定
private void registerAccepted(TNonblockingTransport accepted) {
SelectionKey clientKey = null;
try {
clientKey = accepted.registerSelector(selector, SelectionKey.OP_READ);
FrameBuffer frameBuffer = createFrameBuffer(accepted, clientKey, SelectorThread.this);
clientKey.attach(frameBuffer);
} catch (IOException e) {
//清理工作
LOGGER.warn("Failed to register accepted connection to selector!", e);
if (clientKey != null) {
cleanupSelectionKey(clientKey);
}
accepted.close();
}
}
}
看完代码相信大家还是一头雾水,照旧,画一幅方法调用图,来理清AcceptThread与SelectorThread的关系。
结合序号我们按一次请求的完整处理流程来讲解:
1.AcceptThread 开始运行,发现有新请求时,进入2步骤。
2.获取到新连接,选取一个SelectorThread,并调用doAddAccept方法(备注:这一步可能由线程池来执行doAddAccept,默认模式下由AcceptThread调用该方法)
3.直接调用addAcceptedConnection方法。
4.将新连接加入到SelectorThread的队列中。
5.SelectorThread开始第一次运行。
6.处理队列中的连接,具体的处理方式调用registerAccepted方法。
7.将新连接注册到当前SelectorThread线程的Selector中,并封装为FrameBuffer绑定到注册好的SelectorKey上。
8.SelectorThread第二次运行,发现新连接有数据要读(这里假设一次读完了,然后线程池也将业务执行完毕了,向SelectorThread注册了将由read向write转换的事件)。
9.第三次运行发现,有事件修改需要处理,则将读通道转为写通道。
10.第四次运行发现 ,有写事件要处理,这时将结果返回给客户端。处理完后,再次注册事件修改,可能再次转为读事件(客户端长连接),也可能断掉(客户端短连接)
11.第五次再处理10中的事件修改。
至此,理清了AcceptThread与SelectorThread的关系后,startThreads()方法也就很容易理解了,serve()中的方法都已在上节介绍过,只有waitForShutdown()是子类实现,下节看该方法的源码;
TThreadedSelectorServer.waitForShutdown
@Override
protected void waitForShutdown() {
try {
joinThreads();
} catch (InterruptedException e) {
// Non-graceful shutdown occurred
LOGGER.error("Interrupted while joining threads!", e);
}
gracefullyShutdownInvokerPool();//处理线程池关闭操作,不详述
}
//等待AcceptThread和SelectorThread都停止运行
protected void joinThreads() throws InterruptedException {
acceptThread.join();//阻塞到这一步直到AcceptThread的无限循环跳出
for (SelectorThread thread : selectorThreads) {
thread.join();//阻塞到这一步直到SelectorThread的无限循环跳出
}
}
还是比较容易理解的,就是起到一个阻塞的作用,直到AcceptThread、SelectorThread线程执行完毕。至次整个启动的过程就讲解完毕了,下面需要介绍几个其他地方用到的方法。
TThreadedSelectorServer.stop
这个方法是AcceptThead、SelectorThread中任意一个在运行时抛出异常时调用,这个时候认为服务不可用,所以调用该方法。
@Override
public void stop() {
stopped_ = true;//停止服务,其他AcceptThead、SelectorThread线程读到时跳出run方法
stopListening(); //停止接收新请求
if (acceptThread != null) {
acceptThread.wakeupSelector();//可能acceptThread处于阻塞中
}
if (selectorThreads != null) {
for (SelectorThread thread : selectorThreads) {
if (thread != null)
thread.wakeupSelector();//可能SelectorThread处于阻塞中
}
}
}
TThreadedSelectorServer.requestInvoke
该方法是AbstractNonblockingServer中的抽象方法,在handleRead方法中读完数据时调用,来执行业务处理。
//将frameBuffer封装成Runnable对象扔进线程池,这里较易看懂,不详述
protected boolean requestInvoke(FrameBuffer frameBuffer) {
Runnable invocation = getRunnable(frameBuffer);
if (invoker != null) {
try {
invoker.execute(invocation);
return true;
} catch (RejectedExecutionException rx) {
LOGGER.warn("ExecutorService rejected execution!", rx);
return false;
}
} else {
// Invoke on the caller's thread
invocation.run();
return true;
}
}
protected Runnable getRunnable(FrameBuffer frameBuffer) {
return new Invocation(frameBuffer);
}
class Invocation implements Runnable {
private final FrameBuffer frameBuffer;
public Invocation(final FrameBuffer frameBuffer) {
this.frameBuffer = frameBuffer;
}
public void run() {
frameBuffer.invoke();
}
}
总结
本章至此TThreadedSelectorServer的源码介绍完毕,其中理解了AcceptThread与SelectThread的关系,整个服务的运行流程也就非常容易理解,对于细节大家参考源码中的注释即可。