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
Работает, но чувствую, что неправильно: ловлю вообще всё, жду всегда одну секунду, таймаутов нет. Как это делают нормально?
Чувствуешь правильно. Ловить надо только то, что имеет смысл повторять: таймауты, обрывы соединения и ответы 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
У faraday есть middleware для ретраев, но ради одного вызова раз в пять минут я бы оставил свои двадцать строк
Переписал, как предложила Галина, только паузу ограничил сверху десятью секундами. Неделю в проде: 502 было 23 раза, и во всех случаях курс получили со второй или третьей попытки. Один раз поставщик висел минут двадцать, в логе вместо простыни ошибок по одной строке на запуск. Предохранитель пока отложил в задачи.