NioEventLoop 是jdk nio多路处理实现同修复jdk nio的bug

1.NioEventLoop继承SingleThreadEventLoop 重用单线程处理

2.NioEventLoop是组成 pool EventLoopGroup 基本单元

总之好多边界判断跟业务经验之类的代码,非常烦碎

重要属性

public final class NioEventLoop extends SingleThreadEventLoop {
//绑定 selector
Selector selector;
//优化过的Set集合
private SelectedSelectionKeySet selectedKeys;
//引用全局 SelectorProvider
private final SelectorProvider provider;
///////////////////////////////////////////
//为true时执行selector.wakeup()
private final AtomicBoolean wakenUp = new AtomicBoolean();
//io任务占时比率
private volatile int ioRatio = 50;
//记录selectionKey撤销次数
private int cancelledKeys;
//处理selector.selectNow() 标志
private boolean needsToSelectAgain;
}

替换Selector selectedKeySet字段与重构Selector

优化selectedKeySet集合用的是double cache技术,这种技术在图形渲染处理比较多

    //netty 用到反射加 AccessController技术替换掉 Selector selectedKeySet 字段
private Selector openSelector() {
final Selector selector = provider.openSelector();
final SelectedSelectionKeySet selectedKeySet = new SelectedSelectionKeySet(); Object maybeSelectorImplClass = AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
try {
return Class.forName(
"sun.nio.ch.SelectorImpl",
false,
PlatformDependent.getSystemClassLoader());
} catch (Throwable cause) {
return cause;
}
}
}); final Class<?> selectorImplClass = (Class<?>) maybeSelectorImplClass;
Object maybeException = AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
//用到反射技术更改 SelectorImpl 字段
Field selectedKeysField = selectorImplClass.getDeclaredField("selectedKeys");
Field publicSelectedKeysField = selectorImplClass.getDeclaredField("publicSelectedKeys");
selectedKeysField.setAccessible(true);
publicSelectedKeysField.setAccessible(true); selectedKeysField.set(selector, selectedKeySet);
publicSelectedKeysField.set(selector, selectedKeySet);
return null;
}
}); return selector;
} //重新构建Selector
private void rebuildSelector0() {
final Selector oldSelector = selector;
final Selector newSelector; if (oldSelector == null) {
return;
} newSelector = openSelector(); //迁移处理
int nChannels = 0;
for (SelectionKey key: oldSelector.keys()) {
Object a = key.attachment();
try {
//过滤key是否合法 已处理
if (!key.isValid() || key.channel().keyFor(newSelector) != null) {
continue;
}
int interestOps = key.interestOps();
key.cancel();
SelectionKey newKey = key.channel().register(newSelector, interestOps, a);
if (a instanceof AbstractNioChannel) {
// channel重新绑定SelectionKey
((AbstractNioChannel) a).selectionKey = newKey;
}
nChannels ++;
} catch (Exception e) {
//出错处理 netty认为 socket已关闭
if (a instanceof AbstractNioChannel) {
AbstractNioChannel ch = (AbstractNioChannel) a;
ch.unsafe().close(ch.unsafe().voidPromise());
} else {
@SuppressWarnings("unchecked")
NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
invokeChannelUnregistered(task, key, e);
}
}
}
selector = newSelector;
oldSelector.close();
}

double cache 实现

final class SelectedSelectionKeySet extends AbstractSet<SelectionKey> {

        private SelectionKey[] keysA;
private int keysASize;
private SelectionKey[] keysB;
private int keysBSize;
private boolean isA = true; SelectedSelectionKeySet() {
keysA = new SelectionKey[1024];
keysB = keysA.clone();
} @Override
public boolean add(SelectionKey o) {
if (o == null) {
return false;
}
//是A开关即处理A
if (isA) {
int size = keysASize;
keysA[size ++] = o;
keysASize = size;
//双倍扩展容量
if (size == keysA.length) {
doubleCapacityA();
}
} else {
int size = keysBSize;
keysB[size ++] = o;
keysBSize = size;
if (size == keysB.length) {
doubleCapacityB();
}
} return true;
} private void doubleCapacityA() {
SelectionKey[] newKeysA = new SelectionKey[keysA.length << 1];
System.arraycopy(keysA, 0, newKeysA, 0, keysASize);
keysA = newKeysA;
} private void doubleCapacityB() {
SelectionKey[] newKeysB = new SelectionKey[keysB.length << 1];
System.arraycopy(keysB, 0, newKeysB, 0, keysBSize);
keysB = newKeysB;
}
//获取keys并切换
SelectionKey[] flip() {
if (isA) {
isA = false;
keysA[keysASize] = null;
keysBSize = 0;
return keysA;
} else {
isA = true;
keysB[keysBSize] = null;
keysASize = 0;
return keysB;
}
} @Override
public int size() {
return isA?keysASize : keysBSize;
}
}

