扫二维码与项目经理沟通
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流
一、什么是Netty
网站建设哪家好,找创新互联公司!专注于网页设计、网站建设、微信开发、成都小程序开发、集团企业网站建设等服务项目。为回馈新老客户创新互联还提供了淮滨免费建站欢迎大家使用!
Netty是一个高性能 事件驱动、异步非堵塞的IO(NIO)Java开源框架,Jboss提供,用于建立TCP等底层的连接,基于Netty可以建立高性能的Http服务器,快速开发高性能、高可靠性的网络服务器和客户端程序。支持HTTP、 WebSocket 、Protobuf、 Binary TCP |和UDP,Netty已经被很多高性能项目作为其Socket底层基础,如HornetQ Infinispan Vert.x Play Framework Finangle和 Cassandra。其竞争对手是:Apache MINA和 Grizzly。
也就是说,Netty 是一个基于NIO的客户,服务器端编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户,服务端应用。Netty相当简化和流线化了网络应用的编程开发过程,例如,TCP和UDP的socket服务开发。
“快速”和“简单”并不意味着会让你的最终应用产生维护性或性能上的问题。Netty 是一个吸收了多种协议的实现经验,这些协议包括FTP,SMTP,HTTP,各种二进制,文本协议,并经过相当精心设计的项目,最终,Netty 成功的找到了一种方式,在保证易于开发的同时还保证了其应用的性能,稳定性和伸缩性。
二、不选择Java原生NIO编程的原因
首先开发出高质量的NIO程序并不是一件简单的事情,除去NIO固有的复杂性和BUG不谈,作为一个NIO服务端,还需要能够处理网络的闪断、客户端的重复接入、客户端的安全认证、消息的编解码、半包读写等情况,如果你没有足够的NIO编程经验积累,一个NIO框架的稳定往往需要半年甚至更长的时间。更为糟糕的是,一旦在生产环境中发生问题,往往会导致跨节点的服务调用中断,严重的可能会导致整个集群环境都不可用,需要重启服务器,这种非正常停机会带来巨大的损失。
从可维护性角度看,由于NIO采用了异步非阻塞编程模型,而且是一个I/O线程处理多条链路,它的调试和跟踪非常麻烦,特别是生产环境中的问题,我们无法进行有效的调试和跟踪,往往只能靠一些日志来辅助分析,定位难度很大。
现在我们总结一下为什么不建议开发者直接使用JDK的NIO类库进行开发,具体原因如下。
1)跨平台与兼容性:NIO算是底层的APIs需依赖系统的IO APIs。但Java NIO发现在不同系统平台会出现问题。大量测试也耗不少时间;NIO2只支持JDK1.7+,而且没提供DatagramSocket,故NIO2不支持UDP协议。而Netty提供统一接口,同一语句无论在JDK6.X 还是JDK7.X 都可运行,无需关心底层架构功能!
2)JAVA NIO的ByteBuffer构造函数私有,无法扩展。Netty提供了自己的ByteBuffer实现,通过简单APIs对其进行构造、使用和操作,一此解决NIO的一些限制。
3)NIO对缓冲区的聚合与分散操作可能会导致内存泄漏。直到JDK1.7才解决此问题。
4)NIO的类库和API繁杂,使用麻烦,你需要熟练掌握Selector、ServerSocketChannel、SocketChannel、ByteBuffer等。
5)使用JAVA NIO需要具备其他的额外技能做铺垫,例如熟悉Java多线程编程。这是因为NIO编程涉及到Reactor模式,你必须对多线程和网路编程非常熟悉,才能编写出高质量的NIO程序。
6)可靠性能力补齐,工作量和难度都非常大。例如客户端面临断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异常码流的处理等问题。
7)JDK NIO的BUG,例如臭名昭著的epoll bug,它会导致Selector空轮询,最终导致CPU 100%。官方声称在JDK 1.6版本的update18修复了该问题,但是直到JDK 1.7版本该问题仍旧存在,只不过该BUG发生概率降低了一些而已,它并没有得到根本性解决。该BUG以及与该BUG相关的问题单可以参见以下链接内容。
异常堆栈如下。
java.lang.Thread.State: RUNNABLE
at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)
at sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.java:210)
at sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:65)
at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:69)
- locked 0x0000000750928190 (a sun.nio.ch.Util$2)
- locked 0x00000007509281a8 (a java.util.Collections$ UnmodifiableSet)
- locked 0x0000000750946098 (a sun.nio.ch.EPollSelectorImpl)
at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:80)
at net.spy.memcached.MemcachedConnection.handleIO(Memcached Connection.java:217)
at net.spy.memcached.MemcachedConnection.run(MemcachedConnection. java:836)
由于上述原因,在大多数场景下,不建议大家直接使用JDK的NIO类库,除非你精通NIO编程或者有特殊的需求。在绝大多数的业务场景中,我们可以使用NIO框架Netty来进行NIO编程,它既可以作为客户端也可以作为服务端,同时支持UDP和异步文件传输,功能非常强大。
Executor workerExecutor = Executors.newCachedThreadPool();
Executor bossExecutor = Executors.newCachedThreadPool();
ServerBootstrap server = new ServerBootstrap(new NioServerSocketChannelFactory(bossExecutor, workerExecutor));
server.setPipelineFactory(new ChannelPipelineFactory() {
@Override
public ChannelPipeline getPipeline() throws Exception {
ChannelPipeline p = Channels.pipeline();
p.addLast("logging", new LoggingHandler(InternalLogLevel.INFO));
p.addLast("decoder", new Decoder());
p.addLast("handler", new Netty3Handler());
return p;
}
});
server.bind(new InetSocketAddress("127.0.0.1", 9999));
根据我们前面分析的,接收到消息后,为了避免在I/O线程里执行耗时的操作,一般都会使用线程池来执行业务处理逻辑.
那是使用Netty提供给我们的方法,传入一个线程池还是使用我们自己定义的线程池好呢?
先来看Netty给我们提供的
即我们添加handler的时候可以传入一个线程池进去
DefaultEventExecutorGroup
它与NioEventLoop之间的区别又是什么?
其次
也就是说使用netty提供默认的,是绑定的.如下图
如果采用自定义线程池时,优化方向就是锁消除.
可以使用Disruptor或者使用ChannelId与业务线程池中的某个业务进行绑定
链接:
用while语句求 。
用传统流程图和N-S结构流程图表示算法,见图:
main()
{
int i,sum=0;
i=1;
while(i=100)
{
sum=sum+i;
i++;
}
printf("%d\n",sum);
}
NioEventLoop的事件循环处理,就是在一个死循环中处理IO事件和队列里的任务,并且可以根据策略来平衡这两者之间的执行比例。
首先,先来看下selectStrategy,netty中只有一个默认实现
这个策略,若是当前有任务,那么返回selectNow()方法的返回值,若是没有任务,则返回SelectStrategy.SELECT(-1)。
因此接下来的swtich语句块中只会有一种情况,就是值为-1时,表示没有任务。但是并不是就进入无限的阻塞状态select()方法中,还会判断队列是否有定时任务要执行,若有,则计算到下一次定时任务的时间间隔,并传给select()方法中,表示超时时间,这个是为了防止一直在select等待,而没有及时的执行定时任务。
这个超时时间还会设置到原子变量nextWakeupNanos中,这样应用程序就可以通过nextWakeupNanos获取到下一次线程唤醒的时间。当线程唤醒后,程序finally会执行nextWakeupNanos.lazySet(AWAKE),表示线程目前是唤醒状态。这个变量的主要作用是当线程阻塞在select方法时,而此时又有任务提交给这个NioEventLoop执行时
唤醒selector时,会先判断inEventLoop,因为若是inEventLoop,就是目前的任务正在被NioEventLoop的线程执行,并没有阻塞在selector的select方法,还有会对nextWakeupNanos的值设置为AWAKE唤醒状态,若该变量值之前就是唤醒的,那么也不会唤醒selector。
现在,把流程又回到刚刚的事件循环run方法中,当select方法返回后,要执行selectKeys和任务时,会先判断ioRatio这个参数,这个表示的是在当前循环中处理IO事件的时间与任务的比例
在每次的循环最后,会判断NioEventLoop是否shutdown了,若关闭了,则将Selector上的key都cancel,并关闭channel。
链接:
实现单机的百万连接,瓶颈有以下几点:
1、如何模拟百万连接
2、突破局部文件句柄的限制
3、突破全局文件句柄的限制
在linux系统里面,单个进程打开的句柄数是非常有限的,一条TCP连接就对应一个文件句柄,而对于我们应用程序来说,一个服务端默认建立的连接数是有限制的。
下面通过优化要突破这个连接数。
优化
1、局部文件句柄限制
一个jvm进程最大能够打开的文件数.png
修改65535的这个限制
vi /etc/security/limits.conf
在文件末尾添加两行
*hard nofile 1000000
soft nofile 1000000
soft和hard为两种限制方式,其中soft表示警告的限制,hard表示真正限制,nofile表示打开的最大文件数。整体表示任何用户一个进程能够打开1000000个文件。注意语句签名有
号 表示任何用户
shutdown -r now 重启linux
再次查看
已经修改生效了。
测试
最大连接数10万多.png
2、突破全局文件句柄的限制
cat /proc/sys/fs/file-max
file-max 表示在linux 中最终所有x线程能够打开的最大文件数
修改这个最大值:
sudo vi /etc/sysctl.conf
在文件的末尾添加 fs.file-max=1000000
然后让文件生效 sudo sysctl -p
这个时候再查看一下全局最大文件句柄的数已经变成1000000了
测试
最大连接数36万多.png
注: 测试的服务器型号
cpu 相关配置
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流