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

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

  • 前半本的代码排版太糟糕了,简直就是直接打印Word的版式似的。源码解析部分的条理性和代码排版好多了,感觉比其他部分的质量高多了。
  • 如果你是初学者可能会感觉很详细,几乎每部分都会来一套客户端和服务端的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开发的特点就是功能开发相对容易,但后续的可靠性方面的工作量非常大,需要我们自己处理如断连重连、半包读写、网络拥堵等问题。并且,NIO中还可能有bug,如“臭名昭著”的Selector空轮询导致CPU使用率100%(大学做大作业就碰到过这个问题,当时还纳闷呢,原来是个bug啊)。

所以,要想自己快速开发出健壮可靠的高性能网络公共组件,还真不是件容易事!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线程)负责监听和接收客户端的TCP连接请求,而读写由一个NIO线程池负责。每个NIO可以对应多个链路,但为了防止并发问题,每个链路只对应一个NIO线程。绝大多数场景下,多线程模型都可以满足性能需求了。但在处理百万客户端连接,或需要对客户端进行比较耗时的安全认证时,单一Acceptor还是可能存在性能不足的问题。
  • 主从Reactor:Acceptor不再是一个单独的线程,而是独立的线程池,负责客户端的登录、握手和安全认证,一旦链路建立成功就将链路注册到后端负责I/O读写的SubReactor线程池上。

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

  • 创建两个NioEventLoopGroup来隔离Acceptor和I/O线程。
  • 如果业务逻辑非常简单,就不要在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没有读写消息,客户端主动发送Ping心跳消息给服务端。

2)如果在下一周期T到来时没有收到服务端的Pong心跳或业务消息,则心跳失败计数器加1。

3)每当客户端接收到服务端的Pong心跳或业务消息,则心跳失败计数器清零;当计数器达到N次,则关闭链路,间隔INTERVAL后发起重连操作(保证服务端有充足的时间释放资源,所以不能失败后立即重连)。

4)同理,服务端也要用上面的方法检测客户端(保证无论通信哪一方出现网络故障,都能被及时检测出来)。

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协议栈不区分服务端和客户端,开发完成后可同时支持。
- 可靠性设计:心跳机制;重连机制
- 安全性设计:内网采取IP白名单进行安全过滤;外网采取更加严格的SSL/TSL安全传输。
- 扩展性设计:业务功能可以在消息头中附加流水号等,利用Netty提供的attachment字段扩展。

7.2 可定制的API

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

用Netty开发中间件:网络编程基础的更多相关文章

  1. iOS开发网络篇—网络编程基础

    iOS开发网络篇—网络编程基础 一.为什么要学习网络编程 1.简单说明 在移动互联网时代,移动应用的特征有: (1)几乎所有应用都需要用到网络,比如QQ.微博.网易新闻.优酷.百度地图 (2)只有通过 ...

  2. Java网络编程基础(Netty预备知识)

    今天在家休息,闲来无事,写篇博客,陶冶下情操~~~ =================我是分割线================ 最近在重新学习Java网络编程基础,以便后续进行Netty的学习. 整 ...

  3. python全栈开发从入门到放弃之socket网络编程基础

    网络编程基础 一 客户端/服务器架构 1.硬件C/S架构(打印机) 2.软件C/S架构 互联网中处处是C/S架构 如黄色网站是服务端,你的浏览器是客户端(B/S架构也是C/S架构的一种) 腾讯作为服务 ...

  4. Linux高并发网络编程开发——10-Linux系统编程-第10天(网络编程基础-socket)

    在学习Linux高并发网络编程开发总结了笔记,并分享出来.有问题请及时联系博主:Alliswell_WP,转载请注明出处. 10-Linux系统编程-第10天(网络编程基础-socket) 在学习Li ...

  5. 用Netty开发中间件:高并发性能优化

    用Netty开发中间件:高并发性能优化 最近在写一个后台中间件的原型,主要是做消息的分发和透传.因为要用Java实现,所以网络通信框架的第一选择当然就是Netty了,使用的是Netty 4版本.Net ...

  6. 用Netty开发中间件:高并发性能优化(转)

    用Netty开发中间件:高并发性能优化 最近在写一个后台中间件的原型,主要是做消息的分发和透传.因为要用Java实现,所以网络通信框架的第一选择当然就是Netty了,使用的是Netty 4版本.Net ...

  7. iOS开发之网络编程--2、NSURLSessionDownloadTask文件下载

    本文内容大纲: 1.回顾NSURLSessionTask 2.NSURLSessionDownloadTask大文件之block下载 3.NSURLSessionDownloadTask大文件之代理方 ...

  8. iOS开发之网络编程--使用NSURLConnection实现大文件断点续传下载+使用输出流代替文件句柄

    前言:本篇讲解,在前篇iOS开发之网络编程--使用NSURLConnection实现大文件断点续传下载的基础上,使用输出流代替文件句柄实现大文件断点续传.    在实际开发中,输入输出流用的比较少,但 ...

  9. Python网络编程基础pdf

    Python网络编程基础(高清版)PDF 百度网盘 链接:https://pan.baidu.com/s/1VGwGtMSZbE0bSZe-MBl6qA 提取码:mert 复制这段内容后打开百度网盘手 ...