重载Selector select 逻辑,修复jdk 会产生的 bug

private void select(boolean oldWakenUp) throws IOException {
Selector selector = this.selector; int selectCnt = 0;
long currentTimeNanos = System.nanoTime();
//通过delayNanos计算出 select结束时间
long selectDeadLineNanos = currentTimeNanos + delayNanos(currentTimeNanos);
for (;;) {
//计算出超时并转换成毫秒,再加上延时固定0.5毫秒
long timeoutMillis = (selectDeadLineNanos - currentTimeNanos + 500000L) / 1000000L;
if (timeoutMillis <= 0) {
if (selectCnt == 0) {
selector.selectNow();
selectCnt = 1;
}
break;
} //如果有非IO任务,优先等侍selector操作
if (hasTasks() && wakenUp.compareAndSet(false, true)) {
selector.selectNow();
selectCnt = 1;
break;
}
//阻塞当前线程
int selectedKeys = selector.select(timeoutMillis);
selectCnt ++;
//有IO,非IO,计划任务,wakenUp状态认为已完成 select 处理
if (selectedKeys != 0 || oldWakenUp || wakenUp.get() || hasTasks() || hasScheduledTasks()) {
break;
}
//如果当前线程中断,netty认为关闭了服务,退出处理
if (Thread.interrupted()) {
selectCnt = 1;
break;
} //相当于下面等价,意思是当前时间大于或等于 (selectDeadLineNanos + 0.5毫秒) selectCnt 重置
//currentTimeNanos + (System.nanoTime() - selectDeadLineNanos - 500000L ) >= currentTimeNanos
//System.nanoTime() - selectDeadLineNanos - 500000L >= 0
//System.nanoTime() >= selectDeadLineNanos + 500000L
long time = System.nanoTime();
if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) {
selectCnt = 1;
} else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 &&
selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) { // selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD 默认值512,重构selector
rebuildSelector();
selector = this.selector;
selector.selectNow();
selectCnt = 1;
break;
}
//刷新当前时间
currentTimeNanos = time;
} }

分发io与非io任务逻辑实现

