考虑到 Azure .NET Web 应用中的 SNAT 端口限制,对于数千个调用,最佳并行化策略是什么?



我设计了一个 .NET API,每隔几分钟就需要处理数千个(最终(数十万个请求。

它是一个自定义推送 API,反过来使用 Azure 通知中心(和相应的 NuGet 包(将请求发送到 Azure 通知中心。

我遇到的问题是,由于 Azure SNAT 端口耗尽,我遇到了内部异常。该应用程序在 1 个 S3 实例上运行,理论上该实例的限制为 8064 个 tcp 连接(根据此博客文章(。端口限制适用于所有连接(http、tcp 等(。我在 Azure 应用程序上遇到了一些异常,指出它无法连接到主机 notificationhub.windows.net,我认为这是 Azure 通知中心基础结构的一部分。

无法连接到远程服务器。跟踪ID:xxxxxxx,时间戳:2019-11-25T11:39:02.9349637Z 无法连接到远程服务器 连接尝试失败,因为连接方在一段时间后未正确响应,或者建立的连接失败,因为连接的主机无法响应 23.100.65.137:443

上述 IP 解析为 notificationhub.windows.net。

在推送 API 中,有一个异步终结点,它接受通知对象列表,以允许使用者对此 API 发出单个调用,然后在内部有一个 Task.WhenAll,用于满足每个单独的请求/对象。

需要考虑的一件事是,由于应用设计的限制,消费者在单个批量调用中发送所有对象(目前在 5k 到 8k 之间(。我知道理想的做法是将它们分成 1k 个包中,然后我们可以利用横向扩展,但目前无法做到这一点。

public async Task<IList<NotificationResponse>> SendNotifications(IList<NotificationRequest> pushRequests, string appId)
{
var responses = new List<Task<NotificationResponse>>();
var app = await GetAppFromCache(appId).ConfigureAwait(false);
foreach (var req in pushRequests)
{
responses.Add(SendPushNotification(req, app));
}
return await Task.WhenAll(responses.ToArray());
}

public async Task<NotificationResponse> SendPushNotification(PushNotificationRequest pushReq, Application application)
{
NotificationOutcome outcome = null;
var result = new NotificationResponse();
var _hub = await GetNotificationHub(application.HubName).ConfigureAwait(false);
var tag = //create Tag logic here
var notification = GetBasicNotification(pushReq.Message, pushReq.Title);
outcome = await _hub.SendNotificationAsync(notification, tag).ConfigureAwait(false);
if (outcome != null)
{
result.NotificationId = outcome.NotificationId;
result.Status = Constants.Success;
}
else
{
//error handling omitted for brevity
}
return result;
}

它的内容并不多,内部检索的大部分数据来自缓存(应用程序和集线器连接(,但始终需要与中心连接以发送每条消息。我还没有研究Microsoft.Azure.NotificationHubs nuget包的内部结构,但我认为他们的连接代码是有效的,并且他们可能在内部使用(和重用(HttpClient。

问题是,从理论上讲,每个连接保持打开状态 100 秒。如果我想处理 200,000 条通知,那么限制为 8,064,似乎我会在处理过程中相对较快地遇到端口耗尽。

难道 Task.WhenAll 并行化策略不是最优的吗?我应该查看其他设置以避免失败吗?我不确定切换到 Parallel.ForEach 是否会解决此问题,因为即使我正在处理 500 或 1000 的块,调用也会成功,但连接保持打开状态 100 秒,在此期间我已经在处理最终会遇到端口饥饿的其他块。

由于这是一个批量终结点,并且问题出在传出 tcp 连接上,因此我无法通过扩展到更多实例来解决此问题,因为整个调用在一段代码上运行。目前也无法将其移动到基于事件的(函数(解决方案。

这里最好的解决方案是什么?

主要是,我想了解一般系统设计的目的,如果基于此端口限制,并考虑到处理任何请求可能会导致 1 到最多 5 个传出连接(到 Cosmos、通知中心、表存储等(如果具有"批量"终结点甚至像 Web 作业一样处理(检索所有挂起的请求并尝试从一个实例一次完成(不是设计或支持的最佳方式Azure 中的消息处理。

任何反馈或意见将不胜感激,谢谢!

我看到,在您的SendPushNotification方法中,您可以通过var _hub = await GetNotificationHub(application.HubName).ConfigureAwait(false);获得NotificationHubClient实例

我可以知道你的GetNotificationHub方法会发生什么吗?每次调用该方法时都会创建新NotificationHubClient吗?

根据我的理解,你只需要一个NotificationHubClient实例。它可用于推送通知。有关详细信息,您可以参考推送客户端示例。

最新更新