Netty-流量整形工作机制

   流量整形工作原理及源码分析
      消息读取的流量整形
      消息发送流量整形


流量整形工作原理及源码分析

     流量整形工作原理:拦截channelRead()和write()方法,计算当前需要发送的消息大小,对读取和发送阈值进行判断,如果达到了阈值,则暂停读取和发送消息,待下一个周期继续处理,以实现在某个周期内对消息读写速度进行控制。


消息读取的流量整形

     以ChannelTrafficShapingHandler为例,消息读取的流量整形工作流程如图:
在这里插入图片描述
     (1) 在解码之前拦截channelRead(),计算读取的ByteBuf大小,源码如下(AbstractTrafficShapingHandler):


protected long caculateSize(Object msg){
	if(msg instanceof ByteBuf){
		return ((ByteBuf) msg).readableBytes();
	}
	if(msg instanceof ByteBufHolder){
		return ((ByteBufHolder) msg).content().readableBytes();
	}
	return -1;
}

     (2) 计算需要暂停读取消息的等待时间,源码如下(TrafficCounter):


public long readTimeToWait(final long size, final long limitTraffic, final long maxTime, final long now){
	bytesRecvFlowControl(szie);
	if(size == 0 || limitTraffice == 0){
		return 0;
	}
	final long lastTimeCheck = lastTime.get();
	long sum = currentReadBytes.get();
	long localReadTime = readingTime;
	long lastRB = lastReadBytes;
	final long interval = now - lastTimeCheck;
	long pastDelay = Math.max(this.lastReadingTime - lastTimeCheck, 0L);

	if(interval > 10L){
		long time = sum * 1000 / limitTraffic - interval + pastDelay;
		if(time > maxTime && now + time - localReadingTime > maxTime){
			time = maxTime;
		}
		readingTime = Math.max(localReadingTime, now + time);
		return time;
	}
	readingTime = Math.max(localReadingTime, now);
	return 0;
}

     (3) 满足整形条件,则修改Channel状态为非自动读取,并将READ_SUSPENDED的属性修改为true,Channel进入整形形态,不再从TCP缓冲区读取请求消息,相关代码(AbstractTrafficShapingHandler的channelRead()):


if(config.isAutoRead() && isHandlerActive(ctx)){
	config.setAutoRead(false);
	channel.attr(READ_SUSPEND).set(true);
}

     (4) 创建恢复Channel为可读的定时任务,由Channel对应的NioEventLoop执行,代码如下(AbstractTrafficShapingHandler的channelRead方法):


Attribute<Runnable> attr = channel.attr(REOPEN_TASK);
Runnable reopenTask = attr.get();
if(reopenTask == null){
	reopenTask = new ReopenReadTimerTask(ctx);
	attr.set(reopenTask);
}
ctx.executor().schedule(reopenTask, wait, TimeUnit.MILLISECONDS);

     (5) 到达暂停读取时间之后,触发定时任务,重新修改Channel的READ_SUSPENDED属性为false,同时将autoRead设置为true,代码如下(ReopenReadTimerTask):

public void run(){
	Channel channel = ctx.channel();
	ChannelConfig config = channel.config();
	if(!config.isAutoRead() && isHandlerActive(ctx)){
		channel.attr(READ_SUSPENDED).set(false);
	}else{
		channel.attr(READ_SUSPENDED).set(false);
		config.setAutoRead(true);
		channel.read();
	}
}

消息发送流量整形

     消息发送的流量整形工作流程如图:
在这里插入图片描述
     (1) 计算需要暂停发送的等待时间,代码如下(TraffiCounter类):


public long writeTimeToWait(long size, long limitTraffic, long maxTime, long now) {
        this.bytesWriteFlowControl(size);
        if(size == 0 || limitTraffice == 0){
			return 0;
		}
        long lastTimeCheck = this.lastTime.get();
        long sum = this.currentWrittenBytes.get();
        long lastWB = this.lastWrittenBytes;
        long localWritingTime = this.writingTime;
        long pastDelay = Math.max(this.lastWritingTime - lastTimeCheck, 0L);
        long interval = now - lastTimeCheck;
        long time;
        if (interval > 10L) {
            time = sum * 1000L / limitTraffic - interval + pastDelay;
            if (time > 10L) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Time: " + time + ':' + sum + ':' + interval + ':' + pastDelay);
                }

                if (time > maxTime && now + time - localWritingTime > maxTime) {
                    time = maxTime;
                }

                this.writingTime = Math.max(localWritingTime, now + time);
                return time;
            } 
            this.writingTime = Math.max(localWritingTime, now);
            return 0L;
        } 
    }

     (2) 如果需要暂停发送的时间大于10ms,则通过定时任务进行消息发送,否则直接封装成ToSend任务,添加到NioEventLoop的Task队列立即执行,代码如下(AbstractTrafficShapingHandler类):


public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        long size = this.calculateSize(msg);
        long now = TrafficCounter.milliSecondFromNano();
        if (size > 0L) {
            long wait = this.trafficCounter.writeTimeToWait(size, this.writeLimit, this.maxTime, now);
            if (wait >= 10L) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Write suspend: " + wait + ':' + ctx.channel().config().isAutoRead() + ':' + isHandlerActive(ctx));
                }

                this.submitWrite(ctx, msg, size, wait, now, promise);
                return;
            }
        }

        this.submitWrite(ctx, msg, size, 0L, now, promise);
    }

     (3) 由Channel对应的NioEventLoop从messagesQueue中循环获取待发送的ToSend任务,执行消息发送操作,相关代码(ChannelTrafficShapingHandler类):


private void sendAllValid(ChannelHandlerContext ctx, long now) {
        synchronized(this) {
            ChannelTrafficShapingHandler.ToSend newToSend = (ChannelTrafficShapingHandler.ToSend)this.messagesQueue.pollFirst();

            while(true) {
                if (newToSend != null) {
                    if (newToSend.relativeTimeAction <= now) {
                        long size = this.calculateSize(newToSend.toSend);
                        this.trafficCounter.bytesRealWriteFlowControl(size);
                        this.queueSize -= size;
                        ctx.write(newToSend.toSend, newToSend.promise);
                        newToSend = (ChannelTrafficShapingHandler.ToSend)this.messagesQueue.pollFirst();
                        continue;
                    }

                    this.messagesQueue.addFirst(newToSend);
                }

                if (this.messagesQueue.isEmpty()) {
                    this.releaseWriteSuspended(ctx);
                }
                break;
            }
        }

        ctx.flush();
    }

     如果到达了执行时间,之前取出来待发送的消息需要重新加入消息发送队列,由于消息是有先后顺序的,所以重新加入队列首位。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值