Написал свои ретраи...
 
Уведомления
Очистить всё

Написал свои ретраи для внешнего API. Посмотрите, что тут не так

6 Сообщения
3 Пользователи
4 Реакции
471 Просмотры
vanyaandreev
Сообщения: 3
Автор темы
Пишет в irb разработчик
Присоединился: 1 год назад
[#98]

Sinatra, Ruby 3.4. Сервис раз в пять минут по расписанию в Sidekiq забирает курсы у внешнего поставщика, а тот иногда отвечает 502 или просто висит. Ретраи написал сам:

require "net/http"
require "json"

def fetch_rates(uri)
  attempts = 0
  begin
    attempts += 1
    response = Net::HTTP.get_response(uri)
    raise "HTTP #{response.code}" unless response.is_a?(Net::HTTPSuccess)

    JSON.parse(response.body)
  rescue => e
    sleep 1
    retry if attempts < 5
    raise e
  end
end

Работает, но чувствую, что неправильно: ловлю вообще всё, жду всегда одну секунду, таймаутов нет. Как это делают нормально?


5 ответов
GalinaKorneva
Сообщения: 30
Читает чужие PR бэкенд-разработчица
Присоединился: 4 года назад

Чувствуешь правильно. Ловить надо только то, что имеет смысл повторять: таймауты, обрывы соединения и ответы 5xx. На 400 или 401 повтор ничего не даст, только забьёт лог. Ждать нужно с растущей паузой и случайной добавкой, иначе после сбоя у поставщика все клиенты придут повторять одновременно. И обязательно таймауты, по умолчанию read_timeout у Net::HTTP 60 секунд.

RETRYABLE = [Net::OpenTimeout, Net::ReadTimeout, Errno::ECONNRESET, Errno::ECONNREFUSED].freeze
class ServerError < StandardError; end

def fetch_rates(uri, attempts: 5)
  tries = 0
  begin
    tries += 1
    response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https",
                               open_timeout: 3, read_timeout: 5) { |http| http.get(uri.request_uri) }
    raise ServerError, response.code if response.is_a?(Net::HTTPServerError)

    JSON.parse(response.body)
  rescue *RETRYABLE, ServerError
    raise if tries >= attempts

    sleep(0.5 * 2**tries + rand * 0.5)
    retry
  end
end

Ответ
2 ответа
vanyaandreev
Присоединился: 1 год назад

Пишет в irb разработчик
Сообщения: 3

А если поставщик лежит долго? Пять попыток с паузами это секунд пятнадцать, а следующий запуск через пять минут. Нормально, что воркер столько спит?


Ответ
GalinaKorneva
Присоединился: 4 года назад

Читает чужие PR бэкенд-разработчица
Сообщения: 30

Пятнадцать секунд раз в пять минут - нормально. Плохо, если запуски начнут накладываться друг на друга. Когда поставщик лежит долго, у вас просто остаётся старый курс, а в лог и мониторинг уходит одна ошибка на запуск, а не триста. Следующим шагом можно сделать простой предохранитель: после пяти неудач подряд не ходить к поставщику минут десять.


Ответ
kirill_spb
Сообщения: 27
Собрал свой gem разработчик инструментов
Присоединился: 4 года назад

У faraday есть middleware для ретраев, но ради одного вызова раз в пять минут я бы оставил свои двадцать строк


Ответ
vanyaandreev
Сообщения: 3
Автор темы
Пишет в irb разработчик
Присоединился: 1 год назад

Переписал, как предложила Галина, только паузу ограничил сверху десятью секундами. Неделю в проде: 502 было 23 раза, и во всех случаях курс получили со второй или третьей попытки. Один раз поставщик висел минут двадцать, в логе вместо простыни ошибок по одной строке на запуск. Предохранитель пока отложил в задачи.


Ответ