【问题标题】:Making UDP lose less messages in .NET/Mono使 UDP 在 .NET/Mono 中丢失更少的消息
【发布时间】:2015-07-08 23:20:20
【问题描述】:

我们目前正在为一个开源学术项目Logbus-ng 执行一些基准测试。它基本上通过 UDP (RFC 5426) 和 TLS (RFC 5425) 实现 Syslog 协议。 我们知道 TLS 的优点是可靠性(即我们不会丢失消息),但缺点是性能。

我们有一个基准测试客户端和一个特殊的伪造 Apache 安装,可以高速发送消息。 我们的目标是将UDP数据包的丢失减少到最低限度。 Apache 1.3.41 已被检测为通过 UDP 发送特殊日志消息(不是 Syslog 格式,而是我们在服务器端解析的特殊短语法),并且这种检测使它在 httpd 启动时发送超过 2000 条消息,并且我们希望它发生 :)

此外,我可以告诉您,在 Apache 的启动阶段,少量消息(与我们提交给日志服务器的其他工作负载相比)以极快的速度发送高速率,可能淹没 UDP。

现在,日志服务器与 HTTP 服务器位于不同的机器上,并且两者都几乎没有像样的硬件(甚至没有双核 CPU,而是带有超线程的 Pentium 4)。日志服务器代码在 C# 中。以下方法由 4 个线程以 AboveNormal 优先级运行

    UdpClient _client;
    IQueue<T>[] _byteQueues; //not really IQueue, but a special FIFO queue class that reduces overhead to the minimum

    private void ListenerLoop()
    {
        IPEndPoint remoteEndpoint = new IPEndPoint(IPAddress.Any, 0);
        while (_listen)
        {
            try
            {
                byte[] payload = _client.Receive(ref remoteEndpoint);

                _byteQueues[
                    (((Interlocked.Increment(ref _currentQueue))%WORKER_THREADS) + WORKER_THREADS)%WORKER_THREADS].
                    Enqueue(payload);
            }
            catch (SocketException)
            {
            }
            catch (Exception)
            {
            } //Really do nothing? Shouldn't we stop the service?
        }
    }

为了减少线程在 Receive 方法之外花费的时间,我们不会在收到消息后解析消息,而是将其存储在 4 个特殊队列之一中,其他工作线程将读取这些队列。据我所知,.NET 调度器是贪婪的,所以无论线程等待多长时间,更高优先级的线程都会更早调度并可能导致饥饿,所以这就是为什么我们目前不关心线程数量增加的原因应用程序(全球大约有 20 个)。

我们不仅增加了线程优先级,而且尝试将 UDP 缓冲区大小增加到 1MB。这是初始化代码的片段

try
{
    Socket clientSock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)
                            {
#if !MONO
                                //Related to Mono bug 643475
                                ExclusiveAddressUse = true,
#endif
                            };

    if (ReceiveBufferSize >= 0) clientSock.ReceiveBufferSize = ReceiveBufferSize;
    clientSock.Bind(localEp);
    _client = new UdpClient {Client = clientSock};
}
catch (SocketException ex)
{
    throw new LogbusException("Cannot start UDP listener", ex);
}

ReceiveBufferSize 在运行时配置...

Apache 发送的每条日志消息都很短,我认为不超过 50 个字节。我们在实验室中运行千兆以太网。

