热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

用Netty开发中间件:网络编程基础

用Netty开发中间件:网络编程基础《Netty权威指南》在网上的评价不是非常高,尤其是第一版,第二版能稍好些?入手后高速翻看了大半本,不免还是想对《Netty权威指南(第二版)》

用Netty开发中间件:网络编程基础

《Netty权威指南》在网上的评价不是非常高,尤其是第一版,第二版能稍好些?入手后高速翻看了大半本,不免还是想对《Netty权威指南(第二版)》吐槽一下:

  • 前半本的代码排版太糟糕了,简直就是直接打印Word的版式似的。

    源代码解析部分的条理性和代码排版好多了,感觉比其它部分的质量高多了。

  • 假设你是刚開始学习的人可能会感觉非常具体,差点儿每部分都会来一套client和服务端的Demo。假设你不是入门者的话可能会感觉水分比較多。
  • 最后一部分高级特性。内容有些混乱,不少内容都在不同的章节里反复了好几遍。

不管如何,假设你是网络通信或后台中间件的入门者。尤其是Java程序猿,那么这本书还是值得入手的。尤其是书中对I/O模型、协议解析、可靠性等方面的点拨还是会让你有非常多收获的。好了吐槽就到这了,下面就是《Netty权威指南(第二版)》的重点摘录,抽掉了水分,全部干货都在这里了。

1.Linux和Java的I/O演进之路

Linux从select -> poll -> epoll机制。简要说epoll的长处就是:从主动轮询+线性扫描变为被动事件通知,mmap避免到用户态的拷贝,更加简单的API。

Java方面呢,JDK 1.3之前仅仅有堵塞I/O,到1.4增加了NIO。

在JDK 1.5 update 10和Linux 2.6以上版本号。JDK使用epoll替换了select/poll。1.7增加了AIO。

2.四种I/O模型

2.1 堵塞BIO

堵塞BIO是我们最常见的一种形式。就不具体说了。

2.2 伪异步I/O

伪异步I/O利用堵塞I/O的Acceptor+线程池实现的是伪异步I/O。它仅仅是对同步堵塞I/O在系统资源方面使用方面做了“一小点”的优化(重用了线程)。可是 它没法从根本上解决同步I/O导致的通信线程堵塞问题

TCP/IP知识复习:当消息接收方处理缓慢时,将不能及时从TCP缓冲区读取数据,这将会导致发送方的TCP window size不断变小直到为0。

此时两方处于Keep-Alive状态,发送方将不能再向TCP缓冲区写入消息。

假设使用的是同步堵塞I/O,write操作将无限期堵塞直到window size大于0或发生I/O异常。

2.3 非堵塞NIO

非堵塞NIO的特点是:

  • 1)全部数据都是用缓冲区(ByteBuffer)处理的;
  • 2)使用全双工的Channel而不是输入/输出流,能更好地映射底层操作系统的API。
  • 3)多路复用器是基础。

NIO提供了非堵塞的读写操作。相比于BIO的确是异步的。因此从这个角度我们能够说NIO是异步非堵塞的。

然而假设严格依照UNIX网络编程模型定义的话,NIO并不能算是异步的,由于当事件完毕时不是由系统触发回调函数,而是须要我们不断轮询

2.4 异步AIO

AIO才是真正的异步I/O:NIO仅仅是实现了读写操作的非堵塞。但它还是要靠轮询而非事件通知(虽然前面说过JDK 1.5里升级为epoll。但上层API还是轮询没有变化)。说它是异步的事实上就是想说它是非堵塞的。JDK 1.7 NIO 2中提供的AIO才是真正的异步I/O。

3.Netty介绍

3.1 为什么选择Netty

使用原生NIO开发的特点就是功能开发相对easy,但兴许的可靠性方面的工作量非常大。须要我们自己处理如断连重连、半包读写、网络拥堵等问题。而且,NIO中还可能有bug,如“臭名昭著”的Selector空轮询导致CPU使用率100%(大学做大作业就碰到过这个问题。当时还纳闷呢,原来是个bug啊)。

所以,要想自己高速开发出健壮可靠的高性能网络公共组件,还真不是件easy事!

