教你如何 TCP 抢带宽

如果网络实在太慢太卡要怎么办?

自己动手并不适合每个人,也不意味着每个场景你都能登录服务器玩把戏,这时候就要用钱解决,给运营商支付更多钱买更好的服务。

支付更多钱意味着获得更高优先级,获得更高带宽和服务质量,但优先级是相对概念,资源总量不变,优先级意味着价格内卷,谁付钱多谁优先。而带宽是个截面,是瞬时量,在统计复用网络上是无法承诺刚兑的均值。

一个月花 100 元承诺给你 100Mbps 可有很多解释,但用户往往认为这是刚兑承诺,即月内随时都有 100Mbps,但这在技术上是不可能的。互联网流量特征是突发和静默并存,要获得高带宽利用率,就要拥有足够多条流来统计平滑突发和静默间的 gap,“足够多” 也是个统计描述,它本身也波动,并不是说时刻都有 “这么多”。所以,即使求平均做除法,合同带宽也不可能刚兑。

和优先级内卷类似,用户为获得更高带宽争相支付更多钱,但总带宽不变,更多的钱并没有带来更多带宽,而只带来更高带宽占比,和用更大 inflight 挤占 buffer 可挤兑更高带宽占比的假象一样,钱类比 buffer 就是了。若没有限价,必然导致价格内卷,进入拍卖模式。你不得不花更多钱 “保持”(而不是获得) 跟此前一样(而不是更多)的带宽,因为别人愿付更多钱。

综上,运营商靠用户出价内卷就能躺赚,哪有动力升级扩容。这种极端静态模式是大家都不想看到的,现实中往往要么均价包月,偶尔打折,要么类 95 计费,显然要合理得多。

若仅考虑拥塞成本,95 计费沾边儿,我的意思是,不要为成本而支付,而是为收益而缴税,这就好理解多了。

报文间隔越小,收费越高,税率设定为一个合理的常规 pacing 报文间隔,因此要缴纳的税额就是将发送序列所有报文的间隔乘适用税率金额的加和。结果,谁的 burst 率越高缴税越高,进而督促大家尽量平缓发送,减少拥塞。和大城市交通拥堵税一个意思。但一定不能预支付,因为预支付几乎肯定会进入内卷。

出售获得资源的机会,而不是出售资源本身。

关于计费如何缓解拥塞说得差不多了,现在假设上述 “拥塞税” 已成现实,并且你有足够的钱支付足够高的税,换句话说你对钱不在乎,并且你也能登录机器瞎折腾,如何抢带宽呢?

本文提供两种抢带宽的方式:

  • 恒定速率发送,不做拥塞控制,无论如何都以比如 100Mbps 的速率发送。
  • 保吞吐,不做拥塞控制,无论如何都尽量保住比如 100Mbps 的有效吞吐。

门槛问题一定要说,写个 TCP 发包(这里不再说 cc,因为除了套用了 Linux tcp cc 框架,剩下的事和拥塞控制无关,相反,它在制造拥塞)算法不难,麻烦的是很多人并不了解如何让自己的一些简单想法落地,而这个很简单,只要做过就忘不了。Linux 4.9 版本以上的内核,写一个内核模块即可,详见 写死 cwnd 猛怼(特别是 4.9 内核以前的 systemtap 方法)。

关于 Linux tcp cc 框架没啥好说的,它既带来机会又增加了束缚,relentless cc 就因为无法摆脱 Linux cc 状态机约束而无法尽情:

Our implementation is not quite as elegant, and consequently is not as accurate as the mental model. It is based on an existing TCP implementation that supports plugable congestion control modules. Unfortunately, some of the cwnd adjustments are wired into the loss recovery code and can not be suppressed directly. They can only be offset later in the plugable Relentless Congestion Control module.

