基于 TCP 实现极简的业务流控

2020-06-14 11:07:11 +08:00
 feng32
假设有一系列实时数据 (Message 结构) 要通过 TCP 发送

我们要实现: TCP 传输通畅的时候,按照预定速率发送; TCP 阻塞 (但是连接没有断开) 时,可以丢掉一些 Message

我想到的一个非常简单的方法是:把 TCP 发送缓冲区调大点,然后调用 send 时,如果能把 Message 一次性填进去,就填进去,否则就把 Message 整个丢掉

问题来了:这个方案可实施吗?如何让 send api 工作在原子的、非阻塞模式?
3319 次点击
所在节点    程序员
24 条回复
ho121
2020-06-14 11:09:39 +08:00
udp 不好么
catror
2020-06-14 11:21:33 +08:00
可以,不过当前消息有部分填进去的话,你只能丢后面的消息。
gamexg
2020-06-14 11:35:06 +08:00
增加一个队列
开一个线程,从队列取消息写入 tcp 连接
其他线程发消息都是发送到队列,队列满了就丢弃。
rrfeng
2020-06-14 11:51:28 +08:00
还是从业务层实现比较好。
xuanbg
2020-06-14 12:01:09 +08:00
不好,传输层不应该干涉 /影响业务层的逻辑
feng32
2020-06-14 12:02:39 +08:00
@ho121 UDP 长度不够

如果消息比较短,UDP 就完全没问题了
feng32
2020-06-14 12:04:01 +08:00
@catror 要避免的,就是 “只填消息的一部分进去”
catror
2020-06-14 12:08:14 +08:00
@feng32 所以我说只能丢后面的消息,填了一部分的消息剩余部分你要发完。对于你来说,丢当前这条和丢下一条并没有区别。
Vegetable
2020-06-14 12:19:23 +08:00
说的好像很复杂,其实逻辑抽象一下很简单,就是发送消息的时候判断是否要丢弃下一条消息。
如果所谓阻塞是通过前 N 条消息判断的:当前时间距离之前 N 条消息开始发送的时间已经超过了 t,则认为当前网络阻塞,判断下一条消息是否可以丢弃,可以丢弃则丢弃。

这么抽象一下之后,就和使用什么协议没有关系了。
optional
2020-06-14 12:20:09 +08:00
debug 的时候要死人。
feng32
2020-06-14 12:25:07 +08:00
@catror 假设生产的速率是一定的,在 3F 的解法中:

每次生产出一个 Messsage (onNewMessage),就调用一次 queue.push() 方法实现原子操作;另一个线程从 queue 中不断读取数据并发送

在 8F 的解法中:

每次生产一个 Message (onNewMessage),就调用 send() 方法把这个 Message 整个发出去,如果需要等待,就一直阻塞在那里;这时因为发生了阻塞,实际上也就不会有新的 Message 产生

本质上,前者是 “生产不阻塞” 模式,后者是 “阻塞生产” 模式

我想了一下,对我的实际场景,两者都是可以的。但是 8F 的解法对生产的逻辑有一个要求:生产者要能感知到阻塞的发生,在阻塞结束后,测量一下阻塞了多少时间,然后下次生产新的实时数据,而不因为阻塞导致后续生产的数据实时性发生整体偏移
reus
2020-06-14 12:43:46 +08:00
TCP 是可靠传输,除非你断开连接,否则不可能实现“丢 message”

再说了,协议实现怎么知道什么是“message”? TCP 是流式传输,传输层也不可能引入这个概念

没有哪个 socket 实现提供缓冲区的控制,所以你这纯粹是空想

允许丢弃的当然用 UDP,自己切帧再组合就是了,不存在什么“长度不够”

想那么多,难道不会写个 demo 验证自己的想法?如果没有验证的能力,那还是多学习,少思考比较好。
jedihy
2020-06-14 12:44:00 +08:00
缓冲区大小和网络拥塞不拥塞有什么关系
feng32
2020-06-14 12:50:11 +08:00
@jedihy 在原方案下,即能够知道缓冲区能不能再放下一个 Message,而此时原本最多只有 64K 缓冲区,一个 Message 都放不下,那就肯定不行了

但是如果采用 3F / 8F 的方案,就没有这个问题了
feng32
2020-06-14 12:54:28 +08:00
@reus 标题里已经提到了,这里要做的是业务层的流控,传输层用的是 TCP

3F / 8F 已经给出比较好的解法了,3F 是一个解法,8F 是一个绕过的思路
feng32
2020-06-14 12:59:17 +08:00
@reus 我刚才查了一下,还真有这个方法,我原本以为这种功能应该不存在的

https://stackoverflow.com/questions/38359272/linux-sockets-how-to-get-number-of-bytes-packets-in-sending-buffer

所以这就是第三个解法了 :)
GeruzoniAnsasu
2020-06-14 15:00:41 +08:00
“业务流控” 跟 tcp 压根就没什么关系
sujin190
2020-06-14 15:01:03 +08:00
如果你的意思是很多大 message 会占用很多传输时间,网络不好的时候会阻塞整体性能的话
一般来说这种情况下用 tcp 的话,更常用的做法应该是大 message 使用单独连接发送,小消息使用一个连接,网络不好大消息超过一定时间无法传输完成直接关闭连接就是了,考虑到连接建立开销啥的,用连接池就行了吧
不能开连接的话,那么应该是分小帧传输,加入优先级就行,大消息优先级比小消息优先级更低,这样也可以保证良好性能
raynor2011
2020-06-14 15:10:13 +08:00
接收方判断下消息发送接收时间就行了吧
feng32
2020-06-14 15:11:55 +08:00
@sujin190 实际上是类似视频直播的场景,已经卡了几帧了那就算了,下次继续发最新画面

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/681402

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX