Netty使用与线程模型

Netty是一个用于快速开发高性能协议服务器和客户端的框架,基于NIO,提供易用性和高效性能。文章介绍了Netty与AIO的区别,强调其在分布式系统、RPC框架和游戏通信中的应用场景,以及其独特的线程模型和Pipeline设计。
摘要由CSDN通过智能技术生成

Netty介绍

官网Netty: 首页

        Netty是一个异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。

        Netty是NIO的客户端服务器框架,可以快速轻松地开发网络应用程序,例如协议服务器和客户端。它大大简化和简化了网络编程,例如 TCP 和 UDP 套接字服务器。

        “快速和简单”并不意味着生成的应用程序会遇到可维护性或性能问题。Netty 经过精心设计,积累了从许多协议(如 FTP、SMTP、HTTP 以及各种二进制和基于文本的遗留协议)的实施中获得的经验。因此,Netty 成功地找到了一种在不妥协的情况下实现易开发性、性能、稳定性和灵活性的方法。

Netty为什么使用NIO而不是AIO

        Netty不看重Windows上的使用,在Linux系统上,AIO的底层实现仍使用EPOLL,没有很好实现AIO,因此在性能上没有明显的优势,而且被JDK封装了一层不容易深度优化。
        AIO还有个缺点是接收数据需要预先分配缓存, 而不是NIO那种需要接收时才需要分配缓存, 所以对连接数量非常大但流量小的情况, 内存浪费很多。而且Linux上AIO不够成熟,处理回调结果速度跟不上处理需求。

Not faster than NIO (epoll) on unix systems (which is true)
There is no daragram suppport
Unnecessary threading model (too much abstraction without usage)

特点

设计

  • 适用于各种传输类型的统一 API - 阻塞和非阻塞套接字

  • 基于灵活且可扩展的事件模型,可以清晰地分离关注点

  • 高度可定制的线程模型 - 单线程、一个或多个线程池,如 SEDA

  • 真正的无连接数据报套接字支持(自 3.1 起)

易用性

  • 有据可查的 Javadoc、用户指南和示例

  • 没有其他依赖项,JDK 5 (Netty 3.x) 或 6 (Netty 4.x) 就足够了

    • 注意:某些组件(如 HTTP/2)可能有更多要求。 有关详细信息,请参阅要求页面

性能

  • 更高的吞吐量,更低的延迟

  • 减少资源消耗

  • 最大限度减少不必要的内存拷贝

安全

  • 完整的 SSL/TLS 和 StartTLS 支持

社区活跃

  • 早发布,经常发布

  • 作者自 2003 年以来一直在编写类似的框架,他仍然觉得您的反馈很宝贵!

Netty使用场景

  1. 互联网

    在分布式系统中,各节点之间通常需要进行远程服务调用,而高性能的RPC框架是其中的关键组件。Netty作为一种异步高性能的通信框架,常被用作RPC框架的底层通信组件。

    一些典型应用包括:

    • Dubbo:这是一个流行的分布式服务框架,主要用于RPC通信。Dubbo框架中的Dubbo协议负责节点间的通信,而Netty则作为其基础通信组件,为Dubbo提供异步和高性能的传输能力。

    • RocketMQ:这是阿里巴巴开发的分布式消息队列,其底层通信部分也使用了Netty。Netty在RocketMQ中充当基础通信组件,确保高性能的数据传输和节点间的内部通信。

    这些应用展现了Netty在分布式系统中作为通信基础的重要性,它提供了异步、可扩展和高性能的通信能力,广泛应用于RPC框架和分布式消息系统中。

  2. 游戏

    游戏行业中,Java语言在手游服务端和大型网络游戏中越来越受欢迎。作为高性能通信的基础组件,Netty提供了TCP/UDP和HTTP协议栈,成为支持游戏服务器和网络通信的重要工具。

  3. 大数据

    在大数据领域,Hadoop的高性能通信和序列化组件Avro的RPC框架默认使用Netty来进行节点间的通信。Avro的Netty Service是基于Netty框架经过二次封装实现的,提供了可靠且高效的跨节点通信能力。

 netty相关项目:Netty.docs: 相关项目

Netty使用示例

maven依赖:

官方推荐使用4.x

<dependency>

<groupId>io.netty</groupId>

<artifactId>netty-all</artifactId>

<version>4.1.42.Final</version>

</dependency>

服务端代码:

public class NettyServer {

    public static void main(String[] args) throws Exception {

        //创建两个线程组bossGroup和workerGroup, 子线程NioEventLoop的个数默认为cpu核数的两倍
        // bossGroup只是处理连接请求 ,workerGroup和客户端业务处理
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            //创建服务器端的启动对象
            ServerBootstrap bootstrap = new ServerBootstrap();
            //设置两个线程组
            bootstrap.group(bossGroup, workerGroup) 
                    //使用NioServerSocketChannel作为服务器的通道实现
                    .channel(NioServerSocketChannel.class) 
                    // 初始化服务器连接队列大小,服务端处理客户端连接请求是顺序处理的,所以同一时间只能处理一个客户端连接。
                    // 多个客户端同时来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        //创建通道初始化对象,设置初始化参数

                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            //对workerGroup的SocketChannel设置处理器
                            ch.pipeline().addLast(new NettyServerHandler());
                        }
                    });
            System.out.println("netty server start-->");
            //绑定端口并且同步, 生成了一个ChannelFuture异步对象,通过isDone()等方法可以判断异步事件的执行情况
            //启动服务器(并绑定端口),bind是异步操作,sync方法是等待异步操作执行完毕
            ChannelFuture cf = bootstrap.bind(8989).sync();
            //给ChannelFuture 注册监听器,监听关心的事件
            cf.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    if (cf.isSuccess()) {
                        System.out.println("listen 8989 sucess");
                    } else {
                        System.out.println("listen 8989 fail");
                    }
                }
            });
            //对通道关闭进行监听,closeFuture异步操作,监听通道关闭
            //通过sync方法同步等待通道关闭处理完毕,这里会阻塞,等待通道关闭完成
            cf.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}