接下来给出如何保吞吐的方法:https://github.com/marywangran/pixie (pixie 不是 “皮鞋” 的意思,是 “捣蛋鬼” 的意思,自行查词典,这个词知道的不多,代码怕打不开,在最后贴个文本,Makefile 自己写)
重点有两个:

  • 采用滑动平均的方式,丢包突然增加时紧缩,避免进一步丢包,而丢包突然减少时激进,挤占给多资源,为了实现简单,只算术平均,移动指数平均会更好。
  • 用 pacing_rate 挤占而不用 cwnd 挤占。pacing_rate 一定,理想情况下 cwnd 挤占没用,多出来的 cwnd 尚未发送,feedback 就返回了,此时 inflight < cwnd。

值得一提的是,这种事虽有效但并不值得一提,一个真正值得一提的 TCP 算法一定是不损人的,利己与否是自己的事,倒是次要。所以,如果一个算法不能在数学上证明以及在典型实践给出证据证明自己是不损人的,那它就永远无法普遍部署。

这种考虑的背后逻辑是,TCP 应用太广泛了,几乎涉及互联网的一切,互联网端 “几乎就是” TCP,如果一个随意的算法被普遍部署,稍微不慎的缺陷(不管有意的还是无心的)将伤害整个互联网传输,影响巨大。即使像 bbr 这样的算法已经出现并逐步部署了 7 年,它当前的状态依然还是没有进入正式 rfc,而 bbr 的普遍部署纯属工程化后果,理论上,风险依然巨大。

另一方面,如果只在小范围自用,你有能力抢占的带宽对于从边缘到骨干的真正互联网链路带宽就是沧海之一粟,你的存在无关紧要。进一步,在你把这种算法推广到足以被感知的程度前,非技术力量将介入,迫使你不得不停止自己的行为并支付已造成损害的代价。

quic 让人们对拥塞控制前景开始担忧,在应用程序中定制 cc 太容易了。cdn 厂商很容易脱离普遍标准(比如 AIMD)魔写 cc,就如我这个无脑 pixie 一样,然后得意洋洋上线,加之互联网公司技术人员对这领域缺乏认知,以互联网公司的双海量(海量缺乏认知的程序员部署海量服务)一推一拉作死,就像满高速路上都是刚拿到驾照买了车的特斯拉暴发户,互联网整体前景堪忧。

so,小作怡情,大作伤身,出来混早晚要还的,别玩脱手。

附:tcp_pixie.c

#include 
#include 
#include 

static int rate = 100000000;
module_param(rate, int, 0644);
static int feedback = 2;
module_param(feedback, int, 0644);

struct sample {
	u32	_acked;
	u32	_losses;
	u32	_tstamp_us;
};

struct pixie {
	u64	rate;
	u16	start;
	u16	end;
	u32	curr_acked;
	u32	curr_losses;
	struct sample *samples;
};