Netty为我们提供了开箱即用的高性能、高可靠、安全可扩展的网络组建,同一时候还修复了NIO的一些bug,社区非常活跃。版本号升级快。相比而言,Netty真是个不错的选择!

3.2 核心API简单介绍

Netty有下面几个核心API:

  • ByteBuf:JDK的ByteBuffer仅仅有一个位置指针,每次读写都要flip(),clear()等。ByteBuf有readerIndex和writerIndex两个指针。(0,readerIndex)是已读数据。[readerIndex,writerIndex)是未读的数据。[writerIndex,capacity)是可写空间。

  • Channel:封装了JDK Channel的操作,统一了接口。
  • EventLoop:负责轮询事件并分发给相应Channel的线程。

4.协议解析设计

4.1 TCP拆包和粘包

TCP是流协议,TCP底层并不了解上层业务数据的含义,它会依据TCP缓冲区的实际情况进行包的划分,一个完整的包可能被TCP拆分成多个包发送。也可能与其它小包封装成一个大的数据包发送,这就是所谓的拆包和粘包。

发生拆包的原因可能有:

  • 1)应用程序write写入的数据大小大于Socket发送缓冲区大小;
  • 2)进行MSS大小的TCP分段;
  • 3)以太网帧的payload大于MTU进行IP分片。

经常使用的解决策略:

  • 1)消息定长(FixedLengthFrameDecoder)。
  • 2)包尾加切割符,如回车(DelimiterBasedFrameDecoder);
  • 3)将消息分为消息头和消息体。在消息头中包括消息或消息体的长度(LengthFieldPrepender和LengthFieldBasedFrameDecoder)。

4.2 反序列化

我们能够在自己定义Decoder和Encoder中实现序列化和反序列化,如常见的Jackson,MsgPack。ProtoBuf等等。

5.高性能设计

5.1 Reactor模型

Reactor模型主要由多路复用器(Acceptor)、事件分发器(Dispatcher)、事件处理器(Handler)三部分组成。

深入研究的话,Reactor模型能够细分成三种:

  • 单线程:全部I/O操作都由一个线程完毕,即多路复用、事件分发和处理都是在一个Reactor线程上完毕的。由于全部I/O操作都不会堵塞。所以理论上是可能的。

    在一些小型应用场景下也的确能够使用单线程模型。但对于高并发应用是不合适的。即便这个NIO线程将CPU跑满也无法满足海量消息的编解码和读写。此外这种模型在可靠性上也存在问题。由于一旦这个NIO线程进入死循环就会导致整个系统的不可用。

  • 多线程:一个专门的NIO线程(Acceptor线程)负责监听和接收client的TCP连接请求。而读写由一个NIO线程池负责。每一个NIO能够相应多个链路。但为了防止并发问题。每一个链路仅仅相应一个NIO线程。绝大多数场景下,多线程模型都能够满足性能需求了。但在处理百万client连接,或须要对client进行比較耗时的安全认证时,单一Acceptor还是可能存在性能不足的问题。
  • 主从Reactor:Acceptor不再是一个单独的线程,而是独立的线程池,负责client的登录、握手和安全认证,一旦链路建立成功就将链路注冊到后端负责I/O读写的SubReactor线程池上。

Netty对这三种都支持。通过调整线程池的线程个数、是否共享线程池等參数在三种方式间方便的切换。一般的Netty最佳实践例如以下:

  • 创建两个NioEventLoopGroup来隔离Acceptor和I/O线程。
  • 假设业务逻辑非常easy。就不要在Handler中启动用户线程,直接在I/O线程中完毕业务。
  • 假设业务逻辑复杂,有可能导致线程堵塞的磁盘、数据库、网络等操作,则可将解码后的消息封装成Task派发到业务线程池运行。

  • 不要在用户线程中解码,而要在I/O线程上的解码Handler中完毕。

5.2 无锁化

由于在Handler内的数据读写、协议解析经常要保存一些状态,所以为了避免资源竞争。Netty对Handler採用串行化设计。即一个I/O线程会对我们配置到Netty中的Handler链的运行“负责究竟”。

正是有了这种设计,我们就能够放心的在Handler中保存各种状态。甚至使用ThreadLocal,全然无锁化的设计。

Netty的Handler在这一点上是不是与Struts2中的Action有点像呢?