//这部分做了代码整理
@Override
protected void run() {
for (;;) {
try {
//检查是否有非IO任务同WAKEUP_TASK任务
if(!hasTasks()){
continue;
}
//有任务就触发重写的 select
select(wakenUp.getAndSet(false));
if (wakenUp.get()) {
selector.wakeup();
} cancelledKeys = 0;
needsToSelectAgain = false;
final int ioRatio = this.ioRatio;//默认值50 try {
final long ioStartTime = System.nanoTime();
//processSelectedKeys();
//一般会selectedKeys不会为null做了优化处理
if (selectedKeys != null) {
processSelectedKeysOptimized(selectedKeys.flip());
} else {
processSelectedKeysPlain(selector.selectedKeys());
}
} finally {
//当ioRatio等于100时,百分百执行非IO全部任务
if (ioRatio == 100) {
runAllTasks();
}else{
final long ioTime = System.nanoTime() - ioStartTime;
//计算时非IO任务超时时间,公式 = 100 - ioRatio 算出非IO比率再跟IO相比 执行过的IO时间 * (非IO:IO)
runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
}
}
} catch (Throwable t) {
//防止过多失败
Thread.sleep(1000);
} //处理完任务判断是否结束
try {
if (isShuttingDown()) {
closeAll();
if (confirmShutdown()) {
return;
}
}
} catch (Throwable t) {
Thread.sleep(1000);
}
}
}
private void processSelectedKeysOptimized(SelectionKey[] selectedKeys) {
for (int i = 0;; i ++) {
final SelectionKey k = selectedKeys[i];
if (k == null) {
break;
}
//依赖外部逻辑清理
selectedKeys[i] = null;
final Object a = k.attachment(); //处理SelectedKey
if (a instanceof AbstractNioChannel) {
processSelectedKey(k, (AbstractNioChannel) a);
} else {
@SuppressWarnings("unchecked")
NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
processSelectedKey(k, task);
}
//这里用到比较奇怪的处理,应该是个补丁来的。。。
//从资料来源上说:当触发needsToSelectAgain时 channel全是关闭,所以忽略selectedKeys剩余的key,然后再重获取获取selectedKeys
// null out entries in the array to allow to have it GC'ed once the Channel close
// See https://github.com/netty/netty/issues/2363
if (needsToSelectAgain) {
for (;;) {
i++;
if (selectedKeys[i] == null) {
break;
}
selectedKeys[i] = null;
} selectAgain();
selectedKeys = this.selectedKeys.flip();
i = -1;
}
}
}
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
if (!k.isValid()) {
final EventLoop eventLoop;
try {
eventLoop = ch.eventLoop();
} catch (Throwable ignored) {
return;
}
//这里忽略情况是 在执行 registerd deregistration 时不能关闭,至于前后顺序无需要太多关心,读者可以进去看看
//每个人出现情况不一样,再加上eventLoop不可能为null的,这段代码明显没有经过测试
// Only close ch if ch is still registerd to this EventLoop. ch could have deregistered from the event loop
// and thus the SelectionKey could be cancelled as part of the deregistration process, but the channel is
// still healthy and should not be closed.
// See https://github.com/netty/netty/issues/5125
if (eventLoop != this || eventLoop == null) {
return;
}
unsafe.close(unsafe.voidPromise());
return;
} try {
int readyOps = k.readyOps();
// 如果出现OP_CONNECT 状态必须先完成Connect 才能触发 read or wirte 操作
if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
//清除SelectionKey.OP_CONNECT状态
int ops = k.interestOps();
ops &= ~SelectionKey.OP_CONNECT;
k.interestOps(ops); unsafe.finishConnect();
} //ByteBuffer 发送出去
if ((readyOps & SelectionKey.OP_WRITE) != 0) {
ch.unsafe().forceFlush();
} //netty将OP_READ,OP_ACCEPT 状态统一执行read操作,那netty如何区分 read accept的呢,后面才分析
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
unsafe.read();
}
} catch (CancelledKeyException ignored) {
unsafe.close(unsafe.voidPromise());
}
} //处理任务,失败策略执行注销处理
private static void processSelectedKey(SelectionKey k, NioTask<SelectableChannel> task) {
try {
task.channelReady(k.channel(), k);
if (!k.isValid()) {
task.channelUnregistered(k.channel(), null);
}
} catch (Exception e) {
k.cancel();
task.channelUnregistered(k.channel(), null);
}
}

总结:

1.防cpu假死,超过一定时间重建Selector迁移SelectionKey

2.用反射技术替换Selector selectedKeySet字段,Set集合用到double cache技术

3.优先处理io任务,剩下时间处理非IO任务,通过ioRatio占比分配执行时间

4.在分发IO任务时做了大量的优化处理,如线程中断,读写IO、链路建立处理优先级,Selector 重建情况等

5.逻辑有时看起来好怪,再加上解决问题是修修补补的没经过优化代码,甚至作者没有经过测试就合并了,这是开源框架的通病

