关于
我的项目
相关阅读
热度排行
- [转] 宫崎骏用动漫教给我们的人生哲理,每一句都能说到心里! - (日期:[八月 24, 2013] 点击:[53,222])
- Google 网页爬虫报告无法连接站点解决办法 - (日期:[七月 20, 2014] 点击:[38,641])
- 架设Tiny Tiny RSS(TTRSS)阅读器,找回Google Reader! - (日期:[九月 27, 2013] 点击:[27,769])
- SkyDrive、DropBox和Google Drive三大公有云存储服务对比 - (日期:[六月 25, 2013] 点击:[25,573])
- 升级到至强E5440后,与i5 CPU笔记本性能对比 - (日期:[二月 18, 2014] 点击:[23,713])
- 公钥私钥加密解密数字证书数字签名详解 - (日期:[四月 19, 2014] 点击:[22,959])
- 本站建站技术合集 - (日期:[九月 20, 2013] 点击:[22,490])
- 使用OpenerDNS解决无法访问Google的问题 - (日期:[七月 5, 2014] 点击:[21,789])
- WordPress博客添加“返回顶部”按钮 - (日期:[七月 14, 2013] 点击:[21,203])
- Linux文件系统基础之inode和dentry - (日期:[三月 13, 2015] 点击:[20,167])
- 云存储中的HTTP鉴权算法分析 - (日期:[二月 7, 2014] 点击:[18,639])
- 存储基础知识之——磁盘阵列原理及操作实战 - (日期:[二月 9, 2014] 点击:[17,491])
- 精选37条强大的常用linux shell命令组合 - (日期:[九月 4, 2013] 点击:[17,428])
- DNS原理、架构和配置详解 - (日期:[九月 6, 2013] 点击:[16,802])
- Netty和Jetty的Java NIO 网络框架模型分析 - (日期:[七月 13, 2013] 点击:[16,333])
- CoreOS 初识之安装 - (日期:[十一月 16, 2014] 点击:[16,170])
- Windows与Linux文件系统互访的几种方法 - (日期:[八月 21, 2014] 点击:[15,733])
- Dijkstra算法求解最短路径分析 - (日期:[七月 12, 2014] 点击:[14,924])
- NAS解决方案实现多媒体文件共享播放 - (日期:[十二月 21, 2014] 点击:[13,915])
- 简介 - (日期:[九月 1, 2012] 点击:[13,757])
- 如何编程实现 2 + 2 = 5? - (日期:[六月 2, 2014] 点击:[13,269])
- 搭建了一个iNews程序 - (日期:[十月 15, 2013] 点击:[13,236])
- 2014年9月曝出的Bash ShellShock漏洞简析 - (日期:[九月 26, 2014] 点击:[13,138])
- 彻底解决WordPress博客垃圾评论的问题 - (日期:[八月 5, 2013] 点击:[13,086])
- 如何使用1M的内存排序100万个8位数 - (日期:[三月 27, 2014] 点击:[12,552])
- 全部日志列表 - (日期:[十一月 11, 2012] 点击:[12,328])
- 关于回调函数和this指针探讨 - (日期:[八月 24, 2014] 点击:[12,209])
- 给定一个long型常量,其值为x,给定long型变量a,要求a & x 的取值集合 - (日期:[九月 8, 2012] 点击:[11,703])
- WordPress建站必备实用插件 - (日期:[八月 7, 2014] 点击:[11,360])
- Amazon 云计算业务全面介绍 - (日期:[三月 9, 2014] 点击:[11,268])
分类目录
文章归档
- 2024年四月 (1)
- 2024年二月 (1)
- 2023年九月 (1)
- 2023年一月 (1)
- 2022年十月 (1)
- 2022年八月 (2)
- 2022年四月 (1)
- 2022年三月 (1)
- 2021年十二月 (2)
- 2021年十月 (2)
- 2021年九月 (1)
- 2021年八月 (1)
- 2021年五月 (1)
- 2021年三月 (2)
- 2021年一月 (2)
- 2020年十二月 (5)
- 2020年十一月 (2)
- 2020年十月 (2)
- 2020年九月 (1)
- 2020年八月 (5)
- 2020年七月 (2)
- 2019年九月 (1)
- 2018年八月 (1)
- 2018年七月 (1)
- 2018年六月 (1)
- 2018年五月 (1)
- 2018年三月 (1)
- 2018年二月 (1)
- 2018年一月 (2)
- 2017年十二月 (3)
- 2017年十月 (4)
- 2017年九月 (1)
- 2017年七月 (1)
- 2017年六月 (1)
- 2016年十二月 (1)
- 2016年十月 (1)
- 2016年九月 (1)
- 2016年七月 (2)
- 2016年六月 (1)
- 2016年二月 (3)
- 2015年十二月 (3)
- 2015年十一月 (2)
- 2015年十月 (1)
- 2015年八月 (2)
- 2015年七月 (4)
- 2015年六月 (1)
- 2015年三月 (2)
- 2015年二月 (1)
- 2015年一月 (4)
- 2014年十二月 (2)
- 2014年十一月 (2)
- 2014年十月 (5)
- 2014年九月 (8)
- 2014年八月 (11)
- 2014年七月 (17)
- 2014年六月 (7)
- 2014年五月 (15)
- 2014年四月 (16)
- 2014年三月 (14)
- 2014年二月 (5)
- 2013年十二月 (5)
- 2013年十一月 (3)
- 2013年十月 (13)
- 2013年九月 (13)
- 2013年八月 (13)
- 2013年七月 (9)
- 2013年六月 (8)
- 2013年五月 (1)
- 2013年三月 (3)
- 2013年一月 (1)
- 2012年十一月 (1)
- 2012年九月 (12)
- 2012年八月 (3)
- 2011年二月 (1)
- 2009年三月 (1)
- 2009年二月 (1)
- 2008年十一月 (1)
- 2008年六月 (1)
- 2008年四月 (1)
- 2008年三月 (1)
Jetty线程“互锁”导致数据传输性能降低问题分析
以下分析针对jetty的特定版本:http://dev.eclipse.org/svnroot/rt/org.eclipse.jetty/jetty/tags/jetty-7.2.1.v20101111
首先介绍一下Jetty的反映器模型,Jetty用的经典的NIO异步模型(Scalable IO in Java http://gee.cs.oswego.edu/dl/cpjslides/nio.pdf)。连接管理的示意图如下:
Jetty在使用这个模型的时候,做了一些改动,acceptor是独立出来的一个阻塞线程,用于阻塞地接受新的连接请求,而所有的连接建立之后,都会想selector线程注册网络事件和内部的事件(changes),selector需要同时处理网络事件和内部的changes。同时还要定期检查超时的链接。
当一个连接建立之后,除了分发网络事件之外,主线程还会与子线程有一些交互。当子线程发现网络拥塞,缓冲区的数据无法及时刷走时,会注册一个表明自己处于阻塞状态的内部事件,并且期望主线程在发现拥塞解除的时候能通知到自己。
具体的代码如下:
/* ------------------------------------------------------------ */ /* * Allows thread to block waiting for further events. */ @Override public boolean blockWritable(long timeoutMs) throws IOException { synchronized (this) { long start=_selectSet.getNow(); try { _writeBlocked=true; while (isOpen() && _writeBlocked) { try { updateKey(); this.wait(timeoutMs); timeoutMs -= _selectSet.getNow()-start; if (_writeBlocked && timeoutMs<=0) return false; } catch (InterruptedException e) { Log.warn(e); } } } finally { _writeBlocked=false; if (_idleTimestamp!=-1) scheduleIdle(); } } return true; }
而主线程的select主循环的代码如下:
/* ------------------------------------------------------------ */ /** * Select and dispatch tasks found from changes and the selector. * * @throws IOException */ public void doSelect() throws IOException { try { _selecting=Thread.currentThread(); final Selector selector=_selector; // Make any key changes required Object change; int changes=_changes.size(); while (changes-->0 && (change=_changes.poll())!=null) { try { if (change instanceof EndPoint) { // Update the operations for a key. SelectChannelEndPoint endpoint = (SelectChannelEndPoint)change; endpoint.doUpdateKey(); } else if (change instanceof ChannelAndAttachment) { // finish accepting/connecting this connection final ChannelAndAttachment asc = (ChannelAndAttachment)change; final SelectableChannel channel=asc._channel; final Object att = asc._attachment; SelectionKey key = channel.register(selector,SelectionKey.OP_READ,att); SelectChannelEndPoint endpoint = createEndPoint((SocketChannel)channel,key); key.attach(endpoint); endpoint.schedule(); } else if (change instanceof SocketChannel) { // Newly registered channel final SocketChannel channel=(SocketChannel)change; SelectionKey key = channel.register(selector,SelectionKey.OP_READ,null); SelectChannelEndPoint endpoint = createEndPoint(channel,key); key.attach(endpoint); endpoint.schedule(); } else if (change instanceof Runnable) { dispatch((Runnable)change); } else throw new IllegalArgumentException(change.toString()); } catch (Exception e) { if (isRunning()) Log.warn(e); else Log.debug(e); } catch (Error e) { if (isRunning()) Log.warn(e); else Log.debug(e); } } // Do and instant select to see if any connections can be handled. int selected=selector.selectNow(); _selects++; long now=System.currentTimeMillis(); // if no immediate things to do if (selected==0) { // If we are in pausing mode if (_pausing) { try { Thread.sleep(__BUSY_PAUSE); // pause to reduce impact of busy loop } catch(InterruptedException e) { Log.ignore(e); } now=System.currentTimeMillis(); } // workout how long to wait in select _timeout.setNow(now); long to_next_timeout=_timeout.getTimeToNext(); long wait = _changes.size()==0?__IDLE_TICK:0L; if (wait > 0 && to_next_timeout >= 0 && wait > to_next_timeout) wait = to_next_timeout; // If we should wait with a select if (wait>0) { long before=now; selected=selector.select(wait); _selects++; now = System.currentTimeMillis(); _timeout.setNow(now); checkJvmBugs(before, now, wait, selected); } } // have we been destroyed while sleeping if (_selector==null || !selector.isOpen()) return; // Look for things to do for (SelectionKey key: selector.selectedKeys()) { try { if (!key.isValid()) { key.cancel(); SelectChannelEndPoint endpoint = (SelectChannelEndPoint)key.attachment(); if (endpoint != null) endpoint.doUpdateKey(); continue; } Object att = key.attachment(); if (att instanceof SelectChannelEndPoint) { ((SelectChannelEndPoint)att).schedule(); } else { // Wrap readable registered channel in an endpoint SocketChannel channel = (SocketChannel)key.channel(); SelectChannelEndPoint endpoint = createEndPoint(channel,key); key.attach(endpoint); if (key.isReadable()) endpoint.schedule(); } key = null; } catch (CancelledKeyException e) { Log.ignore(e); } catch (Exception e) { if (isRunning()) Log.warn(e); else Log.ignore(e); if (key != null && !(key.channel() instanceof ServerSocketChannel) && key.isValid()) key.cancel(); } } // Everything always handled selector.selectedKeys().clear(); now=System.currentTimeMillis(); _timeout.setNow(now); Task task = _timeout.expired(); while (task!=null) { if (task instanceof Runnable) dispatch((Runnable)task); task = _timeout.expired(); } // Idle tick if (now-_idleTick>__IDLE_TICK) { _idleTick=now; final long idle_now=((_lowResourcesConnections>0 && selector.keys().size()>_lowResourcesConnections)) ?(now+_maxIdleTime-_lowResourcesMaxIdleTime) :now; dispatch(new Runnable() { public void run() { for (SelectChannelEndPoint endp:_endPoints.keySet()) { endp.checkIdleTimestamp(idle_now); } } }); } } catch (CancelledKeyException e) { Log.ignore(e); } finally { _selecting=null; } } /* ------------------------------------------------------------ */ private void checkJvmBugs(long before, long now, long wait, int selected) throws IOException { Selector selector = _selector; if (selector==null) return; // Look for JVM bugs over a monitor period. // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6403933 // http://bugs.sun.com/view_bug.do?bug_id=6693490 if (now>_monitorNext) { _selects=(int)(_selects*__MONITOR_PERIOD/(now-_monitorStart)); _pausing=_selects>__MAX_SELECTS; if (_pausing) _paused++; _selects=0; _jvmBug=0; _monitorStart=now; _monitorNext=now+__MONITOR_PERIOD; } if (now>_log) { if (_paused>0) Log.debug(this+" Busy selector - injecting delay "+_paused+" times"); if (_jvmFix2>0) Log.debug(this+" JVM BUG(s) - injecting delay"+_jvmFix2+" times"); if (_jvmFix1>0) Log.debug(this+" JVM BUG(s) - recreating selector "+_jvmFix1+" times, cancelled keys "+_jvmFix0+" times"); else if(Log.isDebugEnabled() && _jvmFix0>0) Log.debug(this+" JVM BUG(s) - cancelled keys "+_jvmFix0+" times"); _paused=0; _jvmFix2=0; _jvmFix1=0; _jvmFix0=0; _log=now+60000; } // If we see signature of possible JVM bug, increment count. if (selected==0 && wait>10 && (now-before)<(wait/2)) { // Increment bug count and try a work around _jvmBug++; if (_jvmBug>(__JVMBUG_THRESHHOLD)) { try { if (_jvmBug==__JVMBUG_THRESHHOLD+1) _jvmFix2++; Thread.sleep(__BUSY_PAUSE); // pause to avoid busy loop } catch(InterruptedException e) { Log.ignore(e); } } else if (_jvmBug==__JVMBUG_THRESHHOLD) { synchronized (this) { // BLOODY SUN BUG !!! Try refreshing the entire selector. final Selector new_selector = Selector.open(); for (SelectionKey k: selector.keys()) { if (!k.isValid() || k.interestOps()==0) continue; final SelectableChannel channel = k.channel(); final Object attachment = k.attachment(); if (attachment==null) addChange(channel); else addChange(channel,attachment); } _selector.close(); _selector=new_selector; return; } } else if (_jvmBug%32==31) // heuristic attempt to cancel key 31,63,95,... loops { // Cancel keys with 0 interested ops int cancelled=0; for (SelectionKey k: selector.keys()) { if (k.isValid()&&k.interestOps()==0) { k.cancel(); cancelled++; } } if (cancelled>0) _jvmFix0++; return; } } else if (__BUSY_KEY>0 && selected==1 && _selects>__MAX_SELECTS) { // Look for busy key SelectionKey busy = selector.selectedKeys().iterator().next(); if (busy==_busyKey) { if (++_busyKeyCount>__BUSY_KEY && !(busy.channel() instanceof ServerSocketChannel)) { final SelectChannelEndPoint endpoint = (SelectChannelEndPoint)busy.attachment(); Log.warn("Busy Key "+busy.channel()+" "+endpoint); busy.cancel(); if (endpoint!=null) { dispatch(new Runnable() { public void run() { try { endpoint.close(); } catch (IOException e) { Log.ignore(e); } } }); } } } else _busyKeyCount=0; _busyKey=busy; } }
在checkjvmbugs的方法中,当主线程正在阻塞selector.select(wait)时,子线程出现blockwrite的情况,会导致主线程被惊醒,使得主线程select到的事件数为0,当selector管理大量的连接时,会出现1秒内上百次主线程被惊醒,最终在checkjvmbugs方法内被当作是jvmbug来处理了,强制让主线程休眠50毫秒,而此时还有大量的线程注册阻塞事件,并等待主线程的唤醒。最终一个12K的数据花费了50ms的时间来写往客户端,最终导致性能下降。
问题已经很清晰,jetty自身的阻塞-唤醒机制被当作jvmbugs来处理,导致数据传输性能受影响,这样的情况也只有在有大量连接和数据传输的时候才会体现出来。
修改方案,可以将checkjvmbugs调用直接注释掉,保留每秒select 25000次后休眠一段时间的实现即可。也可以将50毫秒的休眠时间调短来避免阻塞时间过长的问题。
分析最新的jetty代码,发现这个checkjvmbugs的调用已经去掉了,正是按照方案1来实现的。
问题明确了之后,看似比较简单,但实际上发现问题的过程是很漫长和曲折的,在问题已经锁定在上面之后,有幸参与最终的定位并且分析出了具体的问题点,问题找到的那一刻,有一种兴奋感与成就感。