随机推荐

  1. Hadoop 3.x 新特性剖析系列1

    1.概述 目前从Hadoop官网的Wiki来看,稳定版本已经发行到Hadoop2.9.0,最新版本为Hadoop3.1.0,查阅JIRA,社区已经着手迭代Hadoop3.2.0.那么,今天笔者就带着大 ...

  2. python制作一个简单的中奖系统

    注释: 展示图下的代码,我是用pycharm写的,是python解释器中的一种,本课没不同解释器的要求,可根据自己喜欢的解释器编写. 步骤: 本期给大家带来的是,一个简单的中奖系统,首先打开自己电脑上 ...

  3. 使用 Docker 一步搞定 ZooKeeper 集群的搭建

    背景 原来学习 ZK 时, 我是在本地搭建的伪集群, 虽然说使用起来没有什么问题, 但是总感觉部署起来有点麻烦. 刚好我发现了 ZK 已经有了 Docker 的镜像了, 于是就尝试了一下, 发现真是爽 ...

  4. CentOS 7下Flannel安装与配置

    1. 安装前的准备 etcd 3.2.9 Docker 17.12.0-ce 三台机器10.100.97.236, 10.100.97.92, 10.100.97.81 etcd不同版本之间的差别还是 ...

  5. HTML笔记04---计时事件

    JavaScript运动01 计时事件 1.语法:var t=setTimeout("javascript语句",毫秒); setTimeout() 方法会返回某个值.在上面的语句 ...

  6. 用ECMAScript4 ( ActionScript3) 实现Unity的热更新 -- Demo分析

    如何创建工程 下载最新的Unity发布插件包. 打开Unity,新建一个项目 将插件包导入 在菜单中点击ASRuntime/Create ActionScript3 FlashDevelop HotF ...

  7. 使用Docker安装Jenkins

    Jenkins Jenkins是一个开源软件项目,是基于Java开发的一种持续集成工具,用于监控持续重复的工作,旨在提供一个开放易用的软件平台,使软件的持续集成变成可能. 环境准备 腾讯云 硬件配置: ...

  8. [LeetCode] Design Log Storage System 设计日志存储系统

    You are given several logs that each log contains a unique id and timestamp. Timestamp is a string t ...

  9. [LeetCode] Contiguous Array 邻近数组

    Given a binary array, find the maximum length of a contiguous subarray with equal number of 0 and 1. ...

  10. 二哥的xss游戏

    断断续续做完了,收获挺多的. 地址:http://xsst.sinaapp.com/xss/ 二哥的xss游戏 第一题http://xsst.sinaapp.com/xss/ext/1.php?umo ...