[编织消息框架][netty源码分析]4 eventLoop 实现类NioEventLoop职责与实现的更多相关文章

  1. [编织消息框架][netty源码分析]5 eventLoop 实现类NioEventLoopGroup职责与实现

    分析NioEventLoopGroup最主有两个疑问 1.next work如何分配NioEventLoop 2.boss group 与child group 是如何协作运行的 从EventLoop ...

  2. [编织消息框架][netty源码分析]3 EventLoop 实现类SingleThreadEventLoop职责与实现

    eventLoop是基于事件系统机制,主要技术由线程池同队列组成,是由生产/消费者模型设计,那么先搞清楚谁是生产者,消费者内容 SingleThreadEventLoop 实现 public abst ...

  3. [编织消息框架][netty源码分析]6 ChannelPipeline 实现类DefaultChannelPipeline职责与实现

    ChannelPipeline 负责channel数据进出处理,如数据编解码等.采用拦截思想设计,经过A handler处理后接着交给next handler ChannelPipeline 并不是直 ...

  4. [编织消息框架][netty源码分析]11 ByteBuf 实现类UnpooledHeapByteBuf职责与实现

    每种ByteBuf都有相应的分配器ByteBufAllocator,类似工厂模式.我们先学习UnpooledHeapByteBuf与其对应的分配器UnpooledByteBufAllocator 如何 ...

  5. [编织消息框架][netty源码分析]8 Channel 实现类NioSocketChannel职责与实现

    Unsafe是托委访问socket,那么Channel是直接提供给开发者使用的 Channel 主要有两个实现 NioServerSocketChannel同NioSocketChannel 致于其它 ...

  6. [编织消息框架][netty源码分析]9 Promise 实现类DefaultPromise职责与实现

    netty Future是基于jdk Future扩展,以监听完成任务触发执行Promise是对Future修改任务数据DefaultPromise是重要的模板类,其它不同类型实现基本是一层简单的包装 ...

  7. [编织消息框架][netty源码分析]5 EventLoopGroup 实现类NioEventLoopGroup职责与实现

    分析NioEventLoopGroup最主有两个疑问 1.next work如何分配NioEventLoop 2.boss group 与child group 是如何协作运行的 从EventLoop ...

  8. [编织消息框架][netty源码分析]7 Unsafe 实现类NioSocketChannelUnsafe职责与实现

    Unsafe 是channel的内部接口,从书写跟命名上看是不公开给开发者使用的,直到最后实现NioSocketChannelUnsafe也没有公开出去 public interface Channe ...

  9. [编织消息框架][netty源码分析]13 ByteBuf 实现类CompositeByteBuf职责与实现

    public class CompositeByteBuf extends AbstractReferenceCountedByteBuf implements Iterable<ByteBuf ...

随机推荐

  1. centos系统修改网络配置注意事项

    这也是无意之中发现的,我在做一个远程修改工控机网络配置的程序, 网络配置参数/etc/sysconfig/network-scripts/ifcfg-enp1s0下面,当然名字可能不一样ifcfg-e ...

  2. Java AOP (1) compile time weaving 【Java 切面编程 (1) 编译期织入】

    According to wikipedia  aspect-oriented programming (AOP) is a programming paradigm that aims to inc ...

  3. 你跟上技术趋势了么? 来看看这10场2017热门it技术会议!

    2016年各类大会让人应接不暇,技术圈儿最不缺的就是各种大会小会,有的纯干货,有的纯广告.作为一名技术开发者,参加了几场大会,你是不是也开始思忖:究竟哪些会议才值得参加?下面活动家为你推荐几场2017 ...

  4. WPF 自定义ColorDialog DropDownCustomColorPicker

    今天分享一个 WPF 版的ColorDialog,该控件源自 这里,不过笔者已经该控件做了大量的修改工作,以适应自己的产品需求,闲话少说,先看看效果图: 1.DropDownCustomColorPi ...

  5. 关于cisco ccp 或sdm管理gns3中思科路由器的成功分享

    本来工作环境中有一台c1841,闲来无事,升级了最新的IOS=c1841-adventerprisek9-mz.151-4.M6.bin,在xp虚拟机中安装sdm(新windows系统不支持)和在wi ...

  6. hdu2717Catch That Cow 简单BFS

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2717 刚开始思路错了,用的DP,一直WA,后来才发现是搜索,还是简单的BFS,顿时.... 思路: B ...

  7. weblogic java.lang.OutOfMemoryError: PermGen space 问题解决方法

    文章转自:http://blog.csdn.net/cuihaiyang/article/details/6679735 最近安装了WebLogic10.3.4,测试在weblogic上部署项目,没过 ...

  8. JEESZ-kafka消息服务平台实现

    JEESZ的消息服务平台已经抛弃了之前的ActiveMQ,改用高吞吐量比较大的Kafka分布式消息中间件方案:JEESZ-kafka消息平台使用spring+kafka的集成方案,详情如下:1. 使用 ...

  9. nmon指标

    表字段分析 关键指标类型 关键指标名称 关键指标含义 SYS_SUMM CPU% cpu占有率变化情况: IO/sec IO的变化情况: AAA AIX AIX版本号: cpus CPU数量: har ...

  10. Day1-模块初识

    模块,也叫库,分为标准库和第三方库.标准库,直接导入使用,比如import getpass:第三方库,需下载安装才能使用,比如paramiko: 一.sys模块 import sys print(sy ...