Netty学习笔记

Netty的基本组件

  1. 服务端
    1. Accept的时候创建线程
    2. ClientHandler处理请求数据时创建线程
  2. 客户端 (创建线程 处理发送数据)

image.png

  1. NioEventLoop. – Channel. --ByteBuf. – ChannelHandler.
  2. Netty基本主件
    1. NioEventLoop. --> Thread
      1. 服务端接收客户端连接的线程
      2. 处理客户端读写的线程
    2. Channel --> Socket (Channel是对Socket的封装,对数据的读写)
    3. ByteBuf --> IO Bytes
    4. Pipeline --> Logic Chain
    5. ChannelHandler --> Logic
  3. NioEventLoop # run
    1. select(wakenUp.getAndSet(false))
    2. processSelectedKeys()
  4. NioEventLoop # processSelectedKey
    1. if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) { 有一个连接进来
    2. NioMessageUnsafe 对应新连接进来的处理
    3. NioByteUnsafe 对应数据流的处理
    4. NioMessageUnsafe # read
    5. int localRead = doReadMessages(readBuf);
      1. NioServerSocketChannel # doReadMessages
      2. SocketChannel ch = javaChannel().accept();
      3. buf.add(new NioSocketChannel(this, ch))
  5. NioSocketChannel 构造方法
    1. AbstractChannel # 构造方法
    2. pipeline = newChannelPipeline() pipeline的创建. ==> new DefaultChannelPipeline(this)
      1. tail = new TailContext(this);
      2. head = new HeadContext(this);
  6. ChannelHandler 时间的回调
    1. handlerAdded 添加到pipeline的回调
    2. exceptionCaught. 异常捕获

Netty服务端启动

public static void main(String[] args) throws Exception {
    //accept接收请求
    EventLoopGroup bossGroup = new NioEventLoopGroup(1);
    //处理数据流
    EventLoopGroup workerGroup = new NioEventLoopGroup();

    try {
        ServerBootstrap b = new ServerBootstrap();
        //绑定两大基本线程
        b.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                //给客户端连接设置tcp基本属性
                .childOption(ChannelOption.TCP_NODELAY, true)
                //创建客户端连接时绑定基本属性
                .childAttr(AttributeKey.newInstance("childAttr"), "childAttrValue")
                //服务端启动过程中的逻辑
                .handler(new ServerHandler())
                //添加childHandler,添加各种Handler
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    public void initChannel(SocketChannel ch) {
                        ch.pipeline().addLast(new AuthHandler());
                        //..

                    }
                });
        //端口绑定
        ChannelFuture f = b.bind(7777).sync();

        f.channel().closeFuture().sync();
    } finally {
        bossGroup.shutdownGracefully();
        workerGroup.shutdownGracefully();
    }
}
  1. 服务端的socket在哪里初始化
  2. 在哪里accept连接
  3. 4个过程
    1. 创建服务端Channel
    2. 初始化服务端Channel
    3. 注册selector(attachment)
    4. 端口绑定
  4. image.png
    1. AbstractBootstrap # bind
    2. final ChannelFuture regFuture = initAndRegister(); 创建服务端channel
    3. channelFactory.newChannel()
    4. # newChannel. ==> clazz.newInstance()
      
    5. channel(NioServerSocketChannel.class)
    6. channelFactory(new ReflectiveChannelFactory(channelClass))
  5. image.pngimage.png
    1. NioServerSocketChannel # 构造方法
    2. newSocket(DEFAULT_SELECTOR_PROVIDER)
    3. provider.openServerSocketChannel()
    4. new NioServerSocketChannelConfig
    5. AbstractNioChannel # 构造方法. ==> ch.configureBlocking(false)
    6. AbstractChannel 对Client和Server Channel的抽象
    7. unsafe 对tcp的读写操作
  6. 初始化服务端Channelimage.png
    1. image.png
    2. AbstractBootstrap # initAndRegister
    3. init(channel)
    4. ServerBootsStrap # init
    5. channel.config().setOptions(options); 和tcp相关的属性
    6. channel.attr(key).set(e.getValue()) 把用户自定的属性绑定到channel上
    7. currentChildOptions = childOptions.entrySet().toArray(. accept到服务端创建的客户端用的属性
    8. currentChildAttrs = childAttrs.entrySet().toArray
    9. p.addLast(new ChannelInitializer() { 添加pipeline
    10. ChannelHandler handler = config.handler(); 获取到用户自定义到BootStrap上的Handler
    11. pipeline.addLast(handler) 将用户自定义的Handler添加到pipeliine
    12. pipeline.addLast(new ServerBootstrapAcceptor 添加连接器,接收到连接后利用这些属性对新连接进行配置
    13. image.png
    14. server 传播read事件会从head开始到ServerBootstrapAcceptor,再到tail
    15. currentChildGroup
    16. currentChildHandler==> 主程序 childHandler(new ChannelInitializer() { 添加childHandler,添加各种Handler,用户自定义的handler
    17. currentChildOptions
    18. currentChildAttrs.
  7. 注册Selector
    1. image.png
    2. AbstractBootstrap # initAndRegister
    3. ChannelFuture regFuture = config().group().register(channel). 注册
    4. AbstractChannel # register(EventLoop eventLoop, final ChannelPromise promise)
    5. AbstractChannel.this.eventLoop = eventLoop; io事件相关操作eventLoop处理
    6. register0 实际注册
    7. doRegister() 调用jdk底层注册
      1. AbstractNioChannel # doRegister
        1. selectionKey = javaChannel().register(eventLoop().selector, 0, this)
          1. javaChannel() 获取到NipSocketServerChannel
          2. 0 表示不关心任何事件
          3. this. attachment 附件信息,通过这个将服务端的channel绑定到selector上
          4. selector轮询到读写事件,直接获取到channel,通过netty作事件的传播
    8. pipeline.invokeHandlerAddedIfNeeded() 回调注册成功. 触发ChannelInboundHandlerAdapter的handlerAdded方法,自定义的ServerHandler
    9. pipeline.fireChannelRegistered(). 传播事件 触发ChannelInboundHandlerAdapter的channelRegistered方法,自定义的ServerHandler
  8. 端口绑定image.png
    1. AbstractBootstrap # doBind
    2. doBind0(regFuture, channel, localAddress, promise)
    3. AbstractChannel # bind(final SocketAddress localAddress, final ChannelPromise promise)
    4. doBind(localAddress) jdk底层的绑定
      1. NioServerSocketChannel # doBind
      2. javaChannel().bind() jdk socket底层的绑定
    5. if (!wasActive && isActive()) { 之前不是active,绑定端口后是active
    6. pipeline.fireChannelActive(). 传播事件(注册accept事件)
    7. DefaultChannelPipeline # channelActive(ChannelHandlerContext ctx)
      1. ctx.fireChannelActive()
      2. readIfIsAutoRead()
      3. channel.read()
      4. tail.read();
      5. AbstractChannel # beginRead. # doBeginRead()
      6. AbstractNioChannel # doBeginRead()
      7. selectionKey.interestOps(interestOps | readInterestOp). 注册 accept事件到selector上
      8. NioServerSocketChannel # 构造函数super(null, channel, SelectionKey.OP_ACCEPT). 设置 readInterestOp 为SelectionKey.OP_ACCEPT
  9. image.png

NioEventLoop

问题(概括)

  1. 默认情况下,Netty服务端起多少线程,何时启动?
  2. Netty是如何解决jdk空轮询bug的
  3. Nett y如何保证异步串行无锁化
  4. image.png

NioEventLoop创建

  1. image.png
  2. NioEventLoopGroup # 构造
  3. this(nThreads, executor, SelectorProvider.provider())
    1. nThreads 线程数 executor 线程池 Selector 选择器
    2. MultithreadEventLoopGroup # 构造方法
    3. nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads 如果nThreads --0,则给默认值
      1. DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(“io.netty.eventLoopThreads”, Runtime.getRuntime().availableProcessors() * 2)); 2倍的cpu核心数
    4. this(nThreads, executor, DefaultEventExecutorChooserFactory.INSTANCE, args)
      1. executor = new ThreadPerTaskExecutor(newDefaultThreadFactory()); 线程创建器
        1. ThreadPerTaskExecutor 线程池
          1. ThreadPerTaskExecutor # execute ==> threadFactory.newThread(command).start(); 每次执行任务都会创建一个线程实体
          2. NioEventLoop 线程命名规则nioEventLoop-1-xx
          3. MultithreadEventExecutorGroup # newDefaultThreadFactory
          4. toPoolName(poolType) 线程的名称
          5. String poolName = StringUtil.simpleClassName(poolType) 获取到简单类名
          6. prefix = poolName + ‘-’ + poolId.incrementAndGet() + ‘-’;
          7. DefaultThreadFactory # newThread(Runnable r) ==> prefix + nextId.incrementAndGet()
          8. new FastThreadLocalThread(threadGroup, r, name) 对threadLocal的操作进行了优化
            1. InternalThreadLocalMap threadLocalMap
      2. children = new EventExecutor[nThreads]
      3. for (int i = 0; i < nThreads; i ++) {
      4. children[i] = newChild(executor, args)
      5. chooser = chooserFactory.newChooser(children)

newChild

  1. image.png
  2. MultithreadEventExecutorGroup 构造
  3. children[i] = newChild(executor, args)
  4. NioEventLoopGroup # newChild
    1. new NioEventLoop # 构造函数
      1. super(parent, executor, false, DEFAULT_MAX_PENDING_TASKS, rejectedExecutionHandler)
        1. this.executor = ObjectUtil.checkNotNull(executor, “executor”); 保存线程执行器,创建NioEventLoop底层线程会用到
        2. taskQueue = newTaskQueue(this.maxPendingTasks). 外部线程在执行netty任务时,如果判断不是在NioEventLoop对应的线程执行,会放在任务队列中,由NioEventLoop对应的线程执行
          1. PlatformDependent.newMpscQueue(maxPendingTasks)
          2. selector = openSelector(); 一个NioEventLoop和一个Selector绑定
  5. chooserFactory.newChooser(). 创建线程选择器(轮询取值)
    1. MultithreadEventExecutorGroup # next ==> return chooser.next()
    2. image.png
    3. image.png
    4. newChooser. ==> PowerOfTowEventExecutorChooser, GenericEventExecutorChooser ==>next方法
  6. NioEventLoop 启动触发器
    1. 服务端启动绑定端口
    2. 新连接接入通过chooser绑定一个NioEventLoop

NioEventLoop启动

  1. 服务端启动绑定端口
  2. image.png
  3. 主线程调用bind方法,将实际绑定的流程封装为一个task,调用服务端channel的execute方法具体执行,判断不是nio线程,会创建新的线程
  4. AbstractBootstrap # doBind0.
  5. channel.eventLoop().execute
  6. channel.bind(localAddress, promise) 绑定端口
  7. SingleThreadEventExecutor # execute
  8. boolean inEventLoop = inEventLoop(). 是否在本线程内执行
  9. inEventLoop(Thread.currentThread())
  10. thread == this.thread. this.thread 为null,所以不相等
  11. startThread(); 创建线程
  12. SingleThreadEventExecutor # doStartThread. 创建新的线程执行run方法
  13. thread = Thread.currentThread() 将新创建的线程绑定到NioEventLoop的thread对象,创建的线程FastThreadLocalThread.
  14. SingleThreadEventExecutor.this 对应的就是NioEventLoop
  15. run() 启动线程

NioEventLoop # run执行

  1. image.png
  2. SingleThreadEventExecutor.this.run()
    1. select(wakenUp.getAndSet(false))
    2. if (ioRatio == 100) {. 决定下边两个方法的执行时间 ,ioRatio 默认值为50
    3. processSelectedKeys(). 处理io相关的逻辑
    4. final long ioTime = System.nanoTime() - ioStartTime; 处理io相关逻辑用掉的时间
    5. ioTime * (100 - ioRatio) / ioRatio 处理taskQueue中任务能用的时间,默认和ioTime一样的时间
    6. runAllTasks(). 执行外部线程扔到taskQueue中的任务

select()

  1. image.png
    1. NioEventLoop # run()
    2. select(wakenUp.getAndSet(false)). wakenUp.getAndSet(false) 设置 selector不是唤醒状态
    3. selector.selectNow(). 超时并且没有执行过select操作,执行非阻塞的select
    4. hasTasks() 异步任务队列中是否有任务, wakenUp.compareAndSet(false, true) 设置weakUp为true,唤醒状态
      1. 如果有任务需要执行 selector.selectNow() ==> break ,调用非阻塞的select,返回
    5. int selectedKeys = selector.select(timeoutMillis); 进行阻塞式的select
    6. selectCnt ++; 执行次数 + 1
    7. if (selectedKeys != 0 || oldWakenUp || wakenUp.get() || hasTasks() || hasScheduledTasks()) {. ==> break;
      1. Selected something,waken up by user, the task queue has a pending task, a scheduled task is ready for processing 满足以上条件就终止
  2. 避免jdk空轮询的bug
    1. long time = System.nanoTime() 当前的时间
      1. if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) {. ==> selectCnt = 1
        1. 当前时间 - 开始执行的时间 > 超时时间,恢复执行次数 = 1
        2. 如果当前时间 - 开始执行的时间< 超时时间,说明selector.select(timeoutMillis)没有阻塞住,代表一次空轮询
    2. else if (selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) 如果空轮询的时间> 允许空轮询的次数,默认为512
      1. rebuildSelector() 把老的selector上的selectKey注册到新的selector上
        1. newSelector = openSelector()
        2. for (SelectionKey key: oldSelector.keys()) { 遍历之前selector的key
        3. int interestOps = key.interestOps();
        4. key.cancel()
        5. SelectionKey newKey = key.channel().register(newSelector, interestOps, a)
    3. selector = this.selector. 重新赋值selector
      1. selector.selectNow() 非阻塞选择
      2. selectCnt = 1 恢复selectCnt

processSelectedKey

  1. image.png
    1. NioEventLoop # 构造方法中 ==> selector = openSelector() 创建一个selector
    2. selector = provider.openSelector()
    3. SelectedSelectionKeySet 对selector的优化,原生的selector是用HashSet保存数据的
      1. 这个使用数组保存数据的,
      2. private SelectionKey[] keysA
      3. keysA[size ++] = o
    4. Class.forName(“sun.nio.ch.SelectorImpl”…)反射创建SelectorImpl
    5. SelectorImpl ==>. Set selectedKeys. HashSet keys
      1. selectedKeysField.set(selector, selectedKeySet)
      2. publicSelectedKeysField.set(selector, selectedKeySet) 替换该实现类里边的HashSet为SelectedSelectionKeySet 数组实现
    6. 优化Selector中selectedKeys 的实现,由hashset的操作变为数组
    7. ===================
    8. NioEventLoop # run()
    9. processSelectedKeys()
    10. if (selectedKeys != null) { 经过优化后的selectedKeys
    11. selectedKeys.flip(). ==> 返回底层的 SelectionKey[] keysA;
    12. processSelectedKeysOptimized(selectedKeys.flip())
    13. final SelectionKey k = selectedKeys[i] 获取到元素
    14. final Object a = k.attachment(). 获取到NioChannel
    15. processSelectedKey(k, (AbstractNioChannel) a). 处理k
    16. if (!k.isValid()) {. ==> unsafe.close(unsafe.voidPromise()) key不合法,关闭channel
    17. int readyOps = k.readyOps(); 拿到k的io事件
    18. if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) 处理read事件 workerGroup , accept 连接事件 bossGroup

runAllTask()执行逻辑

  1. image.png
  2. SingleThreadEventExecutor # execute(Runnable task)
  3. addTask(task);==> taskQueue.offer(task)
  4. NioEventLoop 两个队列,普通任务队列和定时任务队列
  5. AbstractScheduledEventExecutor # schedule
    1. scheduledTaskQueue().add(task).
  6. runAllTasks(ioTime * (100 - ioRatio) / ioRatio)
  7. fetchFromScheduledTaskQueue 从定时任务中获取任务
  8. Runnable scheduledTask = pollScheduledTask(nanoTime)
  9. !taskQueue.offer(scheduledTask). 将定时任务队列中的任务放到taskQueue 中
  10. Runnable task = pollTask() 从taskQueue中获取一个任务
  11. final long deadline = ScheduledFutureTask.nanoTime() + timeoutNanos 计算截止时间
  12. for (;; ){ ==> safeExecute(task) 挨个执行每个任务
  13. if ((runTasks & 0x3F) == 0) {. 累计到64个任务
  14. if (lastExecutionTime >= deadline) { ==> break; 计算时间,超过的话终止
  15. task = pollTask(); 再次拿出一个任务
  16. afterRunningAllTasks()做收尾的

总结

  1. NioEventLoop的创建 ==> 在创建bossGroup和workGroup的时候创建,参数为空,会创建2被cpu合数的workGroup,每个NioEventLoop会有一个chooser,会创建一个selector和定时任务队列,创建selector的时候会替换hashset的实现
  2. NioEventLoop启动 ==> 在首次调用exector的时候创建线程
  3. NioEventLoop执行. 检测io事件, 处理io事件,处理任务队列
  4. Netty如果保证异步串行无锁化的. ==> 在所有外部线程取调用EventLoop或者Channel的方法,利用inEventLoop判断是外部线程,将所有的操作封装为task,放到mps queue中,在N ioEventLoop执行逻辑的第三部执行执行queue中的task

Netty新连接接入

问题

  1. Netty是在哪里检测有新连接接入的?
    1. boss线程的轮询出accept事件,通过jdk底层的channel的accept创建新的连接
  2. 新连接是怎样注册到NioEventLoop线程的?
    1. boss线程调用childGroup 的 chooser的next方法,拿到一个NioEventLoop,然后将这个连接注册到NioEventLoop的selector上

新连接接入处理逻辑

  1. image.png

检测新连接

  1. image.png
  2. NioEventLoop # processSelectedKey
  3. unsafe.read(). ==> NioMessageUnsage.read
  4. int localRead = doReadMessages(readBuf)
  5. SocketChannel ch = javaChannel().accept()
  6. allocHandle.continueReading(). 自动读,和读取到的channel小于个

创建NioSocketChannel

  1. 服务端的Channel是通过反射创建的,接收到客户端的是通过new 创建的
  2. image.png
  3. NioServerSocketChannel # doReadMessages
  4. new NioSocketChannel(this, ch). this==> NioServerSocketChannel, ch==>jdk自定义的channel
  5. super(parent, socket);
    1. super(parent, ch, SelectionKey.OP_READ) OP_READ ==> 感兴趣的事件为读事件
    2. ch.configureBlocking(false). 设置非阻塞
    3. this.parent = parent; 设置客户端channel的服务端channel
  6. config = new NioSocketChannelConfig(this, socket.socket()) socket.socket() ==> jdk的scoket
  7. setTcpNoDelay(true). 禁止Nagle算法,让小的数据包,集成为大的数据包发送
    1. javaSocket.setTcpNoDelay(tcpNoDelay). socket 的方法

Netty中的Channel分类

  1. NioServerSocketChannel
  2. NioSocketChannel
  3. Unsafe 实现每种channel底层具体的协议
  4. image.png
  5. AbstractChannel. ==> Channel parent, ChannelId id, Unsafe unsafe, DefaultChannelPipeline pipeline
  6. AbstractNioChannel. ==> SelectionKey selectionKey, int readInterestOp, SelectableChannel ch—> jdk的channel
  7. AbstractNioMessageChannel(NioMessageUnsafe) 服务端channel. ==>. SelectionKey.OP_ACCEPT
    1. newUnsafe() ==> new NioMessageUnsafe();
    2. NioMessageUnsafe # read()
      1. doReadMessages(readBuf). ==> NioServerSocketChannel
      2. SocketChannel ch = javaChannel().accept() 读取到了连接
    3. NioServerSocketChannelConfig 配置
  8. AbstractNioByteChannel(NioByteUnsafe) 客户端channel.===>. SelectionKey.OP_READ
    1. newUnsafe() ==> return new NioByteUnsafe();
    2. NioByteUnsafe # read()
      1. byteBuf = allocHandle.allocate(allocator) 读取到了数据
    3. NioSocketChannelConfig 配置
  9. unsafe 是对channel的读写抽象,服务端的读,读新的连接,客户端的读,读取新的数据
  10. image.png

新连接NioEventLoop分配和selector注册

  1. NioMessageUnsafe # read()
  2. int size = readBuf.size(). 获取到的连接的channel的个数
  3. for (int i = 0; i < size; i ++) { 遍历
  4. pipeline.fireChannelRead(readBuf.get(i)); 服务端的channel read()
  5. 从head --> ServerBootStrapAcceptor --> tail
  6. image.png
    1. ServerBootStrapAcceptor # channelRead
    2. child.pipeline().addLast(childHandler) 添加childHandler,客户代码自定义的childHandler
//添加childHandler,添加各种Handler
.childHandler(new ChannelInitializer<SocketChannel>() {
    @Override
    public void initChannel(SocketChannel ch) {
        ch.pipeline().addLast(new AuthHandler());  
        //..

    }
});
  1. ChannelInitializer # handlerAdded
  2. initChannel(ctx). ==> initChannel(© ctx.channel()); 调用客户实现的initChannel
  3. child.config().setOption. 设置options
  4. childGroup.register(child). 选择NioEventLoop并注册selector. ==> childGroup --> workGroup
    1. MultithreadEventLoopGroup # register
      1. next() 返回一个NioEventLoop
        1. chooser.next()
        2. return executors[Math.abs(idx.getAndIncrement() % executors.length)]
      2. register(channel). 注册
        1. register(new DefaultChannelPromise(channel, this))
        2. AbstractChannel # register
        3. AbstractChannel.this.eventLoop = eventLoop 把当前的eventLoop绑定到客户端的channel上
        4. eventLoop.inEventLoop()
          1. Thread.currentThread(). 发起线程 服务端channel对应的EventLoop
          2. thread == this.thread. this.thread 客户端channel对应的eventLoop的线程为null
        5. doRegister()
        6. selectionKey = javaChannel().register(eventLoop().selector, 0, this). 进行channel的注册

NioSocketChannel读事件的注册

  1. AbstractChannel # register0
  2. doRegister()
  3. pipeline.fireChannelActive()
  4. AbstractChannelHandlerContext.invokeChannelActive(head)
  5. next.invokeChannelActive(). ==> DefaultChannelPipeline # HeadContext
  6. readIfIsAutoRead() 注册读事件
    1. channel.read(). ==> tail.read();
    2. ((ChannelOutboundHandler) handler()).read(this)
    3. unsafe.beginRead(). unsafe --> NioSocketChannelUnsafe
    4. doBeginRead(). ==> AbstractNioChannel # doBeginRead
    5. final SelectionKey selectionKey = this.selectionKey; selectionKey. 为0 ,不关心事件
    6. readInterestOp --> 客户端channel的时间 read事件

pipeline

问题

  1. netty是如何判断ChannelHandler类型的? 通过instance of 判断
  2. 对于ChannelHandler的添加应该遵循什么样的顺序?
    1. inBound和添加的顺序正相关,从上到下,outBound,从下到上
  3. 用户手动触发事件传播,不同的触发方式有什么样的区别?
    1. 当前节点开始触发,或者通过channel触发
  4. image.png
  5. head节点的unsafe用于实现channel的具体协议,tail节点终止事件和异常事件的处理

pipeline初始化

  1. image.png
  2. AbstractChannel # 构造方法
  3. pipeline = newChannelPipeline(). 一个channel对应一个pipeline
  4. new DefaultChannelPipeline(this). this -->对应channel
    1. tail = new TailContext(this).
    2. head = new HeadContext(this)
  5. ChannelHandlerContext. extends
    1. AttributeMap. 可以存储自定义的属性. attr(AttributeKey key)
    2. ChannelInboundInvoker. Registered. Active. ExceptionCaught. ChannelRead
    3. ChannelOutboundInvoker. deregister. write. flush.
  6. ChannelHandlerContext 属性
    1. channel() 属于哪个channel
    2. executor(). 哪个EventLoop
    3. ChannelHandler handler(). 业务逻辑处理器
    4. name(). 节点处理器的名称
    5. ChannelPipeline pipeline(). 属于哪个pipeline
    6. ByteBufAllocator alloc() 当前节点的内存分配器,有数据读写需要分配ByteBuf的时候用哪个内存分配器
  7. AbstractChannelHandlerContext ChannelHandlerContext 的实现类
    1. AbstractChannelHandlerContext next;
    2. AbstractChannelHandlerContext prev;
  8. TailContext ==> AbstractChannelHandlerContext. ChannelInboundHandler. (是一个输入的ChannelHandler,做一些收尾的事情)
    1. image.png
    2. exceptionCaught. ==> onUnhandledInboundException. ===> logger.warn
    3. channelRead. ==>. onUnhandledInboundMessage. ==> logger.debug. +. ReferenceCountUtil.release(msg) 释放消息对象
  9. HeadContext ==> AbstractChannelHandlerContext. , ChannelOutboundHandler. , ChannelInboundHandler . 是一个输出的ChannelHandler,和读写相关的操作,读写操作都是从head开始
    1. image.png
    2. read, write, flush, exceptionCaught
    3. channelActive. --> readIfIsAutoRead(). 刚建立到一个连接,会调用channelActive,可以注册一个读事件

添加ChannelHandler

  1. image.png
  2. DefaultChannelPipeline # addLast
  3. addLast(executor, null, h)
  4. checkMultiplicity(handler) 判断handler是否重复添加
  5. if (!h.isSharable() && h.added) {. 不是共享,并且已经添加过抛出异常
  6. newCtx = newContext(group, filterName(name, handler), handler) 创建一个节点,group为null
  7. filterName name为空,生成一个名称,校验名称是否重复,遍历查找
  8. new. DefaultChannelHandlerContext. 创建HandlerContext
    1. super(pipeline, executor, name, isInbound(handler), isOutbound(handler));
    2. isInbound. ==> handler instanceof ChannelInboundHandler
    3. isOutbound. ==> handler instanceof ChannelOutboundHandler
  9. addLast0(newCtx) 添加到tail节点的前边
  10. callHandlerAdded0(newCtx). ==> ctx.handler().handlerAdded(ctx) 调用节点的handlerAdded方法
  11. ctx.setAddComplete() 设置添加完成,通过CAS自旋
  12. ChannelInitializer #. handlerAdded channel. 添加完ChannelInitializer这个handler,调用handlerAdded方法,执行自定义的initChannel 的实现,自己实现的方法
  13. initChannel(ctx)
  14. initChannel(© ctx.channel()).

删除ChannelHandler

  1. image.png
  2. 权限校验的场景,通过校验移除该channelHandler,没有通过校验关闭这个连接
  3. ctx.pipeline().remove(this)
  4. remove(getContextOrDie(handler));
  5. getContextOrDie 查找节点
    1. AbstractChannelHandlerContext ctx = (AbstractChannelHandlerContext) context(handler) 将channel包装为AbstractChannelHandlerContext
    2. AbstractChannelHandlerContext ctx = head.next; 从头的下一个节点开始遍历查找
  6. remove 删除节点
    1. assert ctx != head && ctx != tai 断言节点不是head也不是tail
    2. remove0(ctx). --> 标准的双向节点的删除
  7. callHandlerRemoved0(ctx). 调用回调方法
    1. ctx.handler().handlerRemoved(ctx). 回调handlerRemoved 方法

inBound事件的传播

  1. image.png
  2. channelHandler接口的继承关系
    1. image.png
    2. ChannelHandlerAdapter
    3. ChannelOutBoundHandlerAdapter.
    4. ChannelInBoundHandlerAdapter
  3. ChannelHandler handlerAdded, handlerRemoved, exceptionCaught. Sharable 是否共享
  4. ChannelInboundHandler. channelActive,channelRead… 主要是被动的事件触发
  5. InBound事件,执行顺序按添加顺序从上到下,代码上边是头,下边是尾,addLast是在tail的前一个节点添加
  6. image.png
  7. inBound事件从头开始向后传播
  8. ctx.channel().pipeline().fireChannelRead
  9. AbstractChannelHandlerContext.invokeChannelRead(head, msg)
  10. next.invokeChannelRead(m). next 为head节点
  11. HeadContext # channelRead. ==> ctx.fireChannelRead(msg)
  12. invokeChannelRead(findContextInbound(), msg)
  13. findContextInbound 查找下一个inbound节点, 最终会找到tail节点
  14. ctx.fireChannelRead(msg) ==> invokeChannelRead 将事件继续往下传播,ctx.fireChannelRead从当前节点往下传播,ctx.channel().pipeline().fireChannelRead 会从head开始往下传播
  15. TailContext # channelRead ==> onUnhandledInboundMessage. 最终会传递到tail节点
  16. ReferenceCountUtil.release(msg); 释放这个msg的内存
  17. SimpleChannelInboundHandler 的使用场景, 可以自动释放msg的消息,防止消息没有传到tail节点的,消息一直堆积导致的内存泄漏
  18. SimpleChannelInboundHandler # channelRead
  19. channelRead0(ctx, imsg) 用户自定义实现这个方法
  20. ReferenceCountUtil.release(msg) channelRead中finally调用release 释放资源

outBound事件的传播

  1. image.png
  2. ChannelOutboundHandler. bind端口绑定. connect close. read. write. flush,用户主动发起的调用
  3. 添加的顺序和运行的顺序是相反的,代码从下到上
  4. ctx.channel().write(“hello world”) 向客户端的channel写数据
  5. pipeline.write(msg);
  6. tail.write(msg)
  7. write(msg, newPromise())
  8. write(msg, false, promise)
  9. AbstractChannelHandlerContext next = findContextOutbound() 查找outBound. ==> ctx = ctx.prev;
  10. HeadContext # write. ==> unsafe.write(msg, promise).
  11. ctx.channel().write(“hello world”). 从tail节点开始向prev节点传播
  12. ctx.write 从当前节点开始向prev节点传播
  13. image.png
  14. 代码的下边靠近tail,上边靠近头

异常的传播

  1. image.png
  2. image.png
  3. notifyHandlerException(t)
  4. invokeExceptionCaught(cause)
  5. handler().exceptionCaught(this, cause)
  6. ctx.fireExceptionCaught(cause)
  7. invokeExceptionCaught(next, cause) 拿到当前节点的下一个节点
  8. TailContext. # exceptionCaught ==> onUnhandledInboundException. ==> logger.warn 打印这个异常
  9. 执行顺序,直接向next传递
  10. 子类不处理,父类会直接向后传递异常
  11. ChannelHandlerAdapter # exceptionCaught ==> ctx.fireExceptionCaught(cause)
  12. 最佳解决方式在channel的最后添加一个异常处理器

ByteBuf

问题

  1. 内存的类别有哪些
  2. 如何减少多线程内存分配之间的竞争
    1. 所有的线程都有自己的独立的线程缓存(PoolThreadCache)
  3. 不同大小的内存是如何进行分配的
    1. netty使用对象池,缓存,完全二叉树,bitmap来维护内存
  4. image.png

ByteBuf结构和重要API

  1. ByteBuf结构
  2. image.pngmaxCapacity 最大的容量,超过这个会进行拒绝
  3. read, write, set方法. read 从readerIndex向后读
    1. set 给一个位置的内容直接设置值
  4. mark和reset方法 markReaderIndex 保存当前指针 resetReadIndex 恢复指针
  5. readableBytes writeableBytes. 可读和可写的容量 maxWritbleBytes 最大可写容量

ByteBuf分类

  1. image.png
  2. AbstractByteBuf(抽象实现)
    1. readerIndex. writerIndex. 保存读写指针
    2. 一些工具方法的实现. markReaderIndex, writeableBytes …
    3. byte readByte()
      1. int i = readerIndex; 保存读指针
      2. byte b = _getByte(i); 调用实现类的_getByte 方法
      3. readerIndex = i + 1 读指针加1
    4. writeByte ==> _setByte ==> writerIndex++
    5. getByte ==> _getByte ==> 直接获取数据,不移动指针
  3. 分类角度
    1. Pooled和Unpooled. Pooled 是从预先分配好的内存里边取一段连续内存封装为ByteBuf,Unpooled每次调用系统api向操作系统分配内存
    2. Unsafe和非Unsafe. Unsafe直接拿到对象的内存地址进行读写操作,用Unsafe操作可以直接拿到ByteBuf在jvm中的内存地址,调用jdk的Unsafe进行读写,非Unsafe直接不依赖Unsafe
      1. PooledHeapByteBuf. ==> PooledUnsafeHeapByteBuf
      2. UnpooledUnsafeDirectByteBuf UnpooledDirectByteBuf.(ByteBuffer buffer jdk底层的ByteBuffer)==> DirectByteBuffer
      3. UnpooledHeapByteBuf (利用 byte[] array 操作) ==> UnpooledUnsafeHeapByteBuf
      4. PooledUnsafeDirectByteBuf. PooledDirectByteBuf
    3. Heap和Direct heap直接在堆上进行内存分配(自动释放),direct 使用调用jdk直接进行内存分配,不进行gc
      1. Unpooled# directBuffer(int initialCapacity, int maxCapacity)
        1. AbstractByteBufAllocator # directBuffer==> newDirectBuffer
        2. new UnpooledDirectByteBuf. # 构造
        3. setByteBuffer(ByteBuffer.allocateDirect(initialCapacity))
        4. ByteBuffer.allocateDirect(initialCapacity). ==> new DirectByteBuffer(capacity)

ByteBufAllocator分析

  1. image.png
  2. 内存分配管理器,负责分配所有类型的内存
  3. buffer(根据实现判断是直接还是堆) ioBuffer (最好是直接内存). heapBuffer(堆内存). directBuffer(直接内存) compositeBuffer (合并的buffer)
  4. AbstractByteBufAllocator. 实现
    1. ByteBuf buffer() 区分直接内存还是堆内存
      1. return directBuffer()
        1. directBuffer(DEFAULT_INITIAL_CAPACITY(256), Integer.MAX_VALUE)
        2. newDirectBuffer(initialCapacity, maxCapacity). 抽象方法
      2. return. heapBuffer()
        1. newHeapBuffer(initialCapacity, maxCapacity) 抽象方法,区分是pooled还是Unpooled
  5. image.png
  6. PooledByteBufAllocator 在池子中取
  7. UnpooledByteBufAllocator 调用系统api取
  8. unsafe 和 非unsafe netty自动判别 jdk底层有unsafe 就分配unsafe
    1. PlatformDependent.hasUnsafe() ? new UnpooledUnsafeHeapByteBuf : new UnpooledHeapByteBuf

UnPooledByteBufAllocator分析

  1. heap内存分配
    1. newHeapBuffer (区分unsafe)
    2. UnpooledUnsafeHeapByteBuf. 构造 (创建一个byte数组)
      1. this(alloc, new byte[initialCapacity], 0, 0, maxCapacity);
      2. setArray(initialArray). 设置内存
      3. setIndex(readerIndex, writerIndex) 设置指针
      4. _getByte 方法 ==> UnsafeByteBufUtil.getByte(array, index). ==> PlatformDependent.getByte(array, index).
        1. UNSAFE.getByte(data, BYTE_ARRAY_BASE_OFFSET + index). 通过对象和偏移量获取内容
    3. UnpooledHeapByteBuf 构造 (也是利用byte数组)
      1. this(alloc, new byte[initialCapacity], 0, 0, maxCapacity);
      2. _getByte 方法 ==> HeapByteBufUtil.getByte(array, index). ==> memory[index]
  2. direct内存分配(堆外内存分配)
    1. newDirectBuffer (区分unsafe)
    2. newUnsafeDirectByteBuf
      1. new UnpooledUnsafeNoCleanerDirectByteBuf
        1. setByteBuffer(allocateDirect(initialCapacity), false). ==> allocateDirect
        2. ByteBuffer.allocateDirect(initialCapacity) ==> 调用jdk底层创建一个堆外内存
        3. setByteBuffer ==> memoryAddress = PlatformDependent.directBufferAddress(buffer). 计算内存地址(和非Unsafe的主要区别).
          1. UNSAFE.getLong(object, fieldOffset) 通过UNSAFE 拿到byteBuf对应的内存地址
        4. _getByte 方法 > UnsafeByteBufUtil.getByte(addr(index)) addr> memoryAddress(对象地址) + index(偏移地址) 直接计算内存地址
    3. UnpooledDirectByteBuf
      1. setByteBuffer(ByteBuffer.allocateDirect(initialCapacity)). 保存对外内存
        1. ByteBuffer.allocateDirect(initialCapacity). ==> 调用jdk底层创建一个堆外内存
        2. _getByte 方法 ==> buffer.get(index) 直接调用jdk底层的buffer的get方法
  3. unsafe会通过内存地址加偏移量的方式拿到数据,非unsafe是通过数组加下标或者jdk底层的ByteBuf的api拿到数据,通过unsafe拿到数据会更快
  4. 堆内存是通过数组实现的,堆外内存是通过jdk方法分配directBuf

PooledByteBufAllocator概述

  1. newHeapBuffer
  2. newDirectBuffer
    1. PoolThreadCache cache = threadCache.get(); 拿到当前线程的cache,不同的线程会在不同的内存上分配,维护着堆外内存和堆内存
      1. PoolThreadCache thread分配的内存用完,会自己分配内存(ByteBuf的缓存列表)
        1. image.png
        2. tinyCacheSize smallCacheSize. normalCacheSzie (可以有多少个缓存,在PooledByteBufAllocator中维护的)
      2. PoolThreadLocalCache. extends FastThreadLocal
      3. PoolThreadLocalCache # initialValue
        1. final PoolArena<byte[]> heapArena = leastUsedArena(heapArenas). 堆内存
        2. PoolArena directArena = leastUsedArena(directArenas) 对外内存
      4. PooledByteBufAllocator # 构造器 创建heapArenas 和 directArenas
        1. newArenaArray. ==> new PoolArena[] 数组 两个都是默认创建两倍cpu核数的数组大小,每个线程都可以有独享的array
        2. heapArenas. ==> PoolArena.HeapArena.
        3. directArenas. ==> PoolArena.DirectArena.
    2. PoolArena directArena = cache.directArena;
    3. buf = directArena.allocate(cache, initialCapacity, maxCapacity);

directArena分配direct内存的流程

  1. image.png
  2. PooledByteBufAllocator # newDirectBuffer
  3. directArena.allocate. 分配内存
  4. PooledByteBuf buf = newByteBuf(maxCapacity) 从对象池里面拿到PooledByteBuf复用
    1. DirectArena # newByteBuf
    2. PooledUnsafeDirectByteBuf.newInstance(maxCapacity)
    3. PooledUnsafeDirectByteBuf buf = RECYCLER.get() 带有回收特性的对象池
    4. RECYCLER = new Recycler
      1. newObject 如果没有对象,创建一个
      2. Handle handle. ==> void recycle(T object); 参数: 对象回收的方法
    5. buf.reuse(maxCapacity); 对象的复用(恢复一些属性)
      1. maxCapacity. setRefCnt(被多少个地方引用). setIndex discardMarks
  5. allocate(cache, buf, reqCapacity) 从缓存上进行内存分配
    1. cache.allocateNormal. 先从缓存上进行内存分配,如果分配成功就返回
    2. allocateNormal(buf, reqCapacity, normCapacity). 缓存上分配内存没有成功,实际上分配一份内存. 从内存堆里面进行内存分配

内存规格介绍

  1. 0 512B (字节) 8K 16M
  2. tiny(0-512B). small(512B-8k). normal(8k-16M). huge(16M)
  3. 一个chunk是16M,内存申请是通过chunk(16M)为单位向操作系统申请的,后续所有的内存分配都是在chunk里进行操作的
  4. 要一个1M的,申请16M的内存(chunk),16M中取一段内存当做1M,把1M的连续内存放到ByteBuf中
  5. 一个Page是8K,会把16M的内存切分,切分的单位是Page,分为2048个Page
  6. 一个SubPage是动态的,要1个1k的内存,会把8K用1k为单位进行切分,切分为8分; 如果要512B的内存,把8K用512B为单位进行切分

命中缓存的分配逻辑

  1. image.png
  2. MemoryRegionCache(缓存相关的数据结构)
    1. queue,多个entity组合形成的链 , handler指向唯一一段连续的内存, chunk(netty内存分配的单位),chunk和指向chunk的一段连续的内存,可以确定entity的内存大小和内存位置,所有的entity组合起来形成缓存链
    2. sizeClass netty中的内存规格,16K以上的内存是不进行缓存的
    3. size: 一个MemoryRegionCache中缓存的Bytebuf内存大小是固定的
      1. tiny 16B * N. small 512B 1k. 2k. 4k. normal 8k 16k 32k
  3. image.png
  4. MemoryRegionCache
    1. int size. Queue<Entry> queue. SizeClass sizeClass
  5. PoolThreadCache
    1. 1个PoolThreadCache 中维护32个tiny (大小为16B,32B到496B) 4个small 3个normal,每个都是一个MemoryRegionCache,每个MemoryRegionCache中维护着queue队列
    2. 1个tiny是MemoryRegionCache, queue的大小512个. small 的queue的大小是256个
    3. 一个MemoryRegionCache的大小指的是该缓存区段能够处理或存储的数据大小范围的上限,一个queue中存放的最大的缓存大小
 private final MemoryRegionCache<byte[]>[] tinySubPageHeapCaches;
private final MemoryRegionCache<byte[]>[] smallSubPageHeapCaches;
private final MemoryRegionCache<ByteBuffer>[] tinySubPageDirectCaches;
private final MemoryRegionCache<ByteBuffer>[] smallSubPageDirectCaches;
private final MemoryRegionCache<byte[]>[] normalHeapCaches;
private final MemoryRegionCache<ByteBuffer>[] normalDirectCaches;
  1. MemoryRegionCache[] tinySubPageDirectCaches = createSubPageCaches(tinyCacheSize, PoolArena.numTinySubpagePools, SizeClass.Tiny);
    1. tinyCacheSize(cacheSize) : 512
    2. numTinySubpagePools(numCaches) 512/16 = 32 (数组的大小)
    3. TinySizeClass(sizeClass) 类型
    4. MemoryRegionCache[] cache = new MemoryRegionCache[numCaches(32)]
    5. for (int i = 0; i < cache.length; i++) {. 遍历cache(32) 遍历赋值cache(32)
    6. cache[i] = new SubPageMemoryRegionCache(cacheSize(512), sizeClass)
      1. this.size = MathUtil.safeFindNextPositivePowerOfTwo(size). 查找下一个大于等于当前size 2的幂次方的数
      2. queue = PlatformDependent.newFixedMpscQueue(this.size). queue的个数 cacheSize(512)
      3. this.sizeClass = sizeClass
  2. smallSubPageDirectCaches = createSubPageCaches(smallCacheSize,directArena.numSmallSubpagePools, SizeClass.Small)
    1. smallCacheSize(256). queue的大小
    2. numTinySubpagePools 4 memoryRegionCache的个数

命中缓存的分配流程

  1. image.png
  2. PoolArena # allocate
  3. final int normCapacity = normalizeCapacity(reqCapacity) 规格化内存大小
    1. reqCapacity >= chunkSize 大于16k直接返回
    2. isTiny(reqCapacity) 是否大于tiny tiny是以16的倍数自增
  4. isTinyOrSmall(normCapacity) 小于一个page的大小就是tiny或者small
  5. boolean tiny = isTiny(normCapacity) 小于512B就是tiny
  6. cache.allocateTiny(this, buf, reqCapacity, normCapacity) 分配内存的入口
  7. cacheForTiny(PoolArena<?> area, int normCapacity) 寻找 MemoryRegionCache
  8. int idx = PoolArena.tinyIdx(normCapacity) ==> normCapacity >>> 4
    1. image.png
    2. 获取tiny[32] 中的下标,大小除以16,如果要的大小是16B,则取第2个
    3. cache(tinySubPageDirectCaches, idx) ==> 返回数组下标对应的元素 一个 tiny MemoryRegionCache
  9. boolean allocated = cache.allocate(buf, reqCapacity). 从queue中弹出一个entity给ByteBuf
    1. Entry entry = queue.poll() 从queue中拿出一个元素 ,entity中通过handle可以定位chunk中连续的一块内存
    2. initBuf(entry.chunk, entry.handle, buf, reqCapacity) 初始化entity中的chunk给bytebuf内存空间
    3. chunk.initBufWithSubpage(buf, handle, reqCapacity) 初始化 handle 指向一块连续内存的指针
    4. initBufWithSubpage(buf, handle, bitmapIdx(handle), reqCapacity)
    5. PooledByteBuf # inti
void init(PoolChunk<T> chunk, long handle, int offset, int length, int maxLength, PoolThreadCache cache) {
    assert handle >= 0;
    assert chunk != null;

    this.chunk = chunk; //哪一块内存
    this.handle = handle; //哪一段连续空间
    memory = chunk.memory; //
    this.offset = offset;
    this.length = length;
    this.maxLength = maxLength;
    tmpNioBuf = null;
    this.cache = cache;
}

  1. entry.recycle() 回收entity. ==> recyclerHandle.recycle(this). 放到对象池中

arena chunk. page subpage

  1. arena
  2. image.png
  3. PoolThreadCache 属性 PoolArena<byte[]> heapArena 不是缓存的空间竞技场,在内存中划分一块连续的内存,开辟一块内存
  4. PoolArena 数据结构
  5. image.png
  6. 最外层是一个chunkList,chunkList通过双向链表进行连接,chunkList 由chunk进行连接,一个chunk为16M,netty会实时计算chunk分配的情况.利用率,按照内存使用率归为一个chunkList,内存分配是根据一定算法先找chunkList,再从chunkList中寻找一个chunk
  7. PoolArena
private final PoolChunkList<T> q050;    //最小使用率50,最大使用率100
private final PoolChunkList<T> q025;    //最小使用率25,最大使用率100
private final PoolChunkList<T> q000;
private final PoolChunkList<T> qInit;
private final PoolChunkList<T> q075;
private final PoolChunkList<T> q100;
//双向链表进行连接
q100.prevList(q075);
q075.prevList(q050);
q050.prevList(q025);
q025.prevList(q000);
q000.prevList(null);
qInit.prevList(qInit);
  1. image.png
  2. 将chunk分配为8k大小的page,以page为单位进行内存分配
  3. 如果要使用的内存小于8k,会将8k以2k为单位进行划分,就做subpage
  4. PoolSubpage[] tinySubpagePools; PoolArena 属性
  5. PoolChunk chunk 属与哪个chunk
  6. int elemSize; 子页的大小
  7. final long[] bitmap 表示子叶的分配情况 PoolSubpage prev 以双向链表的方式维护
  8. Netty的内存分配,先从线程的PooledThreadCache中拿到一个Arena,再取一个chunkList,再从chunkList中寻找一个chunk,如果要分配的内存超过一个page,以page为单位划分,小于一个page,将page切分为子page进行划分

page级别的内存分配: allocateNormal

public class Scratch {
    public static void main(String[] args) {
        int page = 1024 * 8;
        //直接内存
        PooledByteBufAllocator allocator = PooledByteBufAllocator.DEFAULT;
        allocator.directBuffer(2 * page);
    }
}
  1. PoolArean # allocate(PoolThreadCache cache, PooledByteBuf buf, final int reqCapacity)
  2. normCapacity <= chunkSize cache.allocateNormal 小于16k的cache级别的分配,大于16k的内存级别的分配,page级别
  3. allocateNormal(buf, reqCapacity, normCapacity) page级别的分配,要么分配一个page,要不分配两个page,不会分配page的某一部分空间,normal处理的大小为8k到16k
  4. q050.allocate(buf, reqCapacity, normCapacity) … 尝试在现有的chunk上进行分配
    1. for (PoolChunk cur = head;😉 { 从head节点开始遍历
    2. long handle = cur.allocate(normCapacity) 从每一个chunk尝试分配
    3. if (handle < 0) { ==> cur = cur.next; handler小于0,说明没有分配到,查找下一个节点
    4. else ==> cur.initBuf(buf, handle, reqCapacity); 大于0,说明分配到了,初始化Bytebuf
    5. cur.usage() >= maxUsage ==> nextList.add(cur) 如果使用率大于最大使用率,添加到下一个chunkList中
  5. PoolChunk c = newChunk(pageSize, maxOrder, pageShifts, chunkSize) 创建一个chunk
    1. pageSize 8K == maxOrder 11 == pageShifts 13 2的13次方 等于8k ==chunkSize 16k
    2. allocateDirect(chunkSize). ==> PlatformDependent.allocateDirectNoCleaner(capacity) 使用jdk的api调用返回直接内存
    3. PoolChunk # 构造
    4. image.png
    5. chunk的分配情况: 一个chunk是以page的方式组织内存的,
    6. for (int d = 0; d <= maxOrder; ++ d) { 从0到11层进行遍历
    7. int depth = 1 << d; 层数
    8. memoryMap 和 depthMap 表示第几个节点在第几层,第几层可以表示哪一段连续内存被分配
  6. long handle = c.allocate(normCapacity). 进行分配,normCapacity 分配的大小,handle指向chunk中的一块连续内存
    1. allocateRun(normCapacity). 分配几个page
    2. int d = maxOrder - (log2(normCapacity) - pageShifts) 算出第几层,需要多大的内存空间
    3. int id = allocateNode(d) 从第n层分配一个节点,一个层的内存的大小是一样的(查找没有被使用的节点),通过这个id可以定位到chunk上的唯一一块内存
      1. setValue(id, unusable) 标记当前节点被使用了
      2. updateParentsAlloc(id) 标记父级节点被使用,比如016k被使用了,要标记04M的节点也被使用了,防止有一个需要0~4M的buffer分配到这个内存,因为这个内存的一部分被使用了
    4. 返回这个id,handler,通过这个handler可以标记chunk中的一块内存
  7. c.initBuf(buf, handle, reqCapacity) 通过这个内存给buffer初始化
    1. buf.init(this, handle, runOffset(memoryMapIdx), reqCapacity, runLength(memoryMapIdx),arena.parent.threadCache())
      1. runOffset 偏移量 0 runLength(memoryMapIdx) 16k
    2. PooledByteBuf # init. ==> PoolChunk chunk. long handle. T memory 通过jdk申请的directByteBuf 真实的内存地址
    3. initMemoryAddress() 因为是unsafe实现的,所以要初始化ByteBuf的内存地址

subpage级别的内存分配: allocateTiny

  1. image.png
  2. image.png
  3. PoolArena # allocate
  4. tableIdx = tinyIdx(normCapacity) ==> normCapacity >>> 4 容量除以16
  5. allocateNormal(buf, reqCapacity, normCapacity) 分配内存
  6. allocateSubpage(normCapacity) 分配一个subPage
  7. int id = allocateNode(d) 先找到在第几层分配空间
  8. image.png
  9. subpage = new PoolSubpage(head, 创建一个subPage
    1. PoolSubpage # 构造
    2. init(head, elemSize) 初始化
    3. maxNumElems = numAvail = pageSize / elemSize (elemSize)将一个page划分为多少分,maxNumElems 每个内存的大小
    4. addToPool(head) 将节点添加到arena中
  10. subpages[subpageIdx] = subpage; 赋值到数组中
  11. subpage.allocate() 分配一个内存,返回一个handler,对应第几个节点,第几个subPage
  12. c.initBuf(buf, handle, reqCapacity) 初始化ByteBuf
  13. initBufWithSubpage(buf, handle, bitmapIdx, reqCapacity) 初始化子page
  14. buf.init( …) runOffset(memoryMapIdx) page的偏移量
  15. (bitmapIdx & 0x3FFFFFFF)第几个subPage
  16. runOffset(memoryMapIdx) + (bitmapIdx & 0x3FFFFFFF) * subpage.elemSize 这段内存的偏移量 = page的偏移量 + 第几个subPage * subPage的大小

ByteBuf的回收

  1. AbstractReferenceCountedByteBuf # release ==> release0
  2. deallocate()
  3. this.handle = -1 将handle赋值为-1,表示不指定内存
  4. chunk.arena.free(chunk, han dle, maxLength, cache) 将内存添加到MemoryRegionCache中
    1. cache.add(this, chunk, handle, normCapacity, sizeClass) 尝试将内存放到缓存中
    2. cache.add(chunk, handle) 将cache添加到queue中
  5. freeChunk(chunk, handle, sizeClass) 将内存标记为未使用
    1. !chunk.parent.free(chunk, handle)
    2. chunk.free(handle)
      1. subpage.free(head, bitmapIdx & 0x3FFFFFFF)) 如果是subPage,标记bitmap位图
      2. updateParentsFree(memoryMapIdx) 如果是page反向标记父节点为未使用
  6. recycle() 将ByteBuf放到对象池中
  7. subPage标记未使用,使用bitmap,page是放到树形结构中

Netty解码(把字节流变为对象)

定义

  1. 将二进制数据流解析成自定义协议的数据包,ByteBuf
  2. 解码器抽象解码过程
    1. 累加字节流,调用子类进行解析(当前字节流.list), list中有解析出来的数据,往下进行传播
  3. netty里面有哪些拆箱即用的解码器
    1. 固定长度,基于行,基于固定分割符的解码器,继续长度域的解码器
  4. 解码器的基类和Netty中常见的解码器分析

解码器积累

  1. image.png
  2. ByteToMessageDecoder 解码
  3. ByteToMessageDecoder # channelRead(ChannelHandlerContext ctx, Object msg)
  4. CodecOutputList out = CodecOutputList.newInstance() 将解析完成的数据放入到list中
  5. if (msg instanceof ByteBuf) { netty是基于ByteBuf进行解码的
  6. ByteBuf data = (ByteBuf) msg; 当前读取到的数据
  7. cumulation ==> new Cumulator()累加器 ==> cumulate(累加) 方法,如果内存不够进行扩容,将读到的ByteBuf数据,写入ByteBuf(累加器)对象中
  8. if (first) { ===> cumulation = data; 如果第一次,累加器的数据就是当前读取到的数据
  9. cumulation = cumulator.cumulate(ctx.alloc(), cumulation, data) 不是第一次, 将读进来的数据合并到类加器中
  10. callDecode(ctx, cumulation, out) 调用子类的decode方法进行解析
  11. while (in.isReadable()) { 只要累加器ByteBuf中有数据就进行循环
  12. int outSize = out.size(); ==> (outSize > 0)如果list中有数据了,说明有解析好的对象
  13. fireChannelRead(ctx, out, outSize) ==> out.clear() 将对象进行传播,清空当前list
  14. int oldInputLength = in.readableBytes() 记录当前可读的数据长度
  15. decode(ctx, in, out) 调用子类的decode in 累加器中的数据, out 解析出的对象放到out中
  16. if (outSize == out.size()) { 说明经过这次的decode方法,没有解析出内容
    1. oldInputLength == in.readableBytes() 说明累加器中的数据不能拼接成完整的数据包,继续累加数据,break跳出循环,后续解析
    2. 否则:从当前的数据中读取到了一部分数据,继续循环解析
  17. fireChannelRead(ctx, out, size) 将解析出来的对象向下传播
  18. for (int i = 0; i < numElements; i ++) { 将解析出来的所有数据都进行传播
  19. msgs.getUnsafe(i) --> 获取到的是一个ByteBuf
  20. ctx.fireChannelRead( 传播ByteBuf

netty中的解码器

  1. 基于固定长度解码器分析(以多少个长度为分割,进行解析)
    1. FixedLengthFrameDecoder
    2. image.png
    3. Object decoded = decode(ctx, in)
      1. if (in.readableBytes() < frameLength) { ==> return null 当前可读的数据小于解析的固定长度,返回null,说明子类没有读取ByteBuf中的数据,到了父类会终止循环,继续累加数据,然后再次调用子类
      2. in.readRetainedSlice(frameLength) 从当前累加器中截取固定长度的数据
    4. out.add(decoded) 添加到解析出的对象集合中
  2. 基于行解码器分析(基于换行符分割)
    1. LineBasedFrameDecoder
      1. maxLength 解析出的最大长度,超过长度进行丢弃 failFast 超过长度是否快速抛出异常 discarding 丢弃模式
    2. decode方法
      1. final int eol = findEndOfLine(buffer) 获取到换行分割符的位置image.png
        1. 找到\n的位置,如果前边是\r指针向前移动,指向\r
        2. 非丢弃模式 -->找到换行符
        3. if (eol >= 0) { 找到了换行符
        4. final int length = eol - buffer.readerIndex() 一行的数据
        5. if (length > maxLength) { 一行的数据超过了最大长度
        6. buffer.readerIndex(eol + delimLength) 直接将指针移动到eol的后边,fail 传播异常,读取到一行的内容过长
        7. frame = buffer.readRetainedSlice(length) ==> buffer.skipBytes(delimLength) 从ByteBuf中分割数据,然后跳过换行符
        8. 非丢弃模式 -->没有找到换行符
        9. final int length = buffer.readableBytes() 获取到Buf中可读的数据长度
        10. if (length > maxLength) { 超过了最大长度
        11. buffer.readerIndex(buffer.writerIndex()) 把读指针直接移动到写指针
        12. discarding = true 是否丢弃true,进入到丢弃模式
        13. if (failFast) { ==> 如果是快速失败,向下传递异常
        14. 丢弃模式处理(找到了换行符,将读指针移动到换行符后边将前边内容丢弃,恢复模式为非丢弃模式)
        15. if (eol >= 0) { 找到了换行符
        16. final int length = discardedBytes + eol - buffer.readerIndex() 记录丢弃了多少数据
        17. buffer.readerIndex(eol + delimLength) 将当前读指针移动到换行符后边
        18. discarding = false 标记当前模式为非丢弃
        19. if (!failFast) { ==> fail(ctx, length) 如果不是快速失败,向下传递失败,快速失败指的是第一次发现长度超过了限制就立即向下传递异常,不是快速失败是指最终读到换行符,才向下传递异常
        20. 丢弃模式没有找到换行符
        21. buffer.readerIndex(buffer.writerIndex()) 直接将读指针移动到写指针,丢弃数据
  3. 基于分割符的行解码器
    1. DelimiterBasedFrameDecoder 最大的长度,分割符,可变长度参数
    2. 每次只解析读指针到最小分割符的数据,比如下图中readIndex到A的数据
    3. decode方法
      1. if (lineBasedDecoder != null) { ==> return lineBasedDecoder.decode(ctx, buffer) 如果行处理器不为空,调用行处理器的处理方法 (构造DelimiterBasedFrameDecoder 时创建行分割符处理器)
      2. image.png
      3. for (ByteBuf delim: delimiters) { 找到最小的分割符,距离读指针最近的分割符,A和B找到A
      4. if (minDelim != null) { 如果找到了最下分割符
      5. if (minFrameLength > maxFrameLength) { 超过了最大的长度 buffer.skipBytes丢弃数据,将指针移动到最小分割符后边
      6. frame = buffer.readRetainedSlice(minFrameLength) ==> buffer.skipBytes(minDelimLength)截取需要的数据,移动指针
      7. 没有找到分割符,和行分割符的处理类似
  4. 基于长度域解码器
    1. image.png
    2. LengthFieldBasedFrameDecoder
      1. lengthFiledOffset: 从这个消息开始多少个字节后是表示长度的字节
      2. lengthFieldLength (表示长度)用多少个字节, netty会根据这个数据处理从长度字节后再读取多少个字节的内容
      3. lengthAdjustment 长度调整, lengthFieldLength+lengthAdjustment 表示还要从长度字节后再读多少个字节的内容
      4. initialBytesToStrip 是否需要跳过字节,从消息开始需要跳过多少长度的字节
    3. image.png
    4. decode
      1. if (in.readableBytes() < lengthFieldEndOffset) { 可读字节流没有到长度字节流的尾部
      2. lengthFieldEndOffset = lengthFieldOffset + lengthFieldLength 表示长度大小的结束偏移量 =长度字段的偏移量+长度的字段
      3. int actualLengthFieldOffset = in.readerIndex() + lengthFieldOffset; 真实表示长度的偏移量 = 读指针的位置 + 长度字段的偏移量
      4. long frameLength = getUnadjustedFrameLength(in, actualLengthFieldOffset, lengthFieldLength, byteOrder) 获取长度域的长度
      5. frameLength += lengthAdjustment + lengthFieldEndOffset 计算出需要从数据流总抽取数据的长度 (frameLength + lengthAdjustment) 表示从lengthFieldEndOffset 之后还需要读取多少长度的字节
        1. image.png
        2. 08 两个字节表示长度
        3. 8个字节 - 4个字节 = 从长度字节后还要读取多少字节 + (长度字段字节偏移量 2个字节 + 表示长度需要的字节 2个字节) - 4个字节 (需要跳过的字节)
//长度字段结束的offset = 长度字段的offset + 长度字段的长度
lengthFieldEndOffset = lengthFieldOffset + lengthFieldLength; 
//真实长度字段的偏移量 = 读指针的位置 + 长度字段的offset
int actualLengthFieldOffset = in.readerIndex() + lengthFieldOffset;
//获取长度字段表示的值 (使用lengthFieldLength,长度字段的长度)
long frameLength = getUnadjustedFrameLength(in, actualLengthFieldOffset, 
                                                lengthFieldLength, byteOrder);
//要读取数据流的长度 = 长度字段的长度 + 调整字段 + 长度字段结束的长度
// 长度字段的长度 + 调整字段 表示  从长度字段之后还要读取多少字节数据
frameLength += lengthAdjustment + lengthFieldEndOffset
//读指针向后移动,跳过需要跳过的字节长度
in.skipBytes(initialBytesToStrip)
//真实读取的长度 = 要读取数据的长度 - 需要跳过的字节长度
int actualFrameLength = frameLengthInt - initialBytesToStrip;
//从数据流中获取从度指针开始向后 真实长度的数据
ByteBuf frame = extractFrame(ctx, in, readerIndex, actualFrameLength);
//更新读指针的位置 = 当前读指针位置 + 实际读取的数据长度
in.readerIndex(readerIndex + actualFrameLength);

  6. if (initialBytesToStrip > frameLengthInt) {  需要跳过的字节大于数据包的长度,抛出异常
  7. in.skipBytes(initialBytesToStrip) 跳过多少字节
  8. int readerIndex = in.readerIndex(); 重新获取读指针
  9. int actualFrameLength = frameLengthInt - initialBytesToStrip; 实际的数据包长度 = 需要抽取的数据长度 - 跳过的字节长度
  10. ByteBuf frame = extractFrame(ctx, in, readerIndex, actualFrameLength)  抽取数据流,从读指针开始读取多少长度的字节
  11. <br />

Netty编码(把对象变为字节流)

问题

  1. 如何把对象变成字节流,最终写到socket底层
  2. image.png

writeAndFlush()

  1. image.png
  2. AbstractChannel # writeAndFlush ==> pipeline.writeAndFlush(msg)
  3. tail.writeAndFlush(msg) 从tail节点往前writeAndFlush
  4. write(msg, true, promise) flash 为false只是将数据写入到netty的缓存中
  5. if (executor.inEventLoop()) { 判断当前是否在本地线程操作的
  6. next.invokeWriteAndFlush(m, promise)
    1. invokeWrite0(msg, promise)
      1. ((ChannelOutboundHandler) handler()).write(this, msg, promise) 在pipeline的handler中进行传播
      2. MessageToByteEncoder # write(ChannelHandlerContext ctx, Object msg, …)
    2. invokeFlush0()
      1. ((ChannelOutboundHandler) handler()).flush(this); 将flush往前进行传播
  7. safeExecute(executor, task, promise, m) 不是本地线程操作的,封装为task放到taskQueue中执行

编码器处理逻辑 MessageToByteEncoder

  1. image.png
  2. MessageToByteEncoder # write
  3. acceptOutboundMessage(msg) 判断是否能处理这个对象
    1. matcher.match(msg) 匹配,默认的匹配类是TypeParameterMatcher ==> type.isInstance(msg)
  4. I cast = (I) msg; 将对象强转位泛型类型
  5. buf = allocateBuffer(ctx, cast, preferDirect) 分配内存 ==> return ctx.alloc().heapBuffer(); 分配堆外内存
  6. encode(ctx, cast, buf); 将转换后数据的对象写入到buf中
  7. out.writeBytes(bytes) ==>自定义的实现
  8. ReferenceCountUtil.release(cast) 释放对象,需要转换的对象,如果对像是ByteBuf的时候
  9. 传播事件,将数据传播到head节点,head节点将数据写入到底层
  10. if (buf.isReadable()) { ==> ctx.write(buf, promise) 可读的话,向后传播事件,直到head事件
  11. buf.release(); 如果不可读,释放buf对象
  12. buf = null; 将buf 置为空
  13. finally ==> buf.release() 释放buffer

Head节点的处理

  1. image.png
  2. biz写出user对象,encoder将user写入到bytebuf中,head节点将数据写入到操作系统
  3. HeadContext # write
  4. unsafe.write(msg, promise)
  5. image.png
  6. msg = filterOutboundMessage(msg) msg为ByteBuf
  7. AbstractNioByteChannel # filterOutboundMessage
    1. if (buf.isDirect()) { ==> return msg 如果是对外内存 直接返回
    2. newDirectBuffer(buf) 否则封装为堆外内存
    3. ByteBuf directBuf = alloc.directBuffer(readableBytes) 创建对外内促
    4. directBuf.writeBytes(buf, buf.readerIndex(), readableBytes) 将Buf数据写入
  8. outboundBuffer.addMessage(msg, size, promise) 将堆外内存放到写队列
    1. ChannelOutboundBuffer 属性
// Entry(flushedEntry) --> ... Entry(unflushedEntry) --> ... Entry(tailEntry)  
//  
// 在链表结构中,第一个被刷新的Entry  
private Entry flushedEntry;  
// 在链表结构中,第一个未刷新的Entry  
private Entry unflushedEntry;  
// 代表缓冲区尾部的Entry  
private Entry tailEntry;  
// 尚未写入的已刷新条目的数量  
private int flushed;
  1. flushedEntry:这个变量指向链表中第一个已经被“刷新”(即,已经准备好写入但尚未实际写入存储介质)的条目。在这个条目之前的所有条目都已经被写入存储介质。
  2. unflushedEntry:这个变量指向链表中第一个尚未被刷新的条目。在这个条目之前的所有条目(不包括这个条目本身)都已经被标记为刷新状态,但可能还没有实际写入。
  3. tailEntry:这个变量指向链表的尾部,即最后一个添加的条目。新添加的条目会被放置在尾部。
  4. flushed:这个整数变量记录了已经被标记为刷新但尚未实际写入的条目的数量。这有助于追踪还有多少数据需要被实际写入存储介质。
  1. Entry entry = Entry.newInstance(msg, size, total(msg), promise) 把msg封装为Entity
    1. image.png
    2. 第一次调用write,flashedEntry为null, unflashedEntry和tailEntry指向新进来的Entry
    3. image.png
    4. 第二次调用write将TailEntry指定为新进来的entry,将新进来的entry链接到之前Entry的next
    5. 后边添加entry都是将Entry添加到尾部
  2. incrementPendingOutboundBytes 设置不可写状态(超过64k)
    1. long newWriteBufferSize = TOTAL_PENDING_SIZE_UPDATER.addAndGet(this, size) 标记有多少可写的字节
    2. newWriteBufferSize 写缓存区中的数据不能超过64k 默认为64 * 1024 (WriteBufferWaterMark 中指定)
      1. setUnwritable(invokeLater) 设置当前状态不可写了
      2. fireChannelWritabilityChanged(invokeLater) 向前传播当前不可写状态更新事件

flush-刷新buffer队列

  1. image.png
  2. HeadContext # flush
  3. unsafe.flush()
  4. outboundBuffer.addFlush()
  5. Entry entry = unflushedEntry 将entry指向未写出的entry
  6. if (flushedEntry == null) { ==> flushedEntry = entry; 如果flashedEntry为ull,将flushedEntry指向entry,第一次写出数据
  7. decrementPendingOutboundBytes(pending, false, true); 设置可写状态(小于32k)
    1. long newWriteBufferSize = TOTAL_PENDING_SIZE_UPDATER.addAndGet(this, -size); 写出一个对象将该字节从总大小减去
    2. newWriteBufferSize 当前剩余字节小于32k,表示可以写了
    3. setWritable(invokeLater) 设置当前channel可写了
  8. Entry(flushedEntry) --> … Entry(unflushedEntry) --> … Entry(tailEntry)
    1. Entry entry = unflushedEntry;
    2. entry != null ==> entry = entry.next;
    3. unflushedEntry = null; 表示没有数据unflush了
    4. image.png
  9. flush0();
  10. doWrite(outboundBuffer)
  11. AbstractNioByteChannel # doWrite
  12. Object msg = in.current(); 获取msg数据
    1. Entry entry = flushedEntry; ==> entry.msg; 拿到flushedEntry中的数据
  13. if (msg instanceof ByteBuf) {
    1. int localFlushedAmount = doWriteBytes(buf);
    2. NioSocketChannel # doWriteBytes
      1. buf.readBytes(javaChannel(), expectedWrittenBytes);
      2. PooledDirectByteBuf # readBytes
      3. int readBytes = getBytes(readerIndex, out, length, true);
        1. ByteBuffer tmpBuf; jdk的ByteBuffer
        2. tmpBuf = internalNioBuffer(); 将netty中byteBuf的数据写入到ByteBuffer
        3. out.write(tmpBuf) 将jdk底层的byteBuffer写入到ScoketChannel中
  14. in.remove()
    1. Entry e = flushedEntry;
    2. removeEntry(e);
    1. 如果到了尾部,flushedEntry = null; tailEntry = null;unflushedEntry = null;
    2. flushedEntry = e.next; 将flushedEntry向后移动一个指针
  15. 写入和写出的步骤:
    1. 初始状态flushedEntry , unflushedEntry, tailEntry 都为null
    2. 第一次写入unflushedEntry 和tailEntry 都指向新的Entry
    3. 后边每次写入都在unflushedEntry 和tailEntry中添加数据
    4. 第一次写出数据,unflushedEntry 置为null,flushedEntry 替换到之前unflushedEntry 的位置,写出flushedEntry到tailEntry中的数据

性能优化工具类

FastThreadLocal

  1. image.png
  2. FastThreadLocal # 构造
    1. index = InternalThreadLocalMap.nextVariableIndex(); 每一个FastThreadLocal都有一个index值
      1. int index = nextIndex.getAndIncrement();
      2. image.png
  3. get() 方法
    1. image.png
    2. get(InternalThreadLocalMap.get())
    3. InternalThreadLocalMap.get() 获取ThreadLocalMap
      1. Thread thread = Thread.currentThread(); 获取当前线程
      2. image.png
      3. if (thread instanceof FastThreadLocalThread) { 是否是FastThreadLocalThread
      4. fastGet((FastThreadLocalThread) thread); 用自己实现FastThreadLocal中的InternalThreadLocalMap
        1. InternalThreadLocalMap threadLocalMap = thread.threadLocalMap(); 每个线程都有自己的ThreadLocalMap
      5. slowGet(); 实际用的jdk的ThreadLocal中获取ThreadLocalMap方法,获取每个线程的ThreadLocalMap
        1. InternalThreadLocalMap ret = slowThreadLocalMap.get(); 获取到当前线程的ThreadLocalMap 比较慢的
        2. if (ret == null) { ==> ret = new InternalThreadLocalMap(); 初始化InternalThreadLocalMap
        3. static final ThreadLocal slowThreadLocalMap = new ThreadLocal(); 利用slowThreadLocalMap 获取到每个线程的InternalThreadLocalMap
    4. get()方法
      1. Object v = threadLocalMap.indexedVariable(index); 通过threadLocalMap中的index直接拿到
        1. index对应当前FastThreadLocal对应的下标
        2. Object[] lookup = indexedVariables; 拿到当前线程的ThreadLocalMap对象中的indexedVariables
        3. indexedVariables 使用数组结构,index对应一个ThreadLocal对应的下标,值对应存储的内容
        4. java原生的存储的值和ThreadLocal使用Entry链表实现的,FastThreadLocal使用链表实现的
        5. image.png
    5. InternalThreadLocalMap # 构造方法
      1. super(newIndexedVariableTable())
        1. newIndexedVariableTable()
        2. Object[] array = new Object[32];
        3. Arrays.fill(array, UNSET);
      2. UnpaddedInternalThreadLocalMap # 构造方法
        1. this.indexedVariables = indexedVariables; 初始化indexedVariables
    6. initialize(threadLocalMap) ==> v = initialValue(); 初始化对象值,自己的实现
    7. threadLocalMap.setIndexedVariable(index, v);
      1. lookup[index] = value; 将对应的值设置到对应的索引下边
  4. set()方法
    1. image.png
    2. set(InternalThreadLocalMap.get(), value)
    3. if (value != InternalThreadLocalMap.UNSET) { 如果要设置的值不是初始对象
      1. lookup[index] = value;
    4. remove(threadLocalMap); 如果是初始值,则会执行remove方法
      1. Object v = threadLocalMap.removeIndexedVariable(index); 将初始值对象放到对应数组下标,并返回之前的值
        1. lookup[index] = UNSET;
      2. if (v != InternalThreadLocalMap.UNSET) { 原来的对象不是初始对象
        1. onRemoval((V) v); 回调onRemove方法

Recycler 轻量级的对象池

  1. image.png
  2. 创建对象不要每次创建对象,可以重复利用对象
  3. Recycler构造
    1. FastThreadLocal<Stack> threadLocal 一个ThreadLocal,其中有一个Stack 属性,说明一个线程对应一个Stack对象
    2. new Stack(Recycler.this, Thread.currentThread(), maxCapacityPerThread, maxSharedCapacityFactor,ratioMask, maxDelayedQueuesPerThread)
      1. thread ratioMask 对象被回收的频率 maxCapacity (32k) maxDelayedQueues 当前线程创建的对象在多少可线程中可以缓存
      2. head pre cursor
      3. availableSharedCapacity 线程创建的对象能够在其它线程中使用的最大个数 (16k) 当前对象能够使用别的线程的对象最大的容量 16k
      4. DefaultHandle<?>[] elements; 一个handle包装了一个对象
  4. image.png
    1. Stack stack = threadLocal.get(); 获取当前线程的stack,该stack是自己封装的对象,利用DefaultHandle<?>[] elements 维护数据
    2. DefaultHandle handle = stack.pop(); 从stack中获取一个对象
      1. int size = this.size;
      2. if (size == 0) { 如果当前线程中的对象为0
      3. !scavenge() 回收跑去别的线程的对象
      4. DefaultHandle ret = elements[size]; 取出一个Handle
      5. elements[size] = null; 将该位置设置为null
      6. ret.recycleId = 0; 将 recycleId 设置为0
      7. ret.lastRecycledId = 0; 将lastRecycledId设置为0
    3. if (handle == null) {
      1. handle = stack.newHandle(); 利用stack创建一个handle
        1. new DefaultHandle(this) 一个handle和一个Stack绑定,也和一个object绑定
      2. handle.value = newObject(handle) 创建一个新对象,赋值到handle的value上
    4. handle.value 返回handle中绑定的value对象
  5. image.png
    1. handle.recycle(this) 回收当前对象
    2. stack.push(this) 将当前对象压到栈中
    3. Thread currentThread = Thread.currentThread() 获取当前线程
    4. if (thread == currentThread) { 判断当前线程是否是创建当前对象的线程
      1. pushNow(item); 如果是,直接放到stack中
        1. item.recycleId = item.lastRecycledId = OWN_THREAD_ID; 将recycleId 和lastRecycledId 赋值为当前线程的Id
        2. if (size >= maxCapacity || dropHandle(item)) { 如果当前缓存的对象超过了最大容量,直接扔掉该对象
        3. 数组扩容
        4. elements[size] = item; 将当前对象直接放到elements中
      2. pushLater(item, currentThread); 不是当前线程回收对象,调用pushLater
        1. image.png
        2. Map<Stack<?>, WeakOrderQueue> delayedRecycled = DELAYED_RECYCLED.get();
        3. FastThreadLocal<Map<Stack<?>, WeakOrderQueue>> DELAYED_RECYCLED 不同的线程对应不同的Stack,一个stack对应一个WeakOrderQueue,用于缓存该线程创建的对象的回收对象
        4. WeakOrderQueue queue = delayedRecycled.get(this); 拿到当前线程对应的WeakOrderQueue
        5. if (queue == null) { 如果为null
          1. if (delayedRecycled.size() >= maxDelayedQueues) { 如果当前线程回收别的线程的数量大于最大可以回收的对象,delayedRecycled.put(this, WeakOrderQueue.DUMMY); 放置一个假的map
          2. queue = WeakOrderQueue.allocate(this, thread)) 创建一个WeakOrderQueue
            1. boolean reserveSpace(AtomicInteger availableSharedCapacity, int space) 更新创建对象的线程允许对象被其他线程缓存的最大容量
            2. new WeakOrderQueue(stack, thread) 创建一个WeakOrderQueue
            3. image.png
            4. WeakOrderQueue属性
              1. private Link head, tail; 一个Link对象组成的链表
              2. private WeakOrderQueue next; 多个WeakOrderQueue是由链表构成的,next表示下一个WeakOrderQueue
              3. link的链表,一个link中包含多个Handle,一个Handle对应一个缓存对象
              4. owner 当前回收的线程,线程2回收线程1的对象,owner表示线程2
            5. image.png
            6. head = tail = new Link(); 创建一个link
            7. owner = new WeakReference(thread) 设置所属的线程
            8. next = stack.head; stack表示创建对象的线程的stack,stack中有一个head属性指向其他线程回收的该线程创建对象的WeakOrderQueue,将stack中的head指针内容保存到局部变量next中
            9. stack.head = this; 如果一个线程2回收线程1的对像,会将线程2创建的WeakOrderQueue放到线程1对应的stack中head指针的指向,放到头部
          3. delayedRecycled.put(this, queue); 放到map缓存中
        6. queue.add(item); 将当前元素追加到WeakOrderQueue中的link中
          1. Link tail = this.tail; 获取到链表中的最后一个link
          2. if ((writeIndex = tail.get()) == LINK_CAPACITY) { 如果尾部的link已经满了
          3. this.tail = tail = tail.next = new Link() 创建一个新的link
            1. DefaultHandle<?>[] elements = new DefaultHandle[LINK_CAPACITY] 一个Handle的数组
            2. int readIndex 读指针
          4. writeIndex = tail.get(); 获取到写指针
          5. tail.elements[writeIndex] = handle 将handle追加到WeakOrderQueue 的link中的elements数组中
          6. handle.stack = null; 表示handle不属于原来创建线程的stack了
  6. 从当前线程的对象池stack获取对象为空时,回收别的线程回收的对象,head指针指向的WekOrderQueue中
    1. image.png
    2. Stack中的三个指针
      1. head 执行第一个WeakOrderQueue
      2. cursor 指向要从该WeakOrderQueue中回收对象
      3. pre指向cursor的前一个WeakOrderQueue
    3. Recycler # DefaultHandle pop()
    4. scavenge() 从别的回收线程中拿回对象返回
    5. scavengeSome
      1. WeakOrderQueue cursor = this.cursor; 获取当前需要回收的WeakOrderQueue
      2. cursor.transfer(this) 将WeakOrderQueue中的对象回收到当前stack中,每次回收都是获取一个WeakOrderQueue的一个Link,一个Link中包含多个handle,一次回收回收Queue的一个link的多个对象
        1. Link head = this.head; 拿到当前link的头节点
        2. if (head.readIndex == LINK_CAPACITY) { link的读指针已经是当前link的大小,表示当前link的对象已经被全部取走了
        3. this.head = head = head.next; 将head指向head的下一个节点(之前的head节点会被丢弃)
        4. final int srcStart = head.readIndex; 拿到当前link的读指针
        5. int srcEnd = head.get(); 表示Link.get() 拿到当前link中缓存对象的个数,每次缓存一个对象,该数字会加1
        6. int srcSize = srcEnd - srcStart; 表示Link中还有多少个对象剩余
        7. final DefaultHandle[] srcElems = head.elements; 获取到Link的Handle数组,缓存对象的数组
        8. final DefaultHandle[] dstElems = dst.elements; 获取到stack的Handle数组,缓存对象的数组
        9. for (int i = srcStart; i < srcEnd; i++) { 从Link的readIndex开始遍历到Link的容量大小
        10. if (element.recycleId == 0) { ==> element.recycleId = element.lastRecycledId; recyleId等于0表示当前元素没有被回收过,将当前元素的recycleId 设值,表示被回收过了
        11. DefaultHandle element = srcElems[i]; 拿到Link的handle缓存对像
        12. srcElems[i] = null 将Link的当前元素置为null
        13. dstElems[newDstSize ++] = element; 追加到stack的element数组中
      3. WeakOrderQueue next = cursor.next; 获取到cursor的next,下一个WeakOrderQueue
      4. cursor.owner.get() == null WeakOrderQueue 所属的回收线程不存在了,需要做清理工作,将不存在的线程中的缓存对象全部转移到stack中
        1. if (cursor.hasFinalData()) { ==> cursor.transfer(this) 如果有对象,就死循环一直回收直到获取不到数据
        2. prev.next = next; 将当前WeakOrderQueue进行释放
      5. prev = cursor; cursor = next; 将cursor指针移到下一个WeakOrderQueue

Netty设计模式应用

  1. 单例模式
    1. image.png
    2. ReadTimeoutException, MqttEncoder

public final class ReadTimeoutException extends TimeoutException {

    private static final long serialVersionUID = 169287984113283421L;
    //没有使用到这个类的时候static属性是不会初始化的,加载类的时候会有一个同步块,是线程安全的
    public static final ReadTimeoutException INSTANCE = new ReadTimeoutException();

    private ReadTimeoutException() { }
}
public final class MqttEncoder extends MessageToMessageEncoder<MqttMessage> {

    public static final MqttEncoder INSTANCE = new MqttEncoder();

    private MqttEncoder() { }
}
  1. 策略模式
    1. image.png

public class Strategy {
    private Cache cacheMemory = new CacheMemoryImpl();
    private Cache cacheRedis = new CacheRedisImpl();

    public interface Cache {
        boolean add(String key, Object object);
    }

    public class CacheMemoryImpl implements Cache {
        @Override
        public boolean add(String key, Object object) {
            // 保存到map
            return false;
        }
    }

    public class CacheRedisImpl implements Cache {
        @Override
        public boolean add(String key, Object object) {
            // 保存到redis
            return false;
        }
    }

    public Cache getCache(String key) {
        if (key.length() < 10) {
            return cacheRedis;
        }
        return cacheMemory;
    }
}
  1. DefaultEventExecutorChooserFactory # newChooser
    1. if (isPowerOfTwo(executors.length)) { 如果是2的幂次方(对自增取模的方法做了优化)
      1. return new PowerOfTowEventExecutorChooser(executors);
    2. else
      1. return new GenericEventExecutorChooser(executors);
  2. 装饰者模式
    1. image.png
    2. WrappedByteBuf 装饰 ByteBuf
    3. UnreleasableByteBuf 不支持释放的ByteBuf
    4. SimpleLeakAwareByteBuf 内存泄漏感知的ByteBuf
      1. ResourceLeak 跟踪内存泄漏
      2. release 方法 ==> leak.close();
  3. 观察者模式
    1. image.png

public void write(NioSocketChannel channel, Object object) {
    //channel写出数据时返回ChannelFuture(被观察者) ,可以为事件添加观察者
    ChannelFuture channelFuture = channel.writeAndFlush(object);
    channelFuture.addListener(future -> {
        if (future.isSuccess()) {

        } else {

        }
    });
    channelFuture.addListener(future -> {
        if (future.isSuccess()) {

        } else {

        }
    });
    channelFuture.addListener(future -> {
        if (future.isSuccess()) {

        } else {

        }
    });
}
  1. AbstractChannelHandlerContext# writeAndFlush(Object msg)
  2. writeAndFlush(msg, newPromise())
  3. newPromise() 创建一个被观察者
  4. new DefaultChannelPromise(channel(), executor());
  5. return promise; 返回观察者
  6. channelFuture.addListener 给被观察者添加一个观察者
  7. DefaultChannelPromise # addListener
  8. DefaultPromise # addListener
    1. addListener0(listener)
    2.
if (listeners == null) {
    //如果第一次添加,listeners 就是添加的Future对象
    listeners = listener;
} else if (listeners instanceof DefaultFutureListeners) {
    //后边添加 往new GenericFutureListener[2] 数组中添加元素,
    //如果数组长度不够,则进行扩容
    ((DefaultFutureListeners) listeners).add(listener);
} else {
    //第二次添加,listeners 是一个DefaultFutureListeners 
    //listeners = new GenericFutureListener[2]; 元素只有2个
    listeners = new DefaultFutureListeners((GenericFutureListener<? extends Future<V>>) listeners, listener);
}
  1. HeadContext # write ==> unsafe.write(msg, promise);
  2. 失败通知
    1. safeSetFailure(promise, WRITE_CLOSED_CHANNEL_EXCEPTION)
    2. promise.tryFailure(cause)
    3. DefaultPromise # tryFailure
    4. notifyListeners(); ==> notifyListenersNow(); ==> notifyListeners0((DefaultFutureListeners) listeners);
    5. l.operationComplete(future); 通知到用户的方法
  3. 正常通知
    1. outboundBuffer.addMessage(msg, size, promise);
    2. Entry entry = Entry.newInstance(msg, size, total(msg), promise); 将观察者添加到Entry中
    3. HeadContext # flush ==> unsafe.flush(); ==> flush0();
    4. ChannelOutboundBuffer # remove
    5. ChannelPromise promise = e.promise; 拿到被观察者
    6. safeSuccess(promise);
    7. PromiseNotificationUtil.trySuccess(promise, null, logger);
    8. p.trySuccess(result)
    9. DefaultPromise # trySuccess
    10. notifyListeners();
  4. 迭代器模式
    1. CompositeByteBuf # forEachByte
    2. AbstractByteBuf # forEachByte
    3. Component c = findComponent(index); 获取到当前元素属于CompositeByteBuf 中的哪个组件
    4. c.buf.getByte(index - c.offset) 获取组件中的byte数据
  5. 责任链模式
    1. image.png
    2. image.png
    3. ChannelHandler (责任处理器接口)
    4. ChannelPipeline (链表结构的创建者)
    5. ChannelHandlerContext 上下文
    6. 终止传播,不调用ctx.fireChannelRead 就表示不往下进行传播

Netty高并发性能调优

  1. image.png

单机百万连接构建

  1. image.png
  2. image.png
  3. ulimit - n 一个系统一个进程最多打开多少个文件数
  4. image.png
  5. cat /proc/sys/fs/file-max 一个系统最多能打开的文件数
  6. image.png

Netty应用级别性能调优

  1. 将处理的业务逻辑放到线程池中处理,测试指定线程池的大小
  2. ch.pipeline().addLast(businessGroup, ServerBusinessHandler.INSTANCE); 处理业务逻辑的时候指定线程池
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值