Netty学习篇①

什么是netty

Netty封装了JDK自带的NIO,运用起来更加简单快速,Netty是一个异步事件驱动的网络应用框架,让开发更加简便

Netty相比JDK自带的NIO的优点

  • Netty的api调用简单,JDK编程复杂
  • Netty支持IO模型切换(只需修改部分参数即可)
  • Netty自带拆解包及异常监控,让你更加专注于业务的开发
  • Netty底层采用了Reactor模型,更高效的处理并发
  • 自带各种协议栈让你处理任何协议都不需要亲自动手
  • 社区活跃,组件健壮

线程模型-reactor模型

  • 单线程模型:顾名思义就是只有一个线程去完成所有的操作。在netty中的应用:

    // 服务端主线程
    EventLoopGroup bossGroup = new NioEventLoopGroup(1);
    // 服务端实际操作业务线程
    EventLoopGroup workGroup = new NioEventLoopGroup();
    try {
        ServerBootstrap sb = new ServerBootstrap();
        // 只配置一个线程组去操作
        sb.group(bossGroup)
            .channel(NioServerSocketChannel.class)
            .option(ChannelOption.SO_BACKLOG, 1024)
            .childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel socketChannel) throws Exception {
                    socketChannel.pipeline()
                        .addLast(new StringDecoder())
                        .addLast(new StringEncoder())
                        .addLast(new PongServerHandler());
    
                }
            });
        // 绑定端口,同步等待成功
        ChannelFuture f = sb.bind(port).sync();
    
        // 等待服务器监听端口关闭
        f.channel().closeFuture().sync();
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        bossGroup.shutdownGracefully();
        workGroup.shutdownGracefully();
    }
    

    我们可以查看下源码,就可以很清楚的明白

    @Override
    public ServerBootstrap group(EventLoopGroup group) {
        return group(group, group);
    }
    
    public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
        super.group(parentGroup);
        if (childGroup == null) {
            throw new NullPointerException("childGroup");
        }
        if (this.childGroup != null) {
            throw new IllegalStateException("childGroup set already");
        }
        this.childGroup = childGroup;
        return this;
    }
    

    相当于只有一个线程组它既是parentGroup,同时也是childGroup,所有的事都是由它一个人完成。单线程的通病,并发高的情况下效率低

  • 多线程模型: 设置多个线程去处理

    // 代码和单线程差不多,只不过在于线程数量上的差别
    // 服务端主线程(里面包含5个线程的线程池)
    EventLoopGroup bossGroup = new NioEventLoopGroup(5);
    
  • 主从线程模型: 配置一主一从线程池(bossGroup、childGroup)

    EventLoopGroup bossGroup = new NioEventLoopGroup(1);
    // 服务端实际操作业务线程
    EventLoopGroup workGroup = new NioEventLoopGroup();
    ServerBootstrap sb = new ServerBootstrap();
    // 主从线程池分工合作,一个负责处理请求,一个负责处理handler
    sb.group(bossGroup, workGroup)
    

服务器启动类ServerBootstrap

serverBootstrap:netty服务启动入口,组合group、channel、handler
  1. 配置线程组(bossGroup、workGroup)
  2. 配置非阻塞运输通道(NioServerSocketChannel)
  3. 设置option,配置TCP参数,为每个channel

EventLoopGroup

EventLoopGroup(线程池)包含多个EventLoop(线程),用于管理维护EventLoop,常用的非阻塞NioEventLoopGroup
EventLoopGroup类图:

avatar

EventLoopGroup初始化线程

int nThreads = CPU核数 * 2;
// 我是四核CPU
EventLoopGroup workGroup = new NioEventLoopGroup(nThreads);
具体代码底层代码如下:
/**
* nThreads:自定义线程的数量
* executor:线程的顶级接口类
* selectorProvider: IO多路复用
* selectStrategyFactory:策略工厂
* RejectedExecutionHandlers: 线程池满的拒绝策略
*/
public NioEventLoopGroup(int nThreads, Executor executor, final SelectorProvider selectorProvider,
                         final SelectStrategyFactory selectStrategyFactory) {
    super(nThreads, executor, selectorProvider, selectStrategyFactory, RejectedExecutionHandlers.reject());
}


// EventLoopGroup线程由MultithreadEventLoopGroup来操作
protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
    super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
}

// DEFAULT_EVENT_LOOP_THREADS为默认线程数量,此初始化代码块
// NettyRuntime.availableProcessors() * 2
static {
    DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
        "io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));

    if (logger.isDebugEnabled()) {
        logger.debug("-Dio.netty.eventLoopThreads: {}", DEFAULT_EVENT_LOOP_THREADS);
    }
}