/**
 * 自定义Handler需要继承netty规定好的某个HandlerAdapter(Handler规范)
 */
public class NettyServerHandler extends ChannelInboundHandlerAdapter {

    /**
     * 读取客户端发送的数据
     *
     * @param ctx 上下文对象, 含有通道channel,管道pipeline
     * @param msg 就是客户端发送的数据
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("服务器读取线程 " + Thread.currentThread().getName());
        //Channel channel = ctx.channel();
        //本质是双向链接, 出站入站
        //ChannelPipeline pipeline = ctx.pipeline(); 
        //将 msg 转成一个 ByteBuf,类似NIO 的 ByteBuffer
        ByteBuf buf = (ByteBuf) msg;
        System.out.println("客户端发送消息是:" + buf.toString(CharsetUtil.UTF_8));
    }

    /**
     * 数据读取完毕处理方法
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ByteBuf buf = Unpooled.copiedBuffer("HelloClient", CharsetUtil.UTF_8);
        ctx.writeAndFlush(buf);
    }

    /**
     * 处理异常, 一般是需要关闭通道
     *
     * @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 Exception {
        //客户端需要一个事件循环组
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            //创建客户端启动对象Bootstrap
            Bootstrap bootstrap = new Bootstrap();
            //设置相关参数
            //设置线程组
            bootstrap.group(group) 
                    .channel(NioSocketChannel.class) 
                      // 使用 NioSocketChannel 作为客户端的通道实现
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel channel) throws Exception {
                            //加入处理器
                            channel.pipeline().addLast(new NettyClientHandler());
                        }
                    });
            System.out.println("netty client start");
            //启动客户端去连接服务器端
            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8989).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 {
        ByteBuf buf = Unpooled.copiedBuffer("HelloServer", CharsetUtil.UTF_8);
        ctx.writeAndFlush(buf);
    }

    //当通道有读取事件时会触发,即服务端发送数据给客户端
    @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();
    }
}

Netty框架的目标是将业务逻辑与底层网络编程解耦,使开发者能够专注于业务开发,而无需编写繁杂的NIO网络处理代码。


Netty线程模型

        如下图:

        Netty框架通过抽象出两组线程池来管理网络操作,即BossGroupWorkerGroup。以下是Netty的结构和运作方式:

  1. BossGroup和WorkerGroupBossGroup负责接受客户端的连接,WorkerGroup负责网络数据的读写操作。两者都属于NioEventLoopGroup类型。

  2. NioEventLoopGroup:这是一个事件循环线程组,包含多个事件循环线程。每个事件循环线程被称为NioEventLoop

  3. NioEventLoop:每个NioEventLoop都包含一个Selector,用于监听注册在其上的SocketChannel的网络通信事件。

  4. BossGroup的操作流程:每个Boss NioEventLoop在内部循环中执行以下三步:

    • 处理accept事件,与客户端建立连接并生成NioSocketChannel
    • 将生成的NioSocketChannel注册到Worker NioEventLoop上的Selector
    • 执行任务队列中的任务(runAllTasks)。
  5. WorkerGroup的操作流程:每个Worker NioEventLoop在循环中执行以下步骤:

    • 轮询自己Selector上的所有NioSocketChannel,处理readwrite事件。
    • 根据这些事件处理业务逻辑。
    • 运行任务队列中的任务,允许耗时的业务操作在任务队列中异步处理,以避免影响主线的I/O操作。
  6. Pipeline和HandlerWorker NioEventLoop在处理NioSocketChannel的业务时,使用了Pipeline(管道)。这个管道中维护了一系列的处理器(Handler),用于处理Channel中的数据,提供灵活的处理能力。

        通过这样的设计,Netty实现了高性能的网络操作,并将业务逻辑与底层网络编码分离,使开发者能够更专注于业务处理而非网络基础设施的编写。

  • 45
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Netty的多线程模型与主从多线程模型有一些区别。Netty线程模型基于主从多Reactor模型,其中一个线程负责处理OP_ACCEPT事件,而拥有CPU核数的两倍的IO线程负责处理读写事件。一个通道的IO操作会绑定在一个IO线程中,而一个IO线程可以注册多个通道。在一个IO线程中,所有通道的事件是串行处理的。\[1\] 相比之下,主从多线程模型中,一个线程负责监听客户端请求,而多个线程负责事件处理和转发,还有多个线程负责逻辑处理。每个客户端都分配独立的线程,该线程负责全部的工作,包括监听、读取、处理和响应。而在Netty的多线程模型中,一个IO线程可以处理多个通道的IO操作。\[2\] 此外,Netty的多线程模型还可以通过指定其他线程池来处理编码、解码等操作,以及单独开启业务线程池来处理业务逻辑。这样可以避免线程切换,提高性能。而主从多线程模型中,所有的功能都在子线程中进行处理。\[2\] 总的来说,Netty的多线程模型相对于主从多线程模型更加灵活和高效,能够更好地处理并发请求。 #### 引用[.reference_title] - *1* *3* [【9. Netty Reactor模型之主从多线程模型】](https://blog.csdn.net/W664160450/article/details/123418237)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [面试官:Netty线程模型可不只是主从多Reactor这么简单](https://blog.csdn.net/prestigeding/article/details/112405349)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值