5.3 零拷贝

在Netty内部,ByteBuffer默认使用堆外内存(Direct Buffer)作为缓冲区。这就避免了传统堆内存作缓冲区时的拷贝问题。使用传统堆内存时进行Socket读写时,JVM会先将堆内存缓冲区中的数据复制到直接内存中,然后再写入Socket。

此外。Netty也提供给开发人员一些工具实现零拷贝,这些工具都是我们能够利用的,比如:

  • ByteBufHolder:由于不同的协议消息体能够包括不同的协议字段和功能,使用者继承ByteBufHolder接口后能够按需封装自己的实现。比如Netty内部已提供的MemcacheContent就是继承自ByteBufHolder。
  • CompositeByteBuf:对外将多个ByteBuf“装饰”成一个ByteBuf,但实际上未产生不论什么数据拷贝。

  • DefaultFileRegion:提供了transferTo方法,将文件内容直接发送到目标Channel,实现了文件传输的零拷贝。

5.4 内存池

随着JVM虚拟机和JIT即时编译技术的发展。对象的分配和回收成了一件非常轻量级的工作。可是对于缓冲区。特别是对于堆外直接内存,分配和回收却仍然是一件耗时的操作。所以,Netty提供了内存池来实现缓冲区的重用机制。

这里再简单介绍一下Netty内部的内存管理机制。

首先,Netty会预先申请一大块内存。在内存管理器中一般叫做Arena。

Netty的Arena由很多Chunk组成。而每一个Chunk又由一个或多个Page组成。Chunk通过二叉树的形式组织Page,每一个叶子节点表示一个Page,而中间节点表示内存区域,节点自己记录它在整个Arena中的偏移地址。当区域被分配出去后,中间节点上的标记位会被标记,这样就表示这个中间节点下面的全部节点都已被分配了。

6.可靠性设计

6.1 心跳检測

在凌晨等业务低谷期。假设发生网络闪断、连接Hang住等问题时,由于没有业务消息,应用进程非常难发现。到了白天业务高峰期时,会发生大量的网络通信失败。导致应用进程一段时间内无法处理业务消息。因此能够採用心跳检測机制。一旦发现网络故障则马上关闭链路。并主动重连。

具体来看。心跳检測机制一般的设计思路是:

1)当连续周期T没有读写消息,client主动发送Ping心跳消息给服务端。
2)假设在下一周期T到来时没有收到服务端的Pong心跳或业务消息,则心跳失败计数器加1。
3)每当client接收到服务端的Pong心跳或业务消息,则心跳失败计数器清零。当计数器达到N次,则关闭链路。间隔INTERVAL后发起重连操作(保证服务端有充足的时间释放资源。所以不能失败后马上重连)。
4)同理,服务端也要用上面的方法检測client(保证不管通信哪一方出现网络故障,都能被及时检測出来)。

6.2 内存保护

Netty依据ByteBuf的maxCapacity保护内存不会超过上限。

此外默认的TailHandler会负责自己主动释放ByteBuf的缓冲区。

6.3 优雅停机

Netty利用JVM注冊的Shutdown Hook拦截到退出信号量。然后运行退出操作:释放各个模块的占用资源、将缓冲区中剩余的消息处理完毕或者清空、将待刷新的数据持久化磁盘或数据库等。

6.安全性设计

(略)

7.扩展性设计

7.1 灵活的TCP參数配置

在Netty中能够非常方便地改动TCP的參数。比如缓冲区大小的參数SO_RCVBUF/SO_SNDBUF、关闭将大量小包优化成大包的Nagle算法的參数SO_TCPNODELAY參数来避免对时延敏感应用的影响、以及Linux软中断等。

- Netty协议栈不区分服务端和client,开发完毕后可同一时候支持。

- 可靠性设计:心跳机制。重连机制 - 安全性设计:内网採取IP白名单进行安全过滤。外网採取更加严格的SSL/TSL安全传输。

- 扩展性设计:业务功能能够在消息头中附加流水号等。利用Netty提供的attachment字段扩展。

7.2 可定制的API

Netty中关键的类库都提供了接口或抽象类以及大量的工厂类供开发人员扩展,像Handler则是直接提供了ChannelPipeline实现了责任链模式。方便我们做随意的组合和扩展。

