【问题标题】:SocketException when sending many messages (code 10048)发送多条消息时出现 SocketException(代码 10048)
【发布时间】:2013-02-25 15:50:03
【问题描述】:

我正在使用 cloudQueue.BeginAddMessage 和 EndAddMessage 发送许多消息。 我将尚未返回的开始数量限制为 500。但我遇到了异常,代码为 10048(表示套接字耗尽)。

Microsoft.WindowsAzure.Storage.StorageException: Unable to connect to the remote server ---> System.Net.WebException: Unable to connect to the remote server ---> System.Net.Sockets.SocketException: Only one usage of each socket address (protocol/network address/port) is normally permitted

我在搜索后发现的解决方案都建议修改注册表,但是由于这是在 Azure 中的辅助角色中计划的,所以我不能这样做。

我还有其他插入到表服务的功能,它们运行速度一样快但没有任何问题。似乎 EndAddMessage 函数几乎没有关闭连接或其他东西(我对套接字的了解有限)。

我的问题:这里有 azure 方面的错误吗?除了人为地减慢添加消息的速度外,我应该怎么做才能解决这个问题?

这是我用来发送消息的测试函数。在我的情况下,在添加了大约 16500 条消息并且回调正确且稳定地结束后,它会变慢并在一段时间后抛出上述异常。

很抱歉代码太长了,但这应该是复制粘贴,以便您重现问题。

AsyncCallback endAddCallback抛出异常。

    static void Main()
    {
        Console.SetBufferSize(205, Int16.MaxValue - 1);

        // Set the maximum number of concurrent connections (12*6 in my case)
        ServicePointManager.DefaultConnectionLimit = 12 * Environment.ProcessorCount;
        //setting UseNagleAlgorithm to true reduces network traffic by buffering small packets of data and transmitting them as a single packet, but setting to false can significantly reduce latencies for small packets.
        ServicePointManager.UseNagleAlgorithm = false;
        //if true, "Expect: 100-continue" header is sent to ensure a call can be made. This uses an entire roundtrip to the service point (azure), so setting to false sends the call directly.
        ServicePointManager.Expect100Continue = false;

        CloudStorageAccount storageAccount = CloudStorageAccount.Parse(__CONN_STRING);
        CloudQueueClient client = storageAccount.CreateCloudQueueClient();
        CloudQueue queue = client.GetQueueReference(__QUEUE_NAME);
        queue.CreateIfNotExists();
        List<Guid> ids = new List<Guid>();
        for (Int32 i = 0; i < 40000; i++)
            ids.Add(Guid.NewGuid());

        SendMessages(queue, ids.Select(id => new CloudQueueMessage(id.ToString())).ToList().AsReadOnly());
    }

    public static void SendMessages(CloudQueue queue, IReadOnlyCollection<CloudQueueMessage> messages)
    {
        List<CloudQueueMessage> toSend = messages.ToList();
        Object exceptionSync = new Object();
        Exception exception = null;
        CountdownEvent cde = new CountdownEvent(toSend.Count);
        AsyncCallback endAddCallback = asyncResult =>
        {
            Int32 endedItem = (Int32)asyncResult.AsyncState;
            try
            {
                queue.EndAddMessage(asyncResult);
                Console.WriteLine("SendMessages: Ended\t\t{0}\t/{1}", endedItem + 1, toSend.Count);
            }
            catch (Exception e)
            {
                Console.WriteLine("SendMessages: Error adding {0}/{1} to queue: \n{2}", endedItem + 1, toSend.Count, e);
                lock (exceptionSync)
                {
                    if (exception == null)
                        exception = e;
                }
            }
            finally { cde.Signal(); }
        };

        for (Int32 i = 0; i < toSend.Count; i++)
        {
            lock (exceptionSync)
            {
                if (exception != null)
                    throw exception;
            }
            //if number of added but not ended is larger than the MAX, yield and check again.
            while (true)
            {
                Int32 currentOngoing = (i- (cde.InitialCount - cde.CurrentCount));
                if (currentOngoing > 500)
                    Thread.Sleep(5);
                else
                    break;
            }
            Console.WriteLine("SendMessages: Beginning\t{0}\t/{1}", i + 1, toSend.Count);
            queue.BeginAddMessage(toSend[i], endAddCallback, i);
        }

        cde.Wait();
        if (exception != null)
            throw exception;
        Console.WriteLine("SendMessages: Done.");
    }

