• 并发编程 Outline of Netty


    并发编程- Outline of Netty

    在分布式架构中网络通讯是一个非常重要的东西,因为不管架构多么厉害,网络通讯的性能还是直接影响用户体验的一个重要因素。在java中我们有很多可以处理网络的框架,和一些基础的东西,比如NIO、BIO、Socket, 这些我们在前面都有聊过,但是当我们基于这些东西去开发的时候,我们会发现,他们提供的API比较复杂,那有些开源框架就应运而生,比如Mina,Netty等。但凡是中间件就有涉及网络通讯,比如zk、dubbo、redis、rocketmq,etc.他们有的底层就是使用的Netty, 并且在分布式架构下,网络的重要性也可想而知了。所以我就准备和大家聊聊Netty这个东西。

    网络IO的发展

    【BIO(同步阻塞IO)】:客户端发送一个请求到服务端,服务端收到请求后需要阻塞,直到客户端写到数据到服务端后,服务端才能响应。那么我们服务端能同时处理的数据是有限的。(因为需要排队)

    【NIO】:通过线程去轮询,没有连接可以使用的时候就直接返回。这就带来了性能问题(从用户态到内核态的转变)

      多路复用(epoll):当客户端连接进到服务端,就把连接注册到selector中,当selector中的任何一个channel(任何一个客户端连接)发生了连接事件,或者io事件的时候,都会响应。响应方式就是我们通过一个工作线程去轮询就绪的channel列表,然后进行相关的处理。

    【Reactor模型:基于Nio的底层实现的一种高性能的设计模式,他的核心点就是把相应的机制和业务进行了分离,这样我们就可以通过一个线程或者多个线程去处理IO事件,这样的话就可以进行灵活的扩展.我们聊redis的时候也聊过这个。但是前面聊的是【单线程单reactor】模型,弊病就是当我们的连接进来的时候,还是要等待处理,在reactor处。、

    • 【多线程单reactor模型】:当服务端连接过来后,我们把连接使用线程池进行异步处理,这里就不用阻塞
    • 【多线程多reactor模型】:当连接进来的时候,我们的main reactor 去接收客户端的连接然后把这些连接分发给不同的subreactor进行IO处理

    有几个容易混淆的点,同步非阻塞、异步非阻塞 (阻塞不阻塞指的是IO层面的,同步非同步指的是应用层面的)

    Netty的底层是针对Nio做的一个封装,提供了更加简单的开发方式,之前我们要完成一个多线程多reacto可能需要多个class,而在netty中可能就十几行代码。

    Outline of Netty

    Netty提供了上述三种Reactor模型的支持,并且相比于NIO原生API,它有以 下特点:

    • 提供了高效的I/O模型、线程模型和时间处理机制 提供了非常简单易用的API,相比NIO来说,针对基础的Channel、Selector、Sockets、Buffers等 api提供了更高层次的封装,屏蔽了NIO的复杂性 对数据协议和序列化提供了很好的支持 稳定性.
    • Netty修复了JDK NIO较多的问题,比如select空转导致的cpu消耗100%、TCP断线重连、 keep-alive检测等问题。
    • 性能层面的优化,作为网络通信框架,需要处理大量的网络请求,必然就面临网络对象需要创建和 销毁的问题,这种对JVM的GC来说不是很友好,为了降低JVM垃圾回收的压力,引入了两种优化机 制 对象池复用, 零拷贝技术

    Usage of Netty

    引入Netty的包

    <dependency>
        <groupId>io.netty</groupId>
        <artifactId>netty-all</artifactId>
    </dependency>
    View Code

    使用netty实现一个多线程多reactor模型

    整体流程:

    • 当客户端进来的时候,首先通过mainGroup去接收客户端的连接
    • 然后把连接注册到EventLoopGroup中,这里面有多个eventLoop,每个eventloop都代表的是一个线程,也就是说客户端会注册到其中的某一个eventloop中。
    • 当某个eventLoop有IO事件的时候,会把请求发送给channel pipeline 进行处理
    • channel pipeline 可以多个,我们可以一直添加,他会按照我们的添加的顺序执行。  
      •   channel  pipeline 分为两种入栈(inbound)和出栈(outbound),也就是读取和输出。
    // 具体处理IO的handler
    public class NormalMessageHandler extends ChannelInboundHandlerAdapter {
        //当请求过来的时候,会调用这个方法对数据进行读取
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            ByteBuf in = (ByteBuf) msg;
            byte[] req=new byte[in.readableBytes()];
            in.readBytes(req);
            System.out.println("服务端收到的数据"+new String(req, StandardCharsets.UTF_8));
            ByteBuf resp= Unpooled.copiedBuffer("Service receive message success".getBytes());
            ctx.write(resp);
        }
    
        //把数据写回到客户端,然后监听客户端关闭事件
        @Override
        public void channelReadComplete(ChannelHandlerContext ctx) {
            ctx.write(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
        }
    
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            super.exceptionCaught(ctx, cause);
        }
    }
    View Code
    public class NettyBasicServerExample {
        // 开发一个多reactor,多线程的模型
        public static void main(String[] args) {
            // 主线程(相当于我们的main reactor)
            EventLoopGroup mainGroup=new NioEventLoopGroup();
            //表示多个工作线程组 (相当于我们的sub reactor) 注册我们的事件
            EventLoopGroup workGroup=new NioEventLoopGroup(4);
            //构建netty server 的 API
            ServerBootstrap serverBootstrap=new ServerBootstrap();
            // 使用Nio的API并且构建Handler(这个handler会对消息进行处理)
            serverBootstrap.group(mainGroup,workGroup)
                    // 指定使用的模型
                    .channel(NioServerSocketChannel.class)
                    //具体的工作处理类,处理相关channel io事件
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) {
                            //处理io事件
                            socketChannel.pipeline().addLast(new NormalMessageHandler());
                        }
                    });
            // 同步阻塞等到客户端回调
            try {
                ChannelFuture sync = serverBootstrap.bind(8080).sync();
                System.out.println("Netty server started successfully and the port 8080 has been listened");
                // 同步等待服务端端口关闭
                sync.channel().closeFuture().sync();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                // 释放资源
                mainGroup.shutdownGracefully();
                workGroup.shutdownGracefully();
            }
        }
    }
    View Code

    more detail about netty

    事件调度器(是通过Reactor线程模型对各类事件进行聚合处理,通过Selector主循环线程集成多种事件 (I/O时间、信号时间),当这些事件被触发后,具体针对该事件的处理需要给到服务编排层中相关的 Handler来处理)

    事件调度器的核心组件有两个:

    • EventLoopGroup:实际上就相当于是线程池
    • EventLoop:这就相当于线程池中的线程

    总的来说:

    • 一个EventLoopGroup中有多个eventloop,这些个eventloop用来处理Channel生命周期内所有的 I/O事件,比如accept、connect、read、write等
    • EventLoop同一时间会与一个线程绑定,每个EventLoop负责处理多个Channel
    • 每新建一个Channel,EventLoopGroup会选择一个EventLoop进行绑定,该Channel在生命周期 内可以对EventLoop进行多次绑定和解绑。
    • 我们可以简单的把EventLoopGroup当成是Netty中Reactor线程模型的具体实现,我们可以通过配 置不同的EventLoopGroup使得Netty支持多种不同的Reactor模型。
      • 单线程模型】:EventLoopGroup只包含一个EventLoop,Boss和Worker使用同一个 EventLoopGroup
      • 多线程模型】:EventLoopGroup包含多个EventLoop,Boss和Worker使用同一个 EventLoopGroup。
      • 主从多线程模型】:EventLoopGroup包含多个EventLoop,Boss是主Reactor,Worker是从 Reactor。他们分别使用不同的EventLoopGroup主Reactor负责新的网络连接Channel的创 建(也就是连接的事件)主Reactor收到客户端的连接后,交给从Reactor来处理。

    服务编排层就是I/O事件触发后,需要有一个Handler来处 理,服务编排层就可以通过一个Handler处理链来实现网络事件的动态编排和有序的传播)

    它包含三个组件:

    ChannelPipeline】:它采用了双向链表将多个Channelhandler链接在一起,当I/O事件触发时, ChannelPipeline会依次调用组装好的多个ChannelHandler,实现对Channel的数据处理。 ChannelPipeline是线程安全的,因为每个新的Channel都会绑定一个新的ChannelPipeline一个 ChannelPipeline关联一个EventLoop而一个EventLoop只会绑定一个线程

    ChannelHandler】: 针对IO数据的处理器,数据接收后,通过指定的Handler进行处理

    ChannelHandlerContext】,ChannelHandlerContext用来保存ChannelHandler的上下文信息,也 就是说,当事件被触发后,多个handler之间的数据,是通过ChannelHandlerContext来进行传递 的每个ChannelHandler都对应一个自己的ChannelHandlerContext,它保留了ChannelHandler所 需要的上下文信息,多个ChannelHandler之间的数据传递,是通过ChannelHandlerContext来实 现的。

    总的工作机制:

    服务单启动初始化Boss和Worker线程组,Boss线程组负责监听网络连接事件,当有新的连接建立 时,Boss线程会把该连接Channel注册绑定到Worker线程 Worker线程组会分配一个EventLoop负责处理该Channel的读写事件,每个EventLoop相当于一 个线程。通过Selector进行事件循环监听。 当客户端发起I/O事件时,服务端的EventLoop讲就绪的Channel分发给Pipeline,进行数据的处理 数据传输到ChannelPipeline后,从第一个ChannelInBoundHandler进行处理,按照pipeline链逐 个进行传递 服务端处理完成后要把数据写回到客户端,这个写回的数据会在ChannelOutboundHandler组成 的链中传播,最后到达客户端

  • 相关阅读:
    Anaconda安装之路——坑呀!
    初读《企业应用架构模式》——阅读笔记1
    《需求工程》阅读笔记3
    codeforces 432D. Prefixes and Suffixes(后缀数组)
    hdu 6096String(trie树)
    uva 1349 Optimal Bus Route Design(拆点,费用流)
    数据结构c语言
    六个排序算法
    c无聊编程
    文件写入与文件读取
  • 原文地址:https://www.cnblogs.com/UpGx/p/16006635.html
Copyright © 2020-2023  润新知