static void pixie_main(struct sock *sk, const struct rate_sample *rs)
{
	struct tcp_sock *tp = tcp_sk(sk);
	struct pixie *pixie = inet_csk_ca(sk);
	u32 now = tp->tcp_mstamp;
	u32 cwnd;
	u16 start, end;
	u64 prate;

	if (rs->delivered < 0 || rs->interval_us <= 0)
		return;

	cwnd = pixie->rate;
	if (!pixie->samples) {
		cwnd /= tp->mss_cache;
		cwnd *= (tp->srtt_us >> 3);
		cwnd /= USEC_PER_SEC;
		tp->snd_cwnd = min(2 * cwnd, tp->snd_cwnd_clamp);
		sk->sk_pacing_rate = min_t(u64, pixie->rate, READ_ONCE(sk->sk_max_pacing_rate));
		return;
	}

  // 丢包突然增加时采用紧缩策略,以在保吞吐时尽量减少进一步丢包,丢包突然减少时采用激进策略,以挤占比实际更多资源。移动平均是个好办法。
	pixie->curr_acked += rs->acked_sacked;
	pixie->curr_losses += rs->losses;
	end = pixie->end ++;
	pixie->samples[end]._acked = rs->acked_sacked;
	pixie->samples[end]._losses = rs->losses;
	pixie->samples[end]._tstamp_us = now;

	start = pixie->start;
	while (start < end) {
    // 至少保持半个 srtt 反馈周期,越久越不抖动但性能可能不达预期,这里的 “抖动” 要反着理解
		if (2 * (now -  pixie->samples[start]._tstamp_us) > feedback * tp->srtt_us) {
			pixie->curr_acked -= pixie->samples[start]._acked;
			pixie->curr_losses -= pixie->samples[start]._losses;
			pixie->start ++;
		}
		start ++;
	}
	cwnd /= tp->mss_cache;
	cwnd *= pixie->curr_acked + pixie->curr_losses;
	cwnd /= pixie->curr_acked;
	cwnd *= (tp->srtt_us >> 3);
	cwnd /= USEC_PER_SEC;

	prate = (pixie->curr_acked + pixie->curr_losses) << 10;
	prate /= pixie->curr_acked;
	prate *= pixie->rate;
	prate = prate >> 10;

	printk("##### curr_ack:%llu curr_loss:%llu rsloss:%llu satrt:%llu  end:%llu cwnd:%llu rate:%llu prate:%llu\n",
			pixie->curr_acked,
			pixie->curr_losses,
			rs->losses,
			pixie->start,
			pixie->end,
			cwnd,
			rate,
			prate);
	tp->snd_cwnd = min(cwnd, tp->snd_cwnd_clamp);
  // 用 pacing_rate 去挤兑(而不是 cwnd),促使单位时间内到达报文更多,以抵消丢包损耗。如果用 cwnd 的话就别 pacing。
	sk->sk_pacing_rate = min_t(u64, prate, sk->sk_max_pacing_rate);
  //sk->sk_pacing_rate = min_t(u64, pixie->rate, sk->sk_max_pacing_rate);
}

static void pixie_init(struct sock *sk)
{
	struct pixie *pixie = inet_csk_ca(sk);

	pixie->rate = (u64)rate;
	pixie->start = 0;
	pixie->end = 0;
	pixie->curr_acked = 0;
	pixie->curr_losses = 0;
	pixie->samples = kmalloc(U16_MAX * sizeof(struct sample), GFP_ATOMIC);
	cmpxchg(&sk->sk_pacing_status, SK_PACING_NONE, SK_PACING_NEEDED);
}

static void pixie_release(struct sock *sk)
{
	struct pixie *pixie = inet_csk_ca(sk);

	if (pixie->samples)
		kfree(pixie->samples);
}


static u32 pixie_ssthresh(struct sock *sk)
{
	return TCP_INFINITE_SSTHRESH;
}

static u32 pixie_undo_cwnd(struct sock *sk)
{
	struct tcp_sock *tp = tcp_sk(sk);
	return tp->snd_cwnd;
}

static struct tcp_congestion_ops tcp_pixie_cong_ops __read_mostly = {
	.flags		= TCP_CONG_NON_RESTRICTED,
	.name		= "pixie",
	.owner		= THIS_MODULE,
	.init		= pixie_init,
	.release	= pixie_release,
	.cong_control	= pixie_main,
	.ssthresh	= pixie_ssthresh,
	.undo_cwnd 	= pixie_undo_cwnd,
};

static int __init pixie_register(void)
{
	BUILD_BUG_ON(sizeof(struct pixie) > ICSK_CA_PRIV_SIZE);
	return tcp_register_congestion_control(&tcp_pixie_cong_ops);
}

static void __exit pixie_unregister(void)
{
	tcp_unregister_congestion_control(&tcp_pixie_cong_ops);
}

module_init(pixie_register);
module_exit(pixie_unregister);
MODULE_LICENSE("GPL");

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

你可能感兴趣的:(tcp/ip,github,网络协议)