// 获取运行时可用的处理器数量 Runtime.getRuntime().availableProcessors()
@SuppressForbidden(reason = "to obtain default number of available processors")
synchronized int availableProcessors() {
    if (this.availableProcessors == 0) {
        final int availableProcessors =
            SystemPropertyUtil.getInt(
            "io.netty.availableProcessors",
            Runtime.getRuntime().availableProcessors());
        setAvailableProcessors(availableProcessors);
    }
    return this.availableProcessors;
}
NioEventLoopGroup类图

avatar

EventLoopGroup、EventLoop、Channel的关系

  • EventLoopGroup相当于线程池里面包含至少一个EventLoop
  • 一个Channel在生命周期内只会在一个EventLoop上注册
  • 一个EventLoop在运行过程中可以服务多个Channel(连接、管道)

Channel、ChannelHandler、ChannelPipeline

1. Channel:是netty网络通信的主体,他主要负责客户端和服务端建立的一个连接通道,进行通信和数据操作等

2. ChannelHandler:连接实际的业务逻辑处理,channelHandler之间的传递主要通过channelHandlerContext.fireChannelXXX()方法结束当前handler调用下一个handler;
主要分为ChannelInboundHandler(入站)、ChannelOutboundHandler(出站)的实现

3. ChannelPipeline:相当于流水线,负责管理ChannelHandler的有序容器

4. ChannelHandlerContext:业务逻辑处理上下文,连接ChannelHandler和ChannelPipeline的桥梁,当一个ChannelHandler处理完成之后会通知channelhandlerContext,它会寻找pipeline中的下一个channelHandler。底层是双向链表,next/prev分别是后继节点和前驱节点;


tips: channelHandlerContext.fireChannelXXX()方法 
在channelRead0中fireChannelRead(Object msg);
在channelReadComplete中调用fireChannelReadComplete();
.
.
等等对应上即可
Channel、ChannelHandler、ChannelHandlerContext的流程图
一个Channel包含一个ChannelPipeline,所有的ChannelHandler都会有序的加入到ChannelPipeline中,创建Channel时会自动创建一个ChannelPipeline,每个Channel都有一个管理它的pipeline,这关联是永久性的。

avatar

Selector

Selector是一个多路复用器,它负责管理被注册到其上的SelecttableChannel,Selector的实现根据操作系统的不同而不同,目前多路复用IO常用的主要包括:select、poll、epoll、kqueue

select: 有最大连接数限制:1024,采用轮询的方式处理IO操作,内核想将数据传递到用户态,需要将数据从内核中拷贝到用户态,这个过程非常的耗时。当socket活跃增多轮询的速度会变慢,性能会下降
poll:与select类似,唯一的差别就是没有最大连接数的限制,踏实基于链表来存储,依旧是采用轮询的方式处理活跃的IO操作
epoll:连接有上限,但是很大;1G内存的机器可以打开10万左右的连接,以此类推;epoll不再像select、poll一样轮询所有的活跃IO操作,而是等待活跃的连接调用callback在处理,epoll的内核和用户共享一块内存,因此内存数据和用户数据是共享的
根据实际情况合理利用策略
select低效是由于在大量连接的情况下轮询,少量的连接时候效率不比其他的差;

表面上看epoll的性能最好,大量请求下效率最高,如果同一时间大量活跃连接都处于callback状态下的话,性能不比select好,毕竟callback通知机制需要花时间

优点
利用更少的线程来管理处理通道,减少了多线程来回上下文切换消耗的资源
Netty中的应用
Netty在实例EventLoopGroup线程组的时候会初始化EventLoop组并且绑定一个SelectorProvider,open一个selector来管理通道
// 实例化EventLoopGroup
public NioEventLoopGroup(int nThreads, Executor executor) {
        this(nThreads, executor, SelectorProvider.provider());
}

// 在实例化Eventloop线程时同时绑定Selector用来管理EventLoop中注册的Channel通道
@Override
protected EventLoop newChild(Executor executor, Object... args) throws Exception {
        return new NioEventLoop(this, executor, (SelectorProvider) args[0],
            ((SelectStrategyFactory) args[1]).newSelectStrategy(),
                                (RejectedExecutionHandler) args[2]);
}
/**
  * 返回系统默认的selector给调用的Java虚拟机
 */
public static SelectorProvider provider() {
    synchronized (lock) {
        if (provider != null)
            return provider;
        return AccessController.doPrivileged(
            new PrivilegedAction<SelectorProvider>() {
                public SelectorProvider run() {
                    if (loadProviderFromProperty())
                        return provider;
                    if (loadProviderAsService())
                        return provider;
                    provider = sun.nio.ch.DefaultSelectorProvider.create();
                    return provider;
                }
            });
    }
}

posted @ 2019-10-18 18:38  荷塘月色0519  阅读(286)  评论(2编辑  收藏  举报