并发编程- 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组成 的链中传播,最后到达客户端

posted @ 2022-03-14 23:28  UpGx  阅读(55)  评论(0编辑  收藏  举报