【问题讨论】:

  • Azure 中的辅助角色应该授予您通过 OnStart 事件或通过 RDP 修改某些(如果不是全部)密钥的权利。您不能修改的密钥是什么?
  • 我不知道我可以在工作角色中修改注册表?键是 HKLM\System\CurrentControlSet\Services\Tcpip\Parameters(值 MaxUserPort 和 TCPTimeWaitDelay)。正如这个答案和其他地方所建议的那样stackoverflow.com/questions/1339142/…
  • 是的,可以编辑注册表。有两种方式:A startup task, or the OnStart event
  • 我会试试这个并报告,谢谢。我不知道为什么我认为它无法完成。然而,奇怪的是我没有得到这个错误的表客户端,即使它也非常快。
  • 无论如何我应该指出,这只会移动或使问题不那么明显(并使用更多资源),并且似乎仍然存在套接字未关闭的错误。跨度>

标签: c# wcf sockets azure tcp


【解决方案1】:

Cloud[Blob|Table|Queue]Client 不维护状态,可以跨多个对象使用。

此问题与 ServicePointManager 过载有关。队列压力场景往往会加剧这种行为,因为它们执行许多小请求(在您的情况下是一个非常小的 guid)。您可以采取一些缓解措施来缓解此问题

  • Nagle 算法旨在通过在 tcp 层将小请求批处理在一起来帮助解决这种情况,将其设置为 true 在某些情况下可能会略微增加每条消息的延迟,但在压力下这很可能可以忽略不计,因为请求不会需要等待很长时间才能大于 nagle 正在寻找的窗口(1400 字节)
  • 增加 ServicePointManager.DefaultConnectionLimit 以考虑快速打开和关闭套接字。
  • 您能否提供有关您的代码运行位置的更多信息,以及在运行时是否有任何其他代码使用连接。默认情况下,客户端请求发送 keep alive = true,这应该保持与 Azure 服务的持久连接,并允许多个请求使用同一个套接字,而无需打开/关闭/重新连接。

另外,关于您对表实体未显示相同行为的评论,表服务支持的当前有线协议是 Atom/Pub,它可能非常健谈(xml 等)。因此,一个简单的实体插入比一个简单的队列 guid 消息大得多。本质上,由于大小差异,表流量利用其下方的 TCP 层做得更好,因此这不是真正的苹果对苹果的比较。

如果这些解决方案对您不起作用,获取更多有关您帐户的信息会很有帮助,以便我们在后端进行查看。

【讨论】:

  • 谢谢乔 - 我现在尝试了几件事: 启用 nagle 算法没有明显效果,仍然得到异常。将连接限制增加 5 倍:仍然异常(可能比平时晚几秒钟)。启用了 nagle 和增加连接限制:仍然是例外。我还尝试将 TcpTimedWaitDelay 注册表项减少到 30 秒。这有一些效果,但是当同时增加发送应用程序/发送到不同队列的实例时,异常仍然很快出现。您需要有关帐户的哪些信息?
  • 哦,代码正在我的办公室计算机上运行。我试过几台机器,也在家里。所有机器都在 netstat -n 输出中显示数千个 TIME_WAIT。但插入表存储时只有少数甚至没有。
【解决方案2】:

我怀疑这是因为 CloudQueueClient 不适合您正在执行的多线程(异步)访问。

尝试像这样在SendMessages 中重新创建 CloudQueue

    CloudQueueClient client = storageAccount.CreateCloudQueueClient();
    CloudQueue queue = client.GetQueueReference(__QUEUE_NAME);

我在许多论坛中读到 CloudXXClient 只需要使用一次就可以处理掉。该原则可能适用于此。

没有太多效率可以提高,因为客户端的ctor没有向队列发送请求并且存在线程问题。

【讨论】:

  • 感谢您的回复。对于 for 循环中的每次迭代,我尝试按照您所说的做,并且还将为迭代创建的 queue 作为状态对象发送给回调,以便我也将它用于 EndAddMessage 函数。不幸的是它没有用,我得到了同样的例外。
  • 您可以尝试移动正在创建 CloudQueueMessage 的位置吗?也许发生了一些奇怪的 C# 闭包。尝试在函数中创建该对象。
  • 试过了——即使我在循环中声明并实例化每条消息,我也会得到同样的异常。客户端/队列相同。我也没有来自 ReSharper 的关闭警告。
  • 是否有任何物品是一次性的并且可能需要清理?
【解决方案3】:

此问题现已在 Storage Client Library 2.0.5.1 中得到解决。

另外,还有一种解决方法:卸载 KB2750149。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-12
    相关资源
    最近更新 更多