第3章 Netty高性能架构设计

第3章 Netty高性能架构设计

1 Netty概述

1.1 原生NIO存在的问题

image-20210215170857379

1.2 Netty的优点

image-20210215171115399

image-20210215171016057

1.3 Netty版本说明

image-20210215171703598

2 线程模型基本介绍

image-20210215171934068

3 传统阻塞IO服务模型

3.1 工作原理图

  1. 黄色框表示对象,蓝色框表示线程。
  2. 白色框表示方法(API)

image-20210215173119902

3.2 模型特点

  1. 采用阻塞 IO模式获取输入的数据
  2. 每个连接都需要独立的线程完成数据的输入,业务处理,数据返回

3.3 问题分析

  1. 当并发数很大,就会创建大量的线程,占用大量系统资源
  2. 连接创建后,如果当前线程暂时没有数据可读,该线程会阻塞在read操作,造成线程资源浪费

4 Reactor模式

4.1 传统阻塞IO服务模型的2个缺点,解决方案

image-20210215173442396

image-20210215173501813

4.2 Reactor模式基本设计思想

IO复用结合线程池,就是Reactor模式基本设计思想

image-20210215173933834

image-20210215173943874

4.3 Reactor模式核心组成

image-20210215174008305

4.4 Reactor模式分类

根据Reactor的数量和处理资源池线程的数量不同,有3种典型的实现:

  1. 单Reactor 单线程
  2. 单Reactor 多线程
  3. 主从Reactor 多线程

4.5 单Reactor 单线程

image-20210215174159667

方案说明

image-20210215174253018

优缺点分析

image-20210215174313241


4.6 单Reactor 多线程

image-20210215174358247

image-20210215174529632

image-20210215174517437

优缺点分析

image-20210215174548423


4.7 主从Reactor 多线程

针对单Reactor多线程模型中,Reactor在单线程中运行,高并发场景下容易成为性能瓶颈,可以让Reactor在多线程中运行

image-20210215174736928

image-20210215175004449

image-20210215175015403

Scalable IO in Java 对 Multiple Reactors的原理图解

image-20210215175120258

优缺点

image-20210215175150127

4.8 Reactor模式小结

3种模式用生活案例来理解

image-20210215175230250

Reactor模式优点

image-20210215175301514

5 Netty模型

5.1 工作原理示意图(简单版)

Netty主要基于主从Reactor多线程模型(如图)做了一定的改进,其中主从Reactor多线程模型有多个Reactor

image-20210215230204215

image-20210215230244347

5.2 工作原理示意图(进阶版)

image-20210215230403723

5.3 工作原理示意图(详细版)

image-20210215230600388

image-20210215230613640

image-20210215230622724

5.4 Netty快速入门实例—TCP服务

image-20210216113436860

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种典型使用场景

  1. 用户程序自定义的普通任务
  2. 用户自定义定时任务
  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();
    }
}

image-20210216174301920

5.6 异步模型

基本介绍

image-20210216174541250

Future说明

image-20210216174642959

工作原理示意图

image-20210216174811082

image-20210216174824741

image-20210216174919568

Future-Listener机制

image-20210216174950670

image-20210216175015709

举例说明:绑定端口是异步操作,当绑定操作处理完,将会调用相应的监听器处理逻辑

//绑定一个端口并且同步,生成了一个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服务

image-20210216180002024

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);
        }
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值