• writeAndFlush之发送速率不匹配(转载)


    背景知识
    如果业务线程调用writeAndFlush()发送消息,会生成WriteAndFlushTask,交由IO线程处理,write操作将消息写入ChannelOutboundBuffer,flush操作将ChannelOutboundBuffer写入socket的发送缓冲区;
    ChannelOutboundBuffer是单向链表,没有容量限制;
    ChannelOutboundBuffer虽然无界,但是可以给它配置一个高水位线和低水位线,当buffer的大小超过高水位线的时候对应channel的isWritable就会变成false,当buffer的大小低于低水位线的时候,isWritable就会变成true。应用发送数据前应该对isWritable进行判断,防止OOM。高水位线和低水位线是字节数,默认高水位是64K,低水位是32K,通过以下方式进行设置:
    .option(ChannelOption.WRITE_BUFFER_HIGH_WATER_MARK, 64 * 1024)
    .option(ChannelOption.WRITE_BUFFER_LOW_WATER_MARK, 32 * 1024)
    1
    2
    速率不匹配造成的问题
      如下图所示,当业务线程产生消息的速度大于Socket的发送速率时,首先TCP发送缓冲区会被填满,然后后续所有待发送的数据会不断的占用内存加入到ChannelOutboundBuffer中,出现OOM;


    速率不匹配解决方案
      利用ChannelOutboundBuffer的高低水位特性形成闭环链路:当ChannelOutboundBuffer的容量超过高水位时降低消息产生的速度,当ChannelOutboundBuffer的容量小于低水位时增加消息产生的速度,如下图所示:

    ChannelOutboundBuffer的容量过高或过低时都会触发fireChannelWritabilityChanged()方法,因此可通过重写channelWritabilityChanged()方法调整消息产生速度,如下所示:

    public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
    if(ctx.channel().isWritable()){
    //小于低水位,增加速度
    }else{
    //超过高水位,降低速度
    }
    }
    1
    2
    3
    4
    5
    6
    7
    其它策略
    速率不匹配时还有其它很多策略,如同步阻塞当前业务线程、丢弃当前消息,或者统计Socket的实际发送速率来调整消息产生速率等,代码示例如下:

    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;
    import io.netty.channel.ChannelFuture;
    import io.netty.channel.ChannelFutureListener;
    import io.netty.channel.ChannelHandlerContext;

    public class ChannelWriteUtils {
    /**
    * 类描述:Socket实际发送Qps统计类
    **/
    public static class ChannelWriteMetrics {
    long startTime;
    int qps;
    int cnt;

    ChannelWriteMetrics() {
    startTime = System.currentTimeMillis();
    cnt = 0;
    }

    void inCreaseCnt() {
    cnt++;
    }

    void updateMetrics() {
    qps = cnt;
    cnt = 1;
    startTime = System.currentTimeMillis();
    }

    public long getStartTime() {
    return startTime;
    }

    public int getQps() {
    return qps;
    }

    public int getCnt() {
    return cnt;
    }

    }

    private static Map<ChannelHandlerContext, ChannelWriteMetrics> map = new ConcurrentHashMap<>();

    /**
    * 策略1:当ChannelOutboundBuffer不可写时产生消息,自适应调整发送速度
    **/
    public static void processWithMetrics(ChannelHandlerContext ctx, Object msg) {
    if (ctx.channel().isWritable()) {
    ChannelFuture future = ctx.writeAndFlush(msg);
    future.addListener(new ChannelFutureListener() {
    @Override
    public void operationComplete(ChannelFuture future) throws Exception {
    ChannelWriteMetrics metrics = map.putIfAbsent(ctx, new ChannelWriteMetrics());
    if (System.currentTimeMillis() - metrics.getStartTime() < 1000) {
    metrics.inCreaseCnt();
    } else {
    metrics.updateMetrics();
    }
    }
    });
    } else {
    ChannelWriteMetrics metrics = map.get(ctx);
    int qps = (null != metrics ? metrics.getQps() : 0);
    //发送消息(包含Socket实际发送的Qps)
    }

    }

    /**
    * 策略2:当ChannelOutboundBuffer不可写时同步阻塞
    **/
    public static void processWithSync(ChannelHandlerContext ctx, Object msg) {
    if (ctx.channel().isWritable()) {
    ChannelFuture future = ctx.writeAndFlush(msg);
    future.addListener(new ChannelFutureListener() {
    @Override
    public void operationComplete(ChannelFuture future) throws Exception {
    if (!future.isSuccess()) {
    // 发送失败
    }
    }
    });
    } else {
    try {
    ctx.writeAndFlush(msg).sync();
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    }

    /**
    * 策略3:当ChannelOutboundBuffer不可写时直接丢弃消息
    **/
    public static void processWithAbort(ChannelHandlerContext ctx, Object msg) {
    if (ctx.channel().isWritable()) {
    ctx.writeAndFlush(msg);
    }
    }
    }
    ————————————————
    版权声明:本文为CSDN博主「库昊天」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/yangguosb/article/details/79121599

  • 相关阅读:
    在床上手机看完电影让电脑关机 休眠 golang源码--配合手机ES浏览器开一个FTP
    goland授权
    goland 交叉生成linux文件
    串口2345常出错误记录
    [转]Golang号称高并发,但高并发时性能不高
    gogland如何配置路径,解决找不到相对路径配置文件的问题
    window ssh key访问linux
    Vue.js指令小结
    GIT Introduction
    scrapy 简单介绍
  • 原文地址:https://www.cnblogs.com/zhangshiwen/p/14531176.html
Copyright © 2020-2023  润新知