第3章 Netty高性能架构设计
1 Netty概述
1.1 原生NIO存在的问题
1.2 Netty的优点
1.3 Netty版本说明
2 线程模型基本介绍
3 传统阻塞IO服务模型
3.1 工作原理图
- 黄色框表示对象,蓝色框表示线程。
- 白色框表示方法(API)
3.2 模型特点
- 采用阻塞 IO模式获取输入的数据
- 每个连接都需要独立的线程完成数据的输入,业务处理,数据返回
3.3 问题分析
- 当并发数很大,就会创建大量的线程,占用大量系统资源
- 连接创建后,如果当前线程暂时没有数据可读,该线程会阻塞在read操作,造成线程资源浪费
4 Reactor模式
4.1 传统阻塞IO服务模型的2个缺点,解决方案
4.2 Reactor模式基本设计思想
IO复用结合线程池,就是Reactor模式基本设计思想
4.3 Reactor模式核心组成
4.4 Reactor模式分类
根据Reactor的数量和处理资源池线程的数量不同,有3种典型的实现:
- 单Reactor 单线程
- 单Reactor 多线程
- 主从Reactor 多线程
4.5 单Reactor 单线程
方案说明
优缺点分析
4.6 单Reactor 多线程
优缺点分析
4.7 主从Reactor 多线程
针对单Reactor多线程模型中,Reactor在单线程中运行,高并发场景下容易成为性能瓶颈,可以让Reactor在多线程中运行
Scalable IO in Java 对 Multiple Reactors的原理图解
优缺点
4.8 Reactor模式小结
3种模式用生活案例来理解
Reactor模式优点
5 Netty模型
5.1 工作原理示意图(简单版)
Netty主要基于主从Reactor多线程模型(如图)做了一定的改进,其中主从Reactor多线程模型有多个Reactor
5.2 工作原理示意图(进阶版)
5.3 工作原理示意图(详细版)
5.4 Netty快速入门实例—TCP服务
public class NettyServer {
public static void main(String[] args) throws Exception {
/*
创建BossGroup和WorkerGroup
说明:
1、创建两个线程组bossGroup和workerGroup
2、bossGroup只处理连接请求,真正和客户端业务处理交给workerGroup完成
3、两个都是无限循环
4、bossGroup和workerGroup含有的子线程(NioEventLoop)的个数 默认是 实际cpu核数*2
*/
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
try {
//创建服务器端的启动对象,配置参数
ServerBootstrap bootstrap = new ServerBootstrap();
//使用链式编程来进行设置
bootstrap.group(bossGroup, workerGroup) //设置两个线程组
.channel(NioServerSocketChannel.class)//使用NioServerSocketChannel作为服务器的通道实现
.option(ChannelOption.SO_BACKLOG, 128)//设置线程队列等待连接个数
.childOption(ChannelOption.SO_KEEPALIVE, true)//设置保持活动连接状态
.childHandler(new ChannelInitializer<SocketChannel>() {//创建一个通道测试对象(匿名对象)
//给pipeline设置处理器
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new NettyServerHandler());
}
});//给我们的workerGroup的EventLoop对应的管道设置处理器
System.out.println("....服务器 is ready....");
//绑定一个端口并且同步,生成了一个ChannelFuture对象
ChannelFuture cf = bootstrap.bind(6668).sync();
//对关闭通道进行监听
cf.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
/**
* @author :SevenYear
* @description:自定义一个Handler,需要继承Netty规定好的某个HandlerAdapter(规范) 这时自定义的Handler才能称为一个Handler
* @date :2021/2/16 12:11
*/
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
/**
* 读取数据事件(可以读取客户端发送的消息)
*
* @param ctx 上下文对象,含有 管道pipeline,通道channel,地址
* @param msg 客户端发送的数据,默认Object
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("服务器读取线程 "+Thread.currentThread().getName());
System.out.println("server ctx = " + ctx);
System.out.println("看看channelh和pipeline的关系");
//将msg转成一个ByteBuf(Netty提供的,不是NIO的ByteBuffer)
ByteBuf buf = (ByteBuf) msg;
System.out.println("客户端发送的消息是:" + buf.toString(CharsetUtil.UTF_8));
System.out.println("客户端地址:" + ctx.channel().remoteAddress());
}
/**
* 数据读取完毕
* @param ctx
* @throws Exception
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
//writeAndFlush是write+flush,将数据写入缓存并刷新
//一般来讲,我们对发送的数据进行编码
ctx.writeAndFlush(Unpooled.copiedBuffer("hello,客户端....",CharsetUtil.UTF_8));
}
/**
* 异常处理,一般需要关闭通道
* @param ctx
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
public class NettyClient {
public static void main(String[] args) throws InterruptedException {
//客户端需要一个事件循环组
NioEventLoopGroup group = new NioEventLoopGroup();
try {
//创建客户端启动对象,注意客户端使用的不是ServerBootstrap,而是Bootstrap
Bootstrap bootstrap = new Bootstrap();
//设置相关参数
bootstrap.group(group)//设置线程组
.channel(NioSocketChannel.class)//设置客户端通道实现类(反射)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new NettyClientHandler());//加入自己的处理器
}
});
System.out.println("客户端 is ok ....");
//启动客户端连接服务端
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 6668).sync();
//对关闭通道进行监听
channelFuture.channel().closeFuture().sync();
}finally {
group.shutdownGracefully();
}
}
}
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
/**
* 当通道就绪就会触发该方法
*
* @param ctx
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("client " + ctx);
ctx.writeAndFlush(Unpooled.copiedBuffer("hello,server", CharsetUtil.UTF_8));
}
/**
* 当通道有读取事件时,会触发
*
* @param ctx
* @param msg
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
System.out.println("服务器回复的消息:"+buf.toString(CharsetUtil.UTF_8));
System.out.println("服务器的地址:"+ctx.channel().remoteAddress());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
5.5 任务队列中的Task的3种典型使用场景
- 用户程序自定义的普通任务
- 用户自定义定时任务
- 非当前Reactor线程调用Channel的各种发
例如在推送系统的业务线里面,根据用户的标识,找到对应的Channel引用,然后调用Write类方法该用户推送消息,就会进入到这种场景。最终的Write会提交到任务队列中后被异步消费。
/**
* @author :SevenYear
* @description:自定义一个Handler,需要继承Netty规定好的某个HandlerAdapter(规范) 这时自定义的Handler才能称为一个Handler
* @date :2021/2/16 12:11
*/
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
/**
* 读取数据事件(可以读取客户端发送的消息)
*
* @param ctx 上下文对象,含有 管道pipeline,通道channel,地址
* @param msg 客户端发送的数据,默认Object
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//假如这里我们有一个非常耗时的业务->异步执行->提交该channel对应的NIOEventLoop的taskQueue中
//解决方案1 用户程序自定义的普通任务
ctx.channel().eventLoop().execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10*1000);
ctx.writeAndFlush(Unpooled.copiedBuffer("hello,客户端....2",CharsetUtil.UTF_8));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
ctx.channel().eventLoop().execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10*1000);
ctx.writeAndFlush(Unpooled.copiedBuffer("hello,客户端....3",CharsetUtil.UTF_8));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//解决方案2 用户自定义定时任务->将任务提交到scheduledTaskQueue中
ctx.channel().eventLoop().schedule(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10*1000);
ctx.writeAndFlush(Unpooled.copiedBuffer("hello,客户端....4",CharsetUtil.UTF_8));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},5, TimeUnit.SECONDS);
System.out.println("go on....");
}
/**
* 数据读取完毕
* @param ctx
* @throws Exception
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
//writeAndFlush是write+flush,将数据写入缓存并刷新
//一般来讲,我们对发送的数据进行编码
ctx.writeAndFlush(Unpooled.copiedBuffer("hello,客户端....1",CharsetUtil.UTF_8));
}
/**
* 异常处理,一般需要关闭通道
* @param ctx
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
5.6 异步模型
基本介绍
Future说明
工作原理示意图
Future-Listener机制
举例说明:绑定端口是异步操作,当绑定操作处理完,将会调用相应的监听器处理逻辑
//绑定一个端口并且同步,生成了一个ChannelFuture对象
ChannelFuture cf = bootstrap.bind(6668).sync();
//给cf注册监听器,监控我们关心的事件
cf.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture channelFuture) throws Exception {
if (cf.isSuccess()) {
System.out.println("监听端口 6668 成功");
}else {
System.out.println("监听端口 6668 失败");
}
}
});
5.7 快速入门实例—HTTP服务
public class TestServer {
public static void main(String[] args) throws Exception {
NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap Serverbootstrap = new ServerBootstrap();
Serverbootstrap.group(bossGroup,workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new TestServerInitializer());
ChannelFuture channelFuture = Serverbootstrap.bind(8932).sync();
channelFuture.channel().closeFuture().sync();
}finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
public class TestServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//向管道假如处理器
//得到管道
ChannelPipeline pipeline = socketChannel.pipeline();
/*
加入一个Netty提供的httpServerCodec codec =>[coder - decoder]
HttpServerCodec说明:
1、HttpServerCodec是Netty提供的处理Http的编-解码器
*/
pipeline.addLast("MyHttpServerCodec",new HttpServerCodec());
//增加一个自定义的Handler
pipeline.addLast("MyTestHttpServerHandler",new TestHttpServerHandler());
}
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
//判断msg是不是httprequest请求
if (msg instanceof HttpRequest){
System.out.println("msg 类型="+msg.getClass());
System.out.println("客户端地址:"+ctx.channel().remoteAddress());
//获取到
HttpRequest httpRequest = (HttpRequest) msg;
//获取uri,过滤指定的资源
URI uri = new URI(httpRequest.uri());
if ("/favicon.ico".equals(uri.getPath())){
System.out.println("请求了 favicon。ico,不做响应");
return;
}
//回复信息给浏览器[http协议]
ByteBuf content = Unpooled.copiedBuffer("hello,我是服务器", CharsetUtil.UTF_8);
//构造一个http的响应,即httpresponse
DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content);
response.headers().set(HttpHeaderNames.CONTENT_TYPE,"text/plain;charset=utf-8");
response.headers().set(HttpHeaderNames.CONTENT_LENGTH,content.readableBytes());
//将构建好的response返回
ctx.writeAndFlush(response);
}
}
}