用Netty开发中间件:网络编程基础


推荐阅读
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • 本文介绍了通过ABAP开发往外网发邮件的需求,并提供了配置和代码整理的资料。其中包括了配置SAP邮件服务器的步骤和ABAP写发送邮件代码的过程。通过RZ10配置参数和icm/server_port_1的设定,可以实现向Sap User和外部邮件发送邮件的功能。希望对需要的开发人员有帮助。摘要长度:184字。 ... [详细]
  • CentOS 6.5安装VMware Tools及共享文件夹显示问题解决方法
    本文介绍了在CentOS 6.5上安装VMware Tools及解决共享文件夹显示问题的方法。包括清空CD/DVD使用的ISO镜像文件、创建挂载目录、改变光驱设备的读写权限等步骤。最后给出了拷贝解压VMware Tools的操作。 ... [详细]
  • 移动端常用单位——rem的使用方法和注意事项
    本文介绍了移动端常用的单位rem的使用方法和注意事项,包括px、%、em、vw、vh等其他常用单位的比较。同时还介绍了如何通过JS获取视口宽度并动态调整rem的值,以适应不同设备的屏幕大小。此外,还提到了rem目前在移动端的主流地位。 ... [详细]
  • 代理模式的详细介绍及应用场景
    代理模式是一种在软件开发中常用的设计模式,通过在客户端和目标对象之间增加一层中间层,让代理对象代替目标对象进行访问,从而简化系统的复杂性。代理模式可以根据不同的使用目的分为远程代理、虚拟代理、Copy-on-Write代理、保护代理、防火墙代理、智能引用代理和Cache代理等几种。本文将详细介绍代理模式的原理和应用场景。 ... [详细]
  • 本文内容为asp.net微信公众平台开发的目录汇总,包括数据库设计、多层架构框架搭建和入口实现、微信消息封装及反射赋值、关注事件、用户记录、回复文本消息、图文消息、服务搭建(接入)、自定义菜单等。同时提供了示例代码和相关的后台管理功能。内容涵盖了多个方面,适合综合运用。 ... [详细]
  • 本文介绍了如何使用PHP向系统日历中添加事件的方法,通过使用PHP技术可以实现自动添加事件的功能,从而实现全局通知系统和迅速记录工具的自动化。同时还提到了系统exchange自带的日历具有同步感的特点,以及使用web技术实现自动添加事件的优势。 ... [详细]
  • GetWindowLong函数
    今天在看一个代码里头写了GetWindowLong(hwnd,0),我当时就有点费解,靠,上网搜索函数原型说明,死活找不到第 ... [详细]
  • 本文讲述了作者通过点火测试男友的性格和承受能力,以考验婚姻问题。作者故意不安慰男友并再次点火,观察他的反应。这个行为是善意的玩人,旨在了解男友的性格和避免婚姻问题。 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • 后台获取视图对应的字符串
    1.帮助类后台获取视图对应的字符串publicclassViewHelper{将View输出为字符串(注:不会执行对应的ac ... [详细]
  • 本文介绍了在Mac上搭建php环境后无法使用localhost连接mysql的问题,并通过将localhost替换为127.0.0.1或本机IP解决了该问题。文章解释了localhost和127.0.0.1的区别,指出了使用socket方式连接导致连接失败的原因。此外,还提供了相关链接供读者深入了解。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • CEPH LIO iSCSI Gateway及其使用参考文档
    本文介绍了CEPH LIO iSCSI Gateway以及使用该网关的参考文档,包括Ceph Block Device、CEPH ISCSI GATEWAY、USING AN ISCSI GATEWAY等。同时提供了多个参考链接,详细介绍了CEPH LIO iSCSI Gateway的配置和使用方法。 ... [详细]
  • 本文介绍了一个适用于PHP应用快速接入TRX和TRC20数字资产的开发包,该开发包支持使用自有Tron区块链节点的应用场景,也支持基于Tron官方公共API服务的轻量级部署场景。提供的功能包括生成地址、验证地址、查询余额、交易转账、查询最新区块和查询交易信息等。详细信息可参考tron-php的Github地址:https://github.com/Fenguoz/tron-php。 ... [详细]
author-avatar
宜宾开心花行
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有