1

我有一个从 JavaScript 接收 XmlHttpRequests 的 WebApi2 控制器。

我每秒对 api 进行 +500 次调用,任何请求都会执行一些快速计算,然后我创建一个 Azure 存储队列(不是服务总线),传入一个序列化对象以供以后处理。直到这里一切正常,问题是 10-15% 的时间,仅初始化存储队列并添加 20k JSON 消息需要 500 毫秒到 2 秒之间的时间。我将请求分片到 10 个不同的队列中,但问题仍然存在,并且似乎与流量无关,基本上有时队列只是卡在创建过程中并变慢。

我已经禁用了 Nagle 和 Expect100Continue。

我想将这种架构转换为使用 EventHUbs,因为可能我的情况需要事件摄取器而不是简单的队列,需要最大速度。

但是EventHub的初始化有同样的问题!有时启动和接收一条消息需要 2 或 3 秒,平均为 400 毫秒。

我用秒表测量了速度。

这是我在 API 控制器中的代码:

  var eventHubClient = StorageHelpers.InitializeEventHub("name", "Send");
                           await eventHubClient.SendAsync(new EventData(Encoding.UTF8.GetBytes(QueueSerialized)));

InizializeEventHub 在哪里:

        public static EventHubClient InitializeEventHub(string eventHubName, string type)
    {
        string connectionString = RoleEnvironment.GetConfigurationSettingValue("Hub"+type+eventHubName);
        return EventHubClient.CreateFromConnectionString(connectionString, eventHubName);}

该服务使用云服务托管在 Azure 上,托管在 ServiceBus 和存储的同一位置 (WestUS)。

我的问题是:

  • 1)初始化连接的这段时间是否正常?
  • 2)Web Api 有没有办法为所有调用共享同一个 EventHubClient 实例?Redis 在 Lazy 类中使​​用 ConnectionMultiplexer 完成了类似的操作。
  • 3) 我可以缓存 EventHubClient 对象吗?

对此问题的任何帮助将不胜感激,如果有某种方法可以加快初始化和 AddMessageAsync 操作,我什至可以返回存储队列。

谢谢

4

3 回答 3

1

伟大的Qstn!这是我的看法:

  1. 在 Azure 最繁忙的 scaleunit 之一(如美国西部) - 400 毫秒的顺序。确实听起来可能是 eventhubs 发送延迟的数字。您正在寻找的平均延迟是多少?第一次通话需要 2-3 秒,用于创建连接,尤其是 SSL 协商。这些在该地区的各种 azure 服务之间没有显着差异。只有最初的几个电话需要这个时间。所有后续调用都应该是毫秒级的。EventHubClient.Send API(有 3 种类型的发送 - 您正在使用这 1),它是为 HighAvailability 设计的,首先将消息发送到具有高可用性的 ServiceBus 网关,然后转发到可用的 EventHub 分区之一 - 使其对发送操作具有高可用性。这确实增加了网关在第一次发送时发现分区的少量初始化成本。可以说,如果您的分区数是 4,那么您对 ​​EventHub 的前 4 个 Send 调用可能需要更高的延迟 - 并且从它们开始 - 它是高性能的。
  2. 只要您正在与之交谈的 EventHub - 相同 - 您就可以在 WebAPI 中共享 EventHubClient。每个 EventHubClient 都与一个 Connection 相关联。但是,在 EventHub .net SDK 中,只要 2 个 EventHubClient 的连接字符串相同 - 连接就会被重用。这里有一个优化 - 如果您的流量较少并且通过拥有更多事件集线器而具有扇出架构:即,如果您的场景有多个事件集线器并且您的所有事件集线器都在一个命名空间中并且想要使用 1 个 EventHubClient 对象(意味着每个 webapi 进程只有 1 个套接字)发送到 EventHubs 服务,您可以使用 MessagingFactory(具有命名空间级别 SasKey)来创建 EventHubClient。

var msgFactory = MessagingFactory.CreateFromConnectionString(@"Endpoint=amqps://---namespaceName----.servicebus.windows.net;SharedAccessKeyName=---SasKeyName----;SharedAccessKey=----SasKey----"); var ehClient = msgFactory.CreateEventHubClient("----eventHubName----");

  1. 您可以考虑缓存 EventHubClient 对象。它可以节省几行客户端代码执行来从缓存中获取一个 MessagingFactory(它持有对 Connection 的引用)。

!斯里

于 2016-02-02T04:45:55.227 回答
0
  1. 不确定,我从不费心去计时,因为如果你重复使用它,它就没有以前那么重要了。考虑到网络连接被重用,它似乎太长了
  2. 是的。
  3. 这取决于您所说的缓存是什么意思。如果你的意思是序列化并保存在内存中的某个地方,那么没有。如果您的意思是放入 ConcurrentBag(像游泳池一样使用它),那么肯定会。

如果您每台服务器每秒每 20KB 发出 >500 个请求,那么您应该确认您已设置足够的吞吐量单位,因为这是 >10MB/秒的流入,至少需要 10 个吞吐量单位。节流可以解释延迟问题。要检查的另一件事是初始化的哪些组件需要时间,例如我从未对GetConfigurationSettingValue进行基准测试,它可能没有被缓存。

但假设这些都不是问题,那么问题是你需要做什么才能让它快速?您当然可以重用 EventHubClient 或您自己创建的对象来处理创建时间。不太连接到 WebAPI 的简单方法是简单地拥有一个包含实例的静态变量(可能在Lazy内部使用构造函数初始化)。重用它时,您应该知道 EventHubClient不是正式的线程安全的(尽管 Send似乎在现实中),这意味着您需要管理它。但是单个 EventHubClient 或多个共享同一网络连接的 EventHubClient 可能无法为您提供每台服务器 10MB/s 的速度。在这种情况下,我将您的注意力引向文档的这一部分

最后,还可以从 MessagingFactory 实例创建 EventHubClient 对象,如下例所示。

var factory = MessagingFactory.CreateFromConnectionString("your_connection_string"); var client = factory.CreateEventHubClient("MyEventHub");

需要注意的是,从消息传递工厂实例创建的其他 EventHubClient 对象将重用相同的底层 TCP 连接。因此,这些对象对吞吐量有客户端限制。Create 方法重用单个消息传递工厂。如果您需要来自单个发送者的非常高的吞吐量,那么您可以从每个消息工厂创建多个消息工厂和一个 EventHubClient 对象。

如果你这样做,那么我强烈建议将它们汇集/编写你自己的多路复用器。

于 2016-02-02T04:42:21.380 回答
0

我最终得到了一个疯狂的简单解决方案。EventHubs 和 StorageQueues 都需要时间来初始化,特别是 EventHubs 在向流中添加消息时通常很慢。现在 300 毫秒在 99.99% 的情况下并不慢,但在我的情况下确实如此。

StorageQueue 超级便宜、快速、简单,但添加消息却慢得要命。经过数小时的基准测试和 Redis Pub/Sub 等其他解决方案检查后,我最终使用了 StorageQueues,根本没有等待 Async 调用。

所以标准调用是

await queue.AddMessageAsync(message);

等待部分是问题,如果任务没有返回,WebApi 无法返回。应该是“一劳永逸”,但事实并非如此。

我解决了不等待电话的问题,使用变量隐藏警告

var nowait = queue.AddMessageAsync(message);

队列中的插入在任何情况下都是-立即的,并且不会丢失任何消息。

于 2016-02-06T21:36:41.797 回答