• 最快的 TCP 拥塞控制算法


    声明:本文我并非表达这样的观点,即 “激进发包,就可以做出很好的协议”,我只是为想这么做的人提供一个如何这么做的方法。我说这样的算法是“快”的,因为它确实是快的,我并没有说它是“好”的,对于它的代价,我也提到了,但并不多说。

    最快的 TCP 拥塞控制算法就是去掉拥塞控制算法。

    给出最牛 X 的拥塞控制算法前,先交代背景。

    先看 TCP 拥塞状态机的必要性。

    为确保拥塞控制作用下界,防止拥塞控制算法违宗旨,从而加重拥塞或破坏公平,TCP 不得不收回一些拥塞控制权限,以悬崖勒马。比方说:

    • 发生丢包时接管算法,防止进一步拥塞。
    • 对激进传输的后果实施不可违抗的惩罚。

    so?想维持硬编码的大 cwnd 是不可能的。巨大 cwnd 意味着激进传输,丢包后依然激进重传,这不是拥塞控制,这是制造拥塞。TCP 拥塞状态机阻止了拥塞控制算法的如此行为,它在你无法控制的逻辑里强制将 cwnd 拉低,这是高尚的。

    之前说过 BBR 改变了这一切。

    为使 BBR 靠采集 Delivery Rate 判断拥塞,不得不给 BBR 更多控制权,对丢包进行另一种解释而避开拥塞状态机的动作 。

    但至少对 Linux TCP 而言,拥塞状态机和拥塞控制算法是分离的,为支持 BBR,不得不重构拥塞状态机实现,引入全权接管算法的 cong_control 回调函数,简而言之,一切都在该回调中完成。

    对于 4.9 以上版本内核,终于可以写一个“固定 cwnd”的算法了,这将是最快的算法:

    #include 
    #include 
    
    static u32 const_cwnd = 1000;
    module_param(const_cwnd, uint, 0664);
    
    static void const_main(struct sock *sk, const struct rate_sample *rs)
    {
            tcp_sk(sk)->snd_cwnd = const_cwnd;
    }
    
    static void const_cong_avoid(struct sock *sk, u32 ack, u32 acked)
    {
            tcp_sk(sk)->snd_cwnd = const_cwnd;
    }
    
    static void const_init(struct sock *sk)
    {
            tcp_sk(sk)->snd_cwnd = const_cwnd;
    }
    
    static u32 const_ssthresh(struct sock *sk)
    {
            return const_cwnd;
    }
    
    static void const_set_state(struct sock *sk, u8 new_state)
    {
            if (new_state == TCP_CA_Loss) {
                    tcp_sk(sk)->snd_cwnd = const_cwnd;
            }
    }
    
    static u32 const_undo(struct sock *sk)
    {
            return tcp_sk(sk)->snd_cwnd;
    }
    
    static struct tcp_congestion_ops tcp_const_cong_ops __read_mostly = {
            .name                = "const",
            .undo_cwnd        = const_undo,
            .init                = const_init,
            .cong_control        = const_main,
            /*.cong_avoid        = const_cong_avoid,*/
            .ssthresh        = const_ssthresh,
            .set_state        = const_set_state,
    };
    
    static int __init const_register(void)
    {
            return tcp_register_congestion_control(&tcp_const_cong_ops);
    }
    
    static void __exit const_unregister(void)
    {
            tcp_unregister_congestion_control(&tcp_const_cong_ops);
    }
    
    module_init(const_register);
    module_exit(const_unregister);
    
    MODULE_LICENSE("GPL");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    在 4.9 内核之前,若要如此效果,非要 kprobe/systemtap 强行 hack,给一个 stap 脚本 setcwnd.stp:

    #!/usr/local/bin/stap -g
    
    // 使用方法:./setcwnd.stp 10000
    %{
    #include 
    %}
    
    function _set_cwnd(skk:long, ptype:long, pconst_cwnd:long)
    %{
            struct sock *sk = (struct sock *)STAP_ARG_skk;
            int const_cwnd = (int)STAP_ARG_pconst_cwnd;
            struct tcp_sock *tp = tcp_sk(sk);
            // 可设置更复杂过滤规则
            if (htons(inet_sk(sk)->inet_dport) == 1234) {
                    tp->snd_cwnd = const_cwnd;
            }
    %}
    
    probe kernel.function("tcp_write_xmit")
    {
            _set_cwnd($sk, 1, $1);
    }
    
    probe kernel.function("tcp_xmit_recovery")
    {
            _set_cwnd($sk, 0, $1);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    否则,把上面代码的注释打开,换把 cong_control 回调注释掉,设置的 const_cwnd 是维持不住的。
    效果不展示,自己试,25 Gbps 带宽,丢包率调到 25% ,BBR/CUBIC 已经憋死,const 依然可保持 24 Gbps 无损带宽。Makefile 如下,直接make:

    obj-m += tcp_const.o
    
    all:
            make -C /lib/modules/`uname -r`/build M=`pwd` modules
    
    • 1
    • 2
    • 3
    • 4

    使能命令:

    sysctl -w net.ipv4.tcp_congestion_control=const
    
    • 1

    调整 cwnd 命令:

    echo 50000 >/sys/module/tcp_const/parameters/const_cwnd
    
    • 1

    终极问题,可实用吗?

    拥塞控制本即社会博弈,const 算法本质上是 “取消了拥塞控制”,预期任何使用者均不会设置一个保守 cwnd。

    但除重传代价极大外,有另一个阻止部署 const 算法的因素,“所有参与方都付出大代价后,价值取向就反转了。”,流氓的淫威建立在大多数人总是退让基础上,不可能人人都是流氓,流氓只有一两个时,流氓才有力量。

    不用担心,若不计带宽成本,少量人使用是毫无问题的。如今网络带宽承载力很高,即使最后一公里边缘带宽也不是凭一两条流能淹没的,何况运营商只是软限制超卖,就更别提骨干网了。因此:

    • 使用const算法不会制造拥塞,让你无愧。
    • 使用const算法可以带来收益,让你无悔。
    • 使用const算吧会增加重传率,让你知情。

    可劲造。

    很久以前我就想硬编码 cwnd,比如我有一条很牛的专线,又不想受到偶尔误码丢包对带宽的影响,但总是 hold 不住硬编码的 cwnd,因为除了在 cc 中以外,TCP 拥塞状态机也会设置 cwnd 想取消这些修改必须修改内核或采用 hack 手段。BBR 发布以后,做这件事成了可能。最近也有经理问到,所以就写了本文。不过还可以做得更好点,为每个连接硬编码一个 cwnd:增加一个 socket option,将这个硬编码的 cwnd 保存在每个 tcp_sock 对象里。

    浙江温州皮鞋湿,下雨进水不会胖。

  • 相关阅读:
    【校招VIP】“推推”产品项目课程:产品的规划和商业化分析
    [并发编程]------死肝ReentrantLock源码
    驱动——LED灯循环闪烁
    java毕业设计德纳影城售票管理Mybatis+系统+数据库+调试部署
    windows部署python项目(以Flask为例)到docker,通过脚本一键生成dockerfile并构建镜像启动容器
    三分钟让你掌握数据结构——单链表
    Python——案例
    androdi知识笔记
    Virtual File System了解
    Linux系统InfluxDB数据和日志目录迁移教程
  • 原文地址:https://blog.csdn.net/dog250/article/details/126414088