У меня есть приложение AzureFunctions с 2 HTTP-функциями.Оба направлены из одного класса, но используют разные URL для получения данных.Каждый день конвейер фабрики данных Azure запускает 1-ую функцию HTTP, а другой конвейер вызывает 2-ю функцию в течение 1 минуты
Каждая функция отправляет около 1300 HTTP-запросов на сторонний веб-сайт и сохраняет каждый ответ в виде отдельного файла JSON в хранилище BLOB-объектов.
Проблема возникает почти каждый раз (но не всегда), 2-я функция выдает исключение System.Net.Sockets.SocketException, потому что несколько исходящих запросов работают с общим тайм-аутом TCP 21 с.Странная вещь, которую я заметил - вероятно, Azure по какой-то причине душит мои исходящие запросы: первый пакет занимает около 300 мс, следующий цикл занимает 4,3 с, затем 9,5 с, а следующий пакет достигает 21 с, за исключением
Вот изображение увеличения времени исходящих запросов
Трассировка стека исключений:
System.Net.Http.HttpRequestException: попытка подключения не удалась, потому что подключенная сторона не правильноответить через определенный промежуток времени или не удалось установить установленное соединение, поскольку подключенный узел не смог ответить ---> System.Net.Sockets.SocketException: попытка подключения не удалась, поскольку подключенная сторона не ответила должным образом через определенный промежуток времени или не установилане удалось подключиться, поскольку подключенный узел не смог ответить в System.Net.Http.ConnectHelper.ConnectAsync (строковый узел, порт Int32, CancellationToken cancellationToken) --- конец трассировки стека внутренних исключений --- в System.Net.Http.ConnectHelper.ConnectAsync (Строковый хост, яПорт nt32, CancellationToken CancellationToken) в System.Threading.Tasks.ValueTask 1.get_Result() at
System.Net.Http.HttpConnectionPool.CreateConnectionAsync(HttpRequestMessage
request, CancellationToken cancellationToken) at
System.Threading.Tasks.ValueTask
1.get_Result () в System.Net.Http.HttpConnectionPool.WaitForCreatedConnectionAsync (ValueTask 1
creationTask) at System.Threading.Tasks.ValueTask
1.get_Result в (101) .Net (*) *).Http.HttpConnectionPool.SendWithRetryAsync (запрос HttpRequestMessage, Boolean doRequestAuth, CancellationToken cancellationToken)
в System.Net.Http.RedirectHandler.SendAsync.http:cancellationToken) в System.Net.Http.HttpClient.FinishSendAsyncBuffered (Task`1 sendTask, запрос HttpRequestMessage, CancellationTokenSource cts, Boolean disposeCts) в FunctionApp.BaseFunc. <> c__DisplayClass7_2.d.MoveNext () в E: \ vsts-agent-win-1_work \ 339 \ s \ Services \ Host \ Controllers \ BaseFunc.cs: строка 102 --- Конец трассировки стека из предыдущего расположения, в котором было сгенерировано исключение ---в FunctionApp.BaseFunc.ProcessRun (журнал ILogger, String runId) в E: \ vsts-agent-win-1_work \ 339 \ s \ Services \ Host \ Controllers \ BaseFunc.cs: строка 122.
FunctionApp размещается в плане AppService S1, поэтому нет ограничений на исходящие соединения в 600 (я так полагаю)
Метрики соединений TCP во время исключения (макс было 498): Метрики приложения AzureFunction
TCP-соединения от помощника «Решить проблему» приложения AzureFunction Максимальное количество TCP-соединений во всех состояниях было 502
План обслуживания CPU и Memory of App во время исключения: Метрики плана обслуживания приложения
Приложение - .Net Core 2.2
Мне не удалось воспроизвести это на моем локальном ПК.Но в Azure это происходит почти каждый день в каждой среде (dev, test, prod).После такого сбоя фабрика данных Azure выполняет повторную попытку через 5 минут, и она выполняется каждый раз успешно.
Вот код базового класса, который используется обеими функциями:
public abstract class BaseFunc
{
protected abstract string BlobFolderName { get; }
protected TelemetryClient telemetryClient;
private static HttpClient _httpClient;
static BaseFunc()
{
HttpClientHandler handler = new HttpClientHandler();
handler.MaxConnectionsPerServer = 300;
_httpClient = new HttpClient(handler);
}
protected async Task ProcessRun(ILogger log, string runId)
{
int processedItems = 0;
try
{
Stopwatch sw = Stopwatch.StartNew();
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
await Authentication("url", log, runId); //sets default Authorization header
string getIdeaResult = await _httpClient.GetStringAsync("url");
JObject jsonObject = JObject.Parse(getIdeaResult);
int ideaCount = (int)jsonObject.SelectToken("total_count");
List<Task> tasks = new List<Task>();
string DataPulledDate = DateTime.Now.ToString("dd-MMM-yyyy");
CloudStorageAccount storageAccount = CloudStorageAccount.Parse("connection string");
CloudBlobClient cloudBlobClient = storageAccount.CreateCloudBlobClient();
CloudBlobContainer cloudBlobContainer = cloudBlobClient.GetContainerReference("container");
string getIdsUri = "url" + $"&limit={batchSize}&offset=";
int iterations = (int)Math.Ceiling((decimal)ideaCount/batchSize);
for (int i = 0; i < iterations; i++)
{
string result = await _httpClient.GetStringAsync("url" + i * 50);
JObject jsonIdsObject = JObject.Parse(result);
int[] ideaIds = jsonIdsObject["content"].Children().Values<int>("id").ToArray();
foreach (int id in ideaIds)
{
tasks.Add(Task.Run(async () =>
{
string content = null;
using (var response = await _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, "url"+ id))) //Exception is thrown on this line
{
content = await response.Content.ReadAsStringAsync();
response.EnsureSuccessStatusCode();
}
CloudBlockBlob cloudBlockBlob = cloudBlobContainer.GetBlockBlobReference($"{DataPulledDate}/{BlobFolderName}/ideaId-{id}.json");
await cloudBlobContainer.CreateIfNotExistsAsync();
await cloudBlockBlob.UploadTextAsync(content);
Interlocked.Increment(ref processedItems);
}));
}
}
await Task.WhenAll(tasks);
sw.Stop();
}
catch (Exception ex)
{
log.LogError(ex, "{RunId}: Run failed. {Items} items processed successfully, Exception: {Exception}.", runId, processedItems, ex.ToString());
throw;
}
finally
{
if (telemetryClient != null)
{
telemetryClient.Flush();
Thread.Sleep(3000);
}
}
}
}
сам код функции:
namespace FunctionApp
{
public class GetIdeas : BaseFunc
{
public GetIdeas(TelemetryClient telemetryClient)
{
this.telemetryClient = telemetryClient;
}
protected override string BlobFolderName { get => "folder"; }
protected override string GetItemUrl { get => "url"; }
[FunctionName("GetIdeasFn")]
public async Task Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)] HttpRequest req, ILogger log)
{
await ProcessRun(log, $"GetIdeasFn - {DateTime.UtcNow.Ticks}");
}
}
}
Ценю любую помощь.