代码之家  ›  专栏  ›  技术社区  ›  Jason H

Azure函数应用程序随机失败:超过主机阈值[连接]

  •  1
  • Jason H  · 技术社区  · 6 年前

    我有以下功能应用程序

    [FunctionName("SendEmail")]
    public static async Task Run([ServiceBusTrigger("%EmailSendMessageQueueName%", AccessRights.Listen, Connection = AzureFunctions.Connection)] EmailMessageDetails messageToSend,
        [ServiceBus("%EmailUpdateQueueName%", AccessRights.Send, Connection = AzureFunctions.Connection)]IAsyncCollector<EmailMessageUpdate> messageResponse,
        //TraceWriter log,
        ILogger log,
        CancellationToken token)
    {
        log.LogInformation($"C# ServiceBus queue trigger function processed message: {messageToSend}");
    
        /* Validate input and initialise Mandrill */
        try
        {
            if (!ValidateMessage(messageToSend, log))   // TODO: finish validation
            {
                log.LogError("Invalid or Unknown Message Content");
                throw new Exception("Invalid message content.");
            }
        }
        catch (Exception ex)
        {
            log.LogError($"Failed to Validate Message data: {ex.Message} => {ex.ReportAllProperties()}");
            throw;
        }
    
        DateTime utcTimeToSend;
        try
        {
            var envTag = GetEnvVariable("Environment");
            messageToSend.Tags.Add(envTag);
    
            utcTimeToSend = messageToSend.UtcTimeToSend.GetNextUtcSendDateTime();
            DateTime utcExpiryDate = messageToSend.UtcTimeToSend.GetUtcExpiryDate();
            DateTime now = DateTime.UtcNow;
            if (now > utcExpiryDate)
            {
                log.LogError($"Stopping sending message because it is expired: {utcExpiryDate}");
                throw new Exception($"Stopping sending message because it is expired: {utcExpiryDate}");
            }
            if (utcTimeToSend > now)
            {
                log.LogError($"Stopping sending message because it is not allowed to be send due to time constraints: next send time: {utcTimeToSend}");
                throw new Exception($"Stopping sending message because it is not allowed to be send due to time constraints: next send time: {utcTimeToSend}");
            }
        }
        catch (Exception ex)
        {
            log.LogError($"Failed to Parse and/or Validate Message Time To Send: {ex.Message} => {ex.ReportAllProperties()}");
            throw;
        }
    
        /* Submit message to Mandrill */
        string errorMessage = null;
        IList<MandrillSendMessageResponse> mandrillResult = null;
        DateTime timeSubmitted = default(DateTime);
        DateTime timeUpdateRecieved = default(DateTime);
    
        try
        {
            var mandrillApi = new MandrillApi(GetEnvVariable("Mandrill:APIKey"));
    
            var mandrillMessage = new MandrillMessage
            {
                FromEmail = messageToSend.From,
                FromName = messageToSend.FromName,
                Subject = messageToSend.Subject,
                TrackClicks = messageToSend.Track,
                Tags = messageToSend.Tags,
                TrackOpens = messageToSend.Track,
            };
    
            mandrillMessage.AddTo(messageToSend.To, messageToSend.ToName);
            foreach (var passthrough in messageToSend.PassThroughVariables)
            {
                mandrillMessage.AddGlobalMergeVars(passthrough.Key, passthrough.Value);
            }
    
            timeSubmitted = DateTime.UtcNow;
            if (String.IsNullOrEmpty(messageToSend.TemplateId))
            {
                log.LogInformation($"No Message Template");
                mandrillMessage.Text = messageToSend.MessageBody;
                mandrillResult = await mandrillApi.Messages.SendAsync(mandrillMessage, async: true, sendAtUtc: utcTimeToSend);
            }
            else
            {
                log.LogInformation($"Using Message Template: {messageToSend.TemplateId}");
                var clock = new Stopwatch();
                clock.Start();
                mandrillResult = await mandrillApi.Messages.SendTemplateAsync(
                    mandrillMessage,
                    messageToSend.TemplateId,
                    async: true,
                    sendAtUtc: utcTimeToSend
                );
                clock.Stop();
                log.LogInformation($"Call to mandrill took {clock.Elapsed}");
            }
            timeUpdateRecieved = DateTime.UtcNow;
        }
        catch (Exception ex)
        {
            log.LogError($"Failed to call Mandrill: {ex.Message} => {ex.ReportAllProperties()}");
            errorMessage = ex.Message;
        }
    
        try
        {
            MandrillSendMessageResponse theResult = null;
            SendMessageStatus status = SendMessageStatus.FailedToSendToProvider;
    
            if (mandrillResult == null || mandrillResult.Count < 1)
            {
                if (String.IsNullOrEmpty(errorMessage))
                {
                    errorMessage = "Invalid Mandrill result.";
                }
            }
            else
            {
                theResult = mandrillResult[0];
                status = FacMandrillUtils.ConvertToSendMessageStatus(theResult.Status);
            }
    
            var response = new EmailMessageUpdate
            {
                SentEmailInfoId = messageToSend.SentEmailInfoId,
                ExternalProviderId = theResult?.Id ?? String.Empty,
                Track = messageToSend.Track,
                FacDateSentToProvider = timeSubmitted,
                FacDateUpdateRecieved = timeUpdateRecieved,
                FacErrorMessage = errorMessage,
                Status = status,
                StatusDetail = theResult?.RejectReason ?? "Error"
            };
    
            await messageResponse.AddAsync(response, token).ConfigureAwait(false);
        }
        catch (Exception ex)
        {
            log.LogError($"Failed to push message to the update ({AzureFunctions.EmailUpdateQueueName}) queue: {ex.Message} => {ex.ReportAllProperties()}");
            throw;
        }
    }
    

    当我将100条消息排队时,一切都正常。当我将500多条消息排队时,其中499条消息已发送,但最后一条消息从未发送。我还开始出现以下错误。

    操作已取消。

    我已经安装和配置了Application Insights,并且正在运行日志记录。我无法在本地复制,根据Application Insights提供的以下端到端交易详细信息,我认为问题发生在此时:

    await messageResponse.AddAsync(response, token).ConfigureAwait(false);

    Application Insights端到端事务

    End-to-end transaction

    主办json

    {
      "logger": {
        "categoryFilter": {
          "defaultLevel": "Information",
          "categoryLevels": {
            "Host": "Warning",
            "Function": "Information",
            "Host.Aggregator": "Information"
          }
        }
      },
      "applicationInsights": {
        "sampling": {
          "isEnabled": true,
          "maxTelemetryItemsPerSecond": 5
        }
      },
      "serviceBus": {
        "maxConcurrentCalls": 32
      }
    }
    

    与此相关的可能还有来自Application Insights的错误。

    [ Related Error

    还有其他人有过这样或类似的问题吗?

    1 回复  |  直到 6 年前
        1
  •  6
  •   Mikhail Shilkov    6 年前

    如果您遵循异常中的链接 https://aka.ms/functions-thresholds ,您将看到以下限制:

    连接数:出站连接数(限制为300)。有关处理连接限制的信息,请参阅管理连接。

    你很可能打到了那个。

    在每个函数调用中,创建 MandrillApi 。您没有提到正在使用哪个库,但我怀疑它正在为的每个实例创建新连接 曼陀罗

    我检查过了 Mandrill Dot Net 是的, it's creating 新的 HttpClient 对于每个实例:

    _httpClient = new HttpClient
    {
        BaseAddress = new Uri(BaseUrl)
    };
    

    Managing Connections 建议:

    在许多情况下,可以通过重用客户端实例而不是在每个函数中创建新实例来避免这种连接限制。NET客户端(如HttpClient、DocumentClient和Azure存储客户端)可以在使用单个静态客户端的情况下管理连接。如果这些客户机在每次函数调用时都被重新实例化,那么代码很可能正在泄漏连接。

    如果API客户端是线程安全的,请检查该库的文档,如果是,请在函数调用之间重用它。