netty 是基于 java nio 上封装的框架,所以它的核心功能一定也是依赖 java nio的核心组件。因为原生java nio太原始,如果要在生产上使用,程序员要花大量的时间在非业务上的处理。如写半包,jdk select的空轮询等等。
netty 从 并行,池化,网络通信等方面帮助我们优化程序。并行可以让我们程序充分利用cpu,今天我们浅析一下netty的线程模型。
这篇文章我们先讲讲什么是 reactor模型:
reactor反应堆的引入是为了更好的管理IO。通俗来来讲,我们将每个IO 绑定对应的回调,比如写回调,读回调,然后放在一个集合里。这样一个个被绑定的结构体组合起来,我们称为反应堆,里面被绑定的结构体叫反应粒子。来一个事件反应一下,来一个事件反应一下。
reactor 反应堆的三种模式:
一 单reactor线程模式:
单reactor线程简单来讲,即接收IO事件,也处理事件回调。我们来直接看图
eventLoop可以简单理解为,下面伪代码的 for(;;)。
我用伪代码说明一下流程:
//将普通事件封装为绑定了回调函数的数据结构
class EventItem{
public Object writeCallBack();
public Object readCallBack();
public Object acceptCallBack();
Event event;
String type;
}
// Selector的包装类,可以直接返回EventItem集合
WrapperSelector wrapperSelector = WrapperSelector.open();
ServerSocketWrapper serverSocketWrapper = new ServerSocketWrapper();
// 将 serverSocket注册到 wrapperSelector
serverSocketWrapper.initAndregister(wrapperSelector);
for(;;){
// 取出就绪事件
Set<EventItem> eventItemSet = wrapperSelector.select();
// 调用就绪事件的回调函数
for(EventItem eventItem:eventItemSet){
// 判断是否为 accept 事件,下面的条件判断如此类推
if(eventItem.type.equals("accept")){
eventItem.acceptCallBack();
}
if(eventItem.type.equals("read")){
eventItem.readCallBack();
}
if(eventItem.type.equals("write")){
eventItem.writeCallBack();
}
}
}
这个伪代码的逻辑是:
1 将普通事件封装为EventItem
2 循环取出就绪事件
3 根据就绪事件的类型调用回调函数
二 单reactor线程,多业务线程:
所谓的单reactor线程,多业务线程指的是,reactor线程只处理 accept 事件,read事件和write事件分发给业务线程
我们来看图:
这个伪代码可以这么来写:
//将普通事件封装为绑定了回调函数的数据结构
class EventItem{
public Object writeCallBack();
public Object readCallBack();
public Object acceptCallBack();
Event event;
String type;
}
// 初始化一个 线程池
ExecutorService dispatcherPool = Executors.newFixedThreadPool(10);
// Selector的包装类,可以直接返回EventItem集合
WrapperSelector wrapperSelector = WrapperSelector.open();
ServerSocketWrapper serverSocketWrapper = new ServerSocketWrapper();
// 将 serverSocket注册到 wrapperSelector
serverSocketWrapper.initAndregister(wrapperSelector);
for(;;){
// 取出就绪事件
Set<EventItem> eventItemSet = wrapperSelector.select();
// 调用就绪事件的回调函数
for(EventItem eventItem:eventItemSet){
// 判断是否为 accept 事件,下面的条件判断如此类推
if(eventItem.type.equals("accept")){
eventItem.acceptCallBack();
}
if(eventItem.type.equals("read")){
dispatcherPool.execute(new Runnable() {
@Override
public void run() {
eventItem.readCallBack();
}
});
}
if(eventItem.type.equals("write")){
dispatcherPool.execute(new Runnable() {
@Override
public void run() {
eventItem.writeCallBack();
}
});
}
}
}
这个伪代码的逻辑跟 单reactor线程模式 不同点在于,我在处理 read 和 write 事件的时候使用线程池。
三 主从模式:
主从模式可以说是netty 的线程模型了。主从模式可以这么来理解:
1 初始化主从两个线程池
2 主线程池负责处理接收事件
3 从线程池负责处理耗时的 write/read 等事件
我们继续来看图:
我们继续来看看伪代码:
// 主线程池
ExecutorService masterPool = Executors.newFixedThreadPool(10);
// 从线程池
ExecutorService slavePool = Executors.newFixedThreadPool(10);
ServerSocketWrapper serverSocketWrapper = new ServerSocketWrapper();
// 将 serverSocket注册到 masterSelector
serverSocketWrapper.initAndregister(masterSelector);
class EventItem{
public Object writeCallBack();
public Object readCallBack();
public Object acceptCallBack();
Event event;
String type;
}
// master线程池主要监听的 accept 事件,acceptCallBack 主要逻辑就是 将 返回的fd 注册到 salveSelector,并监听 write/read等事件
masterPool.execute(new Runnable() {
WrapperSelector masterSelector;
Set<WrapperSelector> salveSelectorSet;
@Override
public void run() {
for(;;){
Set<EventItem> eventItemSet = masterSelector.select();
if(eventItem.type.equals("accept")){
eventItem.acceptCallBack(salveSelectorSet.getOne());
}
}
}
});
// 从 线程池负责 处理耗时的业务
salveSelector.execute(new Runnable() {
WrapperSelector salveSelector;
@Override
public void run() {
for(;;){
Set<EventItem> eventItemSet = salveSelector.select();
if(eventItem.type.equals("read")){
eventItem.readCallBack();
}
if(eventItem.type.equals("write")){
eventItem.writeCallBack();
}
}
}
});
上面例子的 masterPool.execute 执行执行了一个线程,实际上可以 执行任意多个。
主要逻辑是:
1 线程池的线程的Selector是独立的
2 主线程池线程接收到 accept 事件,将对应的 fd 注册到从的 selector,并监听相应的事件
3 从线程池的线程负责处理耗时的任务
参考资源:
netty权威指南2
腾讯课堂零声学院