介绍
Netty是一个高性能事件驱动的异步的非堵塞的IO(NIO)网络应用程序框架和工具,基于Netty可以建立高性能的Http服务器。支持HTTP、 WebSocket 、Protobuf、 TCP 和UDP等协议。
通过借助Netty,你可以很容易的自己去实现HTTP、FTP、UDP、RPC、WebSocket、Redis的Proxy、MySQL的Proxy服务器等等。
Netty 整体结构
直接引用官网的图:
-
Core 核心层
它提供了底层网络通信的通用抽象和实现,包括可扩展的事件模型、通用的通信 API、支持零拷贝的 ByteBuf 等。
-
Protocol Support 协议支持层
协议支持层包括了主流的协议的编解码实现,如 Http、Websocket、Protobuf、大文件传输、文本、二进制等,此外还支持自定义的应用层协议。
-
Transport Service 传输服务层
传输服务层提供了网络传输能力的定义和实现方法。支持 Socket、Http 隧道等传输方式。对 TCP、UDP 等数据传输做了抽象和封装,让使用者增加聚焦在业务逻辑的开发上,而不必关系底层数据传输的处理细节。
Netty 逻辑架构
Netty 的逻辑处理架构属于典型的网络分层架构,分为网络通信层、事件调度层、服务编排层。
网络通信层的主要职责是:执行网络 IO 的操作。支持多种网络协议和 I/O 模型的连接操作。核心组件包括:BootStrap、ServerBootStrap、Channel 三个组件。
事件调度层的职责是通过 Reactor 线程模型对各类事件进行聚合处理,通过 Selector 主循环线程集成多种事件( I/O 事件、信号事件、定时事件等),实际的业务处理逻辑是交由服务编排层中相关的 Handler 完成。核心组件包括:EventLoopGroup、EventLoop。
服务编排层的职责是负责组装各类服务,它是 Netty 的核心处理链,用以实现网络事件的动态编排和有序传播。核心组件包括:ChannelPipeline、ChannelHandler、ChannelHandlerContext。
Netty 源码结构
Core 核心层模块:netty-common 模块、netty-buffer 模块、netty-resover 模块。
Protocol Support 协议支持层模块:netty-codec 模块、netty-handler 模块。
Transport Service 传输服务层模块:netty-transport 模块。
为什么使用Netty
首先说一下为什么不使用NIO的原因:
- NIO的类库和API繁杂。需要很多额外的技能做铺垫。例如需要很熟悉Java多线程编程、Selector线程模型。导致工作量和开发难度都非常大。
- 扩展 ByteBuffer:NIO和Netty都有ByteBuffer来操作数据,但是NIO的ByteBuffer长度固定而且操作复杂,许多操作甚至都需要自己实现。而且它的构造函数是私有,不能扩展。Netty 提供了自己的
ByteBuffer 实现, Netty 通过一些简单的 APIs 对 ByteBuffer 进行构造、使用和操作,以此来解决 NIO 中的一些限制。 - NIO 对缓冲区的聚合和分散操作可能会操作内存泄露,到jdk7才解决了内存泄露的问题
- 存在臭名昭著的epoll bug,导致Selector空轮询:这个bug会导致linux上导致cpu 100%,使得nio server/client不可用,直到jdk 6u4才解决。
相比于NIO,netty的优点就很明显了:
- API使用简单,开发门槛低。
- 功能强大,预置了多种编解码功能,支持多种协议开发。
- Netty自带拆包解包,从NIO各种繁复的细节中脱离出来,定制能力强,可以通过ChannelHadler进行扩展。
- 对Selector做了很多细小的优化,性能高,对比其它NIO框架,Netty综合性能最优。
- 经历了大规模的应用验证。在互联网、大数据、网络游戏、企业应用、电信软件得到成功,很多著名的框架通信底层就用了Netty,比如Dubbo
- Netty解决了NIO中Selector空轮询BUG。
- Netty底层IO模型可以随意切换,比如可以从NIO切换到BIO
Netty 的应用场景
- 互联网行业
- 互联网行业: 在分布式系统中, 各个节点之间需要远程服务调用, 高性能的 RPC 框架必不可少, Netty 作为
异步高性能的通信框架, 往往作为基础通信组件被这些 RPC 框架使用。 - 典型的应用有: 阿里分布式服务框架 Dubbo 的 RPC 框架使用 Dubbo 协议进行节点间通信, Dubbo 协议默
认使用 Netty 作为基础通信组件, 用于实现各进程节点之间的内部通信
- 互联网行业: 在分布式系统中, 各个节点之间需要远程服务调用, 高性能的 RPC 框架必不可少, Netty 作为
- 游戏行业
- 无论是手游服务端还是大型的网络游戏, Java 语言得到了越来越广泛的应用
- Netty 作为高性能的基础通信组件, 提供了 TCP/UDP 和 HTTP 协议栈, 方便定制和开发私有协议栈, 账号登
录服务器 - 地图服务器之间可以方便的通过 Netty 进行高性能的通信
- 大数据领域
- 经典的 Hadoop 的高性能通信和序列化组件 Avro 的 RPC 框架, 默认采用 Netty 进行跨界点通信
- 它的 Netty Service 基于 Netty 框架二次封装实现。
Netty实战应用
服务端启动类
Java代码如下:
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
/**
* 应答服务器
*/
public class EchoServer {
private int port;
public EchoServer(int port) {
this.port = port;
}
public void run() throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup(); // (1)
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap(); // (2)
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class) // (3)
.childHandler(new ChannelInitializer<SocketChannel>() { // (4)
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
ch.pipeline().addLast("decoder", new StringDecoder());
ch.pipeline().addLast("encoder", new StringEncoder());
ch.pipeline().addLast(new EchoServerHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128) // (5)
.childOption(ChannelOption.SO_KEEPALIVE, true); // (6)
// 绑定端口,开始接收进来的连接
ChannelFuture f = b.bind(port).sync(); // (7)
System.out.println("Server start listen at " + port );
// 等待服务器 socket 关闭 。
// 在这个例子中,这不会发生,但你可以优雅地关闭你的服务器。
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
int port;
if (args.length > 0) {
port = Integer.parseInt(args[0]);
} else {
port = 8080;
}
new EchoServer(port).run();
}
}
- 两个NioEventLoopGroup对象,可以看作两个线程组。bossGroup的作用是监听客户端请求。workerGroup的作用是处理每条连接的数据读写。
- ServerBootstrap是一个引导类,其对象的作用是引导服务器的启动工作。
- .group是配置上面两个线程组的角色,也就是谁去监听谁去处理读写。上面只是创建了两个线程组,并没有实际使用。
- .channel是配置服务端的IO模型,上面代码配置的是NIO模型。也可以配置为BIO,如OioServerSocketChannel.class。
- .childHandler用于配置每条连接的数据读写和业务逻辑等。上面代码用的是匿名内部类,并没有什么内容。实际使用中为了规范起见,一般会再写一个单独的类也就是初始化器,在里面写上需要的操作。就如Netty实战那篇中的代码一样。
- 最后就是绑定监听端口了。
引导类最小化的参数配置就是如上四个:配置线程组、IO模型、处理逻辑、绑定端口。
服务端处理逻辑类
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
/**
* 处理服务端 channel.
*/
public class EchoServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
System.out.println(ctx.channel().remoteAddress()+"->Server :"+ msg.toString());
ctx.write(msg); // (1)
ChannelFuture future= ctx.channel().write(msg);
future.addListener(new GenericFutureListener<Future<? super Void>>() {
@Override
public void operationComplete(Future<? super Void> future) throws Exception {
System.out.println("asdf");
}
});
ctx.flush(); // (2)
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
// 当出现异常就关闭连接
cause.printStackTrace();
ctx.close();
}
}
客户端类
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
/**
* Sends one message when a connection is open and echoes back any received
* data to the server. Simply put, the echo client initiates the ping-pong
* traffic between the echo client and server by sending the first message to
* the server.
*/
public final class EchoClient {
static final String HOST = System.getProperty("host", "127.0.0.1");
static final int PORT = Integer.parseInt(System.getProperty("port", "8080"));
static final int SIZE = Integer.parseInt(System.getProperty("size", "256"));
public static void main(String[] args) throws Exception {
// Configure the client.
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
p.addLast("decoder", new StringDecoder());
p.addLast("encoder", new StringEncoder());
p.addLast(new EchoClientHandler());
}
});
// Start the client.
ChannelFuture f = b.connect(HOST, PORT).sync();
// Wait until the connection is closed.
f.channel().closeFuture().sync();
} finally {
// Shut down the event loop to terminate all threads.
group.shutdownGracefully();
}
}
}
客户端处理逻辑类
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
public class EchoClientHandler extends ChannelInboundHandlerAdapter {
private final ByteBuf firstMessage;
/**
* Creates a client-side handler.
*/
public EchoClientHandler() {
firstMessage = Unpooled.buffer(EchoClient.SIZE);
for (int i = 0; i < firstMessage.capacity(); i ++) {
firstMessage.writeByte((byte) i);
}
}
@Override
public void channelActive(ChannelHandlerContext ctx) {
ctx.writeAndFlush(firstMessage);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ctx.write(msg);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // Close the connection when an exception is raised.
cause.printStackTrace();
ctx.close();
}
}
总结
-
为了尽可能地提升性能,Netty 在很多地方进行了无锁化的设计,例如在 I/O 线程内部进行串行操作,避免多线程竞争导致的性能下降问题。表面上看,串行化设计似乎 CPU 利用率不高,并发程度不够。但是,通过调整 NIO 线程池的线程参数,可以同时启动多个串行化的线程并行运行,这种局部无锁化的串行线程设计相比一个队列—多个工作线程的模型性能更优。 Netty 的 NioEventLoop 读取到消息之后,直接调用 ChannelPipeline 的
fireChannelRead (Object msg)
。只要用户不主动切换线程,一直都是由 NioEventLoop 调用用户的 Handler,期间不进行线程切换。这种串行化处理方式避免了多线程操作导致的锁的竞争,从性能角度看是最优的。 -
使用Reactor设计思路,提高了Netty框架拓展性和复用性
-
Netty 的 Promise 接口扩展了 Netty 的 Future 接口,它表示一种可写的 Future,就是可以设置异步执行的结果。
-
channel使用组合方式,将IO操作都交给Unsafe接口及其子类实现,减少耦合。
-
Netty 的 ByteBuf 采用了读写索引分离的策略(readerIndex 与 writerIndex),一个初始化(里面尚未有任何数据)的 ByteBuf 的 readerIndex 与 writerIndex 值都为 0