-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
smux 阻塞问题 #722
Comments
有限的内存,并行,充分的带宽利用,三者不可兼得。这是一个困境。-- "smux dilemma" |
可以参考这里的最后一段:https://zhuanlan.zhihu.com/p/53849089 |
我就是看了您这篇文章,才推测前述使用场景会有阻塞问题,然后验证了下果然如此。内存不可能加得无限大,却可能有极大的文件的下载需求,阻塞不可避免。另一个角度考虑,数据中心间的网络质量远好于客户端,缓冲区通常会塞满,如果缓冲区设得太大,中途突然放弃下载,其实也是白白浪费了许多流量的。 所以我有个疑问,为什么要把多路的数据缓存在一个大缓冲区里?为每条连接设立独立的小缓冲区是否可行? |
多条链接,需要开启拥塞控制算法来避免同一底层物理链路的相互竞争。 然而开启拥塞控制算法的结果就是,在高丢包链路下,不可能做到稳定传输,因为窗口不够刚性。 |
我在想是否可以在smux做一个流量整形,这样也许会从发送的角度来控制发送者的公平性 |
流量整形后,对于收方 smux 缓冲区会有什么影响呢? |
流量整形后,发送端的某个流不会霸占整个带宽,发送带宽竞争更加公平。 这样产生的效果是,对某个流量高的stream,产生了write()阻塞,阻塞会反馈到发送源头,通过拥塞控制的传导,使其发送速度减慢。 当然,这个减慢是在窗口(带宽)满了后的行为,窗口不满的时候不会产生。 |
已试验,结论是……没观测到改善(可能改善实在太有限)。 说下我的架构吧,手机/电脑 <==ss over TCP==> 上海VPS <==ss over KCP==> 硅谷VPS(kcptun接到本机 v2ray 进程) <====> 代理目标 上海(客户端)配置:/usr/local/bin/kcptun_client -r "xxxxxxx:443" -l ":443" -mode manual -nodelay 1 -interval 10 -resend 2 -nc 1 --sndwnd 3072 --rcvwnd 3072 --smuxbuf 33554432 --ds 7 --ps 3 --nocomp --crypt xor --key "密码" --dscp 46 --autoexpire 600 --scavengettl -1 --tcp 硅谷(服务端)配置:/usr/local/bin/kcptun_server -t "127.0.0.1:339" -l ":443" -mode manual -nodelay 1 -interval 10 -resend 2 -nc 1 --sndwnd 3072 --rcvwnd 3072 --smuxbuf 33554432 --ds 7 --ps 3 --nocomp --crypt xor --key "密码" --dscp 46 --tcp 其中,上海到硅谷 30-100 Mbps,代理目标在硅谷同机房,带宽很大(1Gbps 左右) 我在个人电脑上,wget 限制一个极低的速率(10k)下载代理目标的文件,开始下载后立刻不断刷新 Google 首页,观察到:刚开始下载时,还能流畅刷新,大约四秒钟后,出现阻塞,阻塞持续很长一段时间,然后恢复畅通。 我推测:开始下载后的前四秒钟,不阻塞是因为上海机子上的 32MB smuxbuf 还没有填满(上海到硅谷 30-100 Mbps),填满后,即使发送端优先发送其他流的数据也作用不大,因为 KCP 层还有很大一段缓冲区,其他流的数据一时半会儿也到不了上层的 smux。当然这和我限制极低的速率下载有关系,毕竟是为了模拟这样一种极端情况(几乎不取走数据)。其实这样一想,除非加大 KCP 层连接数量,否则还真是个无解的问题! 不知道我的理解是否正确,请不吝赐教,谢谢! |
你的rcvwnd太大,因为很可能rcvwnd已经超过线路最大带宽,那么物理上的拥塞一直没有反馈到逻辑的拥塞上,无法触发shaper逻辑。你观测到的阻塞,很可能只是因为线路的阻塞。 注意,这个改动的假设是,滑动窗口满后,smux会均匀发送各个流的数据。如果线路没有拥塞,那么是FIFO的情况。 |
// shaper shapes the sending sequence among streams
func (s *Session) shaperLoop() {
var reqs shaperHeap
var next writeRequest
var chWrite chan writeRequest
for {
if len(reqs) > 0 {
chWrite = s.writes
next = heap.Pop(&reqs).(writeRequest)
} else {
chWrite = nil
}
select {
case <-s.die:
return
case r := <-s.shaper:
if chWrite != nil { // next is valid, reshape
heap.Push(&reqs, next)
}
heap.Push(&reqs, r)
case chWrite <- next:
}
}
} 注意这里的逻辑: |
我觉得是排队了,开始下载后,用 iftop 看到几秒钟就从代理目标接收了约 70MB 的数据随后缓慢增长,而 wget 取走数据的速度只有 10KB/s,数据不会消失,一定是填在整条链路的各处缓冲区里了 |
增大两端 smuxbuf 到 100MB 以上,极低速率下载 100MB 测试文件时其他流的阻塞现象消失,几乎整个文件积压在上海 VPS(客户端),慢慢往我个人电脑传。 这种情况可以抽象成有一个数据源只管不断的发,尽管接收端速率低下,但起初因为中间节点 buffer 充足,会保持高速发送,等 buffer 陆续满了,阻塞反馈到数据源时,两个 kcptun 端点、v2ray 软件以及涉及到的所有 socket buffer 都已经满了 |
如果只管发送,不管读取,那么操作系统的 TCP socket buffer(net.ipv4.tcp_rmem),一样也会塞满,占用内存,这样的链接多了后,OS是分配不出来内存的,也会导致新的链接速度降到很低(rcv_wnd变小)。 可以理解为,操作系统的内存够大,在大多数时候避免了出现HOLB的问题,那么问题等价于提高 目前唯一缺乏的,是tcp per socket buffer的设置,即per stream buffer的设置。 要实现这个,就需要扩展smux,增加控制指令,告知发送方当前的stream buffer的大小。 |
https://github.com/xtaci/smux/tree/v2 增加协议 |
辛苦了,明天编译一下跑跑看。系统 socket buffer 满的问题,如果代理的连接不复用的话,应该还是只阻塞该条代理连接本身吧,不会影响到其他连接。smux 算是(我的使用场景里)唯一存在连接复用的环节了。 |
但注意,虽然smux version 2.0可以缓解HOLB问题,但依然受制于 有限的内存,并行,充分的带宽利用,三者不可兼得 困境。
|
我放了个pre-release 可以先尝试下这个版本,设置如下:
|
做了对比实验,效果非常不错,不存在饿死其他 stream 的问题了 |
目前我认为这是唯一正确的办法, 即:实现对发送方Write()函数的阻塞控制,流式的窗口滑动。 |
@xtaci 的观点方向是正确的,我之前实现过类似算法,其实主要矛盾在于非阻塞io方式下如何设计良好的cork机制。我采用的办法是(说思路,忽略加锁细节): |
最近在整个系统上开了udp都能拿下的列队控制 结果无意发现 下载大文件好几个的时候 客户端网速打满 竟然不堵塞 我用的smux1 这个好像不是holb相关的事情?? 到时候我试试fifo就知道了 |
这个就是类似http/2队头阻塞(Head-of-line blocking)的问题。kcp协议本身只针对单连接,不支持多路复用。所以在kcp之上实现的多路复用必然会有队头阻塞的问题。从根本上解决只能像quic那样把多路复用移到协议同层上实现。 |
目前来看,如果是应用层没有控制流量的场景,如 HTTP 大文件下载,服务器发送的数据会倾向于把所有中间节点的缓冲区塞满,导致其他连接阻塞。换言之,只要下载的文件大小大于中间节点的最小 smuxbuf,就不可避免有阻塞的现象。我使用 32MB 的 smuxbuf,并观测到在下载 100MB 测试文件的起初的几秒钟内,其他连接几乎完全被阻塞,连 Google 首页也无法打开。
期待与您讨论 @xtaci
The text was updated successfully, but these errors were encountered: