Эффективный способ сделать запросы API, не вызывая ошибку 429 (слишком много запросов)? - PullRequest
0 голосов
/ 09 января 2020

У меня есть метод API POST, который редактирует значения в одной строке таблицы в базе данных. API может принимать только 200 запросов в минуту, иначе он выдаст 429. У меня есть 35 000 строк, которые нужно записать в таблицу из другой таблицы (итого требуется 35 000 запросов). Вместо того, чтобы просто давать задержку перед выполнением каждого запроса ( что я сделал сейчас), каков был бы наиболее эффективный способ (наименьшее количество времени) сделать это?

 private static async void callMetadataAPI()
    {
        Stopwatch watch = new Stopwatch();
        watch.Start();
        string strConnection = GetConnectionString("");
        string queryString = "select * from test.dbo.test_table";
        using (SqlConnection connection = new SqlConnection(strConnection))
        {
            SqlCommand command = new SqlCommand(queryString, connection);
            connection.Open();
            SqlDataReader reader = command.ExecuteReader();

            try
            {
                while (reader.Read())
                {
                    HttpResponseMessage success = await runMetadataAPI(reader);
                    success.EnsureSuccessStatusCode();
                }

            }
            catch (Exception e)
            {
                ExceptionHandler(e);
            }
            finally
            {
                reader.Close();
                connection.Close();
            }
        }
        await Task.WhenAll();
        watch.Stop();

    }
    private static async Task<HttpResponseMessage> runMetadataAPI(SqlDataReader reader)
    {
        System.Threading.Thread.Sleep(1000);
        HttpClient client = new HttpClient();
        client.BaseAddress = new Uri("https://test.com");
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("JWT", "token");
        string requestUri = "/api/metadata";
        var metaDataPostData = new MetaDataPostData();
        metaDataPostData.CId= reader["CId"].ToString();
        metaDataPostData.SId = "somevalue"; 
        Metadata[] metaData = new Metadata[1];
        metaData[0] = new Metadata();
        metaData[0].Key = "columnname";
        metaData[0].Value = "columnvalue";
        metaDataPostData.Metadata = metaData;
        var json = JsonConvert.SerializeObject(metaDataPostData);
        var data = new StringContent(json, Encoding.UTF8, "application/json");
        HttpResponseMessage response = await client.PostAsync(requestUri, data);
        return response;
    }
class MetaDataPostData
{
    public Metadata[] Metadata { get; set; }
    public string SId{ get; set; }

    public string CId { get; set; }

}
class Metadata
{
    public string Key { get; set; }
    public string Value { get; set; }

}

Учитывая, что API не может быть изменен, любое предложение очень ценится. В настоящее время этот код займет около 19 минут для выполнения 1000 запросов! и я не уверен, что это правильный подход для всего этого.

Я запрещаю 429 в этом коде, просто давая задержку System.Threading.Thread.Sleep(1000) в функции runMetadataAPI (функция, которая потребляет API).

1 Ответ

0 голосов
/ 10 января 2020

Если вы знаете, что количество запросов в минуту равно 200, тогда я выполняю ваши запросы пакетами и жду 1 минуту между пакетами. В настоящее время ваш HttpClient воссоздает каждый запрос. Вы хотели бы создать это в своем конструкторе класса и затем использовать повторно. Примерно так может работать:

public class MyService() {
    private readonly HttpClient _client;

    public MyService() {
        _client = new HttpClient();
        _client.BaseAddress = new Uri("https://test.com");
        _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("JWT", "token");
    }

    private static async Task callMetadataAPI() 
    {
        Stopwatch watch = new Stopwatch();
        watch.Start();
        var tasks = new List<Task<HttpResponseMessage>>();
        string strConnection = GetConnectionString("");
        string queryString = "select * from test.dbo.test_table";
        using (SqlConnection connection = new SqlConnection(strConnection))
        {
            SqlCommand command = new SqlCommand(queryString, connection);
            connection.Open();
            SqlDataReader reader = command.ExecuteReader();

            try
            {            
                var batchSize = 200;
                var currentIdx = 0;
                while (reader.Read())
                {
                    var batchFinished = currentIdx % batchSize == 0;
                    var metaDataTask = runMetadataAPI(reader, batchFinished);
                    tasks.Add(metaDataTask);
                    currentIdx++;
                }

            }
            catch (Exception e)
            {
                ExceptionHandler(e);
            }
            finally
            {
                reader.Close();
                connection.Close();
            }
        }
        await Task.WhenAll(tasks);
        watch.Stop();
    }

    private static async Task<HttpResponseMessage> runMetadataApi(SqlDataReader reader, bool batchFinished) {
        if(batchFinished) {
            await Task.Delay(60000);
        }
        string requestUri = "/api/metadata";
        var metaDataPostData = new MetaDataPostData();
        metaDataPostData.CId= reader["CId"].ToString();
        metaDataPostData.SId = "somevalue"; 
        Metadata[] metaData = new Metadata[1];
        metaData[0] = new Metadata();
        metaData[0].Key = "columnname";
        metaData[0].Value = "columnvalue";
        metaDataPostData.Metadata = metaData;
        var json = JsonConvert.SerializeObject(metaDataPostData);
        var data = new StringContent(json, Encoding.UTF8, "application/json");
        return client.PostAsync(requestUri, data);
    }
}
...