在上一次使用这种配置的实验期间,日志服务器只收到了生成的 2900 多条中的 700 条以上。 Wireshark 在 UDP 套接字上报告了超过 2900 条消息,但 Logbus 的日志跟踪(将所有接收到的消息存储到文件中)仅报告这 700/800 条。执行 cat /proc/net/udp 并欺骗 lsof 以找到正确的行报告 大量丢弃 数据包。日志肯定是以非常高的速度发送的。如果我们在每次日志调用后将 Apache 内核修改为休眠一小段时间(不到一毫秒),我们会将损失降低到零,但性能也会降低到几乎为零。我们会做这样的测试,但我们必须在现实生活场景中证明 Logbus-ng 的有效性:(

我的直接问题是

  1. UdpClient.ReceiveBufferSize 是否有助于防止数据包丢失?在 C# 中我还能做什么?
  2. 显然应该也可以在 Mono 中工作,但是您知道该属性可能存在的错误吗?我的意思是,有人报告过错误吗? (单声道 2.8)
  3. 你知道先发送数据包到本地主机是否可以减少数据包丢失吗? (我会在 Web 服务器机器上运行一个特殊的日志服务器实例,然后通过不会丢失的 TLS 将日志转发到真正的日志服务器)
  4. 您建议我如何降低丢失率

我们目前必须使用 Apache 执行特殊测试,并且我们可以使用 UDP 来传递消息。我们不能选择 TLS,因为我们只有 C# APIs。

提前感谢您的帮助。我希望已经清楚了。如果有帮助,可以在SVN找到UDP接收器的源代码

【问题讨论】:

  • > 请澄清哪些机器(你可以称之为 A、B、C 等)正在运行:1. Apache 2. Wireshark 3. Logbus(我假设这是你的 C# 代码正在执行的地方。)
  • 机器 A 运行 Apache,机器 B 运行 Logbus 和 Wireshark :)

标签: c# mono udp


【解决方案1】:

ReceiveBufferSize 肯定会影响 UDP 套接字(即 UdpClient),如果数据包丢失是由于缓冲区溢出,那么增加 ReceiveBufferSize 会有所帮助。

请记住,如果数据速率如此之高,以至于您无法从缓冲区中快速读取足够长的时间,那么即使是最大的缓冲区也不可避免地会溢出。

我已经在 Ubuntu 上运行的 Mono 2.6.7 上有效地使用了 UdpClient.Client.ReceiveBufferSize,所以我相信 Mono 实现很好,当然我还没有在 Mono 2.8 上使用它。

根据我的经验,以极高的速率将 UDP 数据包发送到 localhost,可能会发生一些数据包丢失,尽管我从未在实际应用程序中经历过这种数据包丢失。因此,您可能会通过这种方法取得一些成功。

您还需要查看是否发生丢包,丢包可能是由于网络基础设施、数据包冲突、交换机可能由于交换机的某些限制而丢弃数据包。

简单地说,您需要准备好在使用 UDP 时处理和预期数据包丢失。

【讨论】:

  • 我毫不怀疑丢包是一个具体的风险。大多数日志框架都可以容忍这种丢失(然后依赖 UDP 而不是 TCP),我证明了长期可靠性分析不会受到数据包丢失的显着影响。但是您知道丢失超过 70% 的数据包是不好的。感谢您的回答,我将尝试朝这个方向努力,并让您知道下周何时我将重复我的实验,也许在 localhost 中有一个巨大的缓冲区。 :) 谢谢克里斯,你对我们有帮助
  • 不幸的是,将缓冲区设置为 10MB 不起作用,因为测试工具在 2306 上只收集了 67 条消息。如果我们在日志代码中放入 nanosleep,那么所有消息都会被收集:(
  • @djechelon,这是本地机器上的 UDP 还是网络上的 UDP?在网络上所有的赌注都没有了,网络设备开始发挥作用,数据包是否可能被路由器或交换机丢失?
  • 本地机器。我们还发现,在 localhost 上运行时丢失的消息比在远程主机上发送数据报时要多得多。我们甚至尝试使用 nice -5 运行服务器:|
  • @djechelon,这很奇怪。我在 windows boax 和 Linux 机器上本地运行了一些测试,并且在本地丢失的消息肯定比通过网络发送 UDP 时少得多。
【解决方案2】:

如果您使用的是 udp,则必须预料到数据包丢失。丢包的原因有很多。对你来说,很可能是因为通道上任何地方的数据包泛滥。可能是您的开关,也可能是您的接收器。溢出的原因是因为 UDP 没有任何类型的拥塞控制。 Tcp 确实具有拥塞控制(启动缓慢),这就是为什么使用 tcp,它永远不会(理论上在完美环境下)溢出。

防止 udp 传输过度泛滥的有效方法是手动采用 tcp 慢启动拥塞控制策略。

回答你的直截了当的问题 1.不可以。见tcp慢启动算法。 2.很可能它不是一个错误。 udp就是这样设计的。这是有原因的,也是需要的。 3. 不,代理没有帮助。 4. 最简单的解决由于溢出导致的丢包的方法是在发送更多数据包之前等待接收方的确认(注意接收方已成功接收到数据包)。当然,这对于防止其他原因造成的丢包没有帮助。

【讨论】:

  • 感谢您多年后再次提出这个问题。我确实理解使用 UDP 我确实希望丢包,但问题是关于针对大量数据包优化丢包率,我确实预计会有一些损失 - 而不是防止丢失。事实上,我们在实验室中证明,在日志软件中使用任何类型的拥塞控制都会在生成大量日志事件时显着影响正在运行的应用程序
【解决方案3】:

显然它也应该在 Mono 中工作,但是您知道该属性可能存在的错误吗?我的意思是,有人报告过错误吗? (单声道 2.8)

显然,设置套接字接收缓冲区大小在单声道中被严重破坏,直到版本 3.2.7:而不是指定的大小,它将被设置为随机值,因此尝试增加缓冲区大小实际上可能会使性能变差: -(

https://github.com/mono/mono/commit/bbd4ee4181787189fbb1f8ba6364afdd982ae706

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-04-02
    • 1970-01-01
    • 1970-01-01
    • 2012-08-02
    • 2015-03-08
    • 2014-11-06
    • 1970-01-01
    相关资源
    最近更新 更多