Почему я периодически получаю сообщение "OpenSSL::SSL::SSLError: SSL_read: sslv3 alert bad record mac" от Heroku Redis?

После перехода с Redis To Go на Heroku Redis код Redis в нашем приложении Ruby on Rails несколько раз в день выдает ошибку "OpenSSL::SSL::SSLError: SSL_read: sslv3 alert bad record mac".

Есть идеи почему?

3 ответа

Я полагаю, что вы столкнулись с проблемой многопроцессорности, когда разветвленный процесс закрывает соединение Redis родительского процесса. Я только что нашел ошибку, которая приводит к той же ошибке в resqueкоторые пострадали от этой проблемы.

https://github.com/resque/resque/pull/1739

Это не решает проблему напрямую, но вот что сказала поддержка Heroku:

Эту проблему было трудно диагностировать из-за ее редкости и непоследовательности. У нас есть много сообщений о том, что это происходит в разных направлениях, языках приложений, конфигурациях Heroku Dyno и многих других деталях. Эта проблема была написана и диагностируется инженерами, но, опять же, детали сделали для нас почти невозможным сделать это.

Кроме того, у нас нет инфраструктуры для управления исходящими соединениями. У нас есть информация об использовании сети в необработанном виде (количество переданных байтов, количество пакетов и т. Д.), Но в отличие от входящих соединений, в которых маршрутизатор Heroku обрабатывает запросы, исходящие соединения являются "стандартными", как это предусмотрено нашим провайдером инфраструктуры. Не существует специфической инфраструктуры Heroku для работы с исходящими соединениями. Единственный интересный объект - это виртуальный интерфейс, который использует Dynos, и, соответственно, конфигурация сети хоста Dyno, но, опять же, в этом нет ничего особенного. Он использует платформу инфраструктуры, предоставленную конфигурацию сети, необходимую для связи хоста.

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

Если у вас есть сведения о том, как эта ошибка воспроизводится последовательно, это поможет нам значительно.

Я работал над этим в приложении Ruby on Rails, повторяя эти ошибки. До сих пор, кажется, избавился от ошибок. Раньше мы получали около 5 в день, а в прошлый день - с момента введения этого обходного пути - не было ни одного.

В config/initializers/redis_heroku_error_handling.rb:

# Hack to retry errors on Heroku Redis: https://stackru.com/questions/50228454/why-am-i-getting-opensslsslsslerror-ssl-read-sslv3-alert-bad-record-mac

raise "Ensure this hack still works!" if Redis::VERSION != "3.3.5"

module RedisHerokuErrorHandlingHack
  def call(command)
    attempts_left = 3

    begin
      super
    rescue OpenSSL::SSL::SSLError => e
      raise unless e.message.include?("SSL_read: sslv3 alert bad record mac")

      attempts_left -= 1
      raise unless attempts_left > 0

      retry
    end
  end
end

class Redis::Client
  prepend RedisHerokuErrorHandlingHack
end

В spec/initializers/redis_heroku_error_handling_spec.rb:

require "rails_helper"

describe RedisHerokuErrorHandlingHack do
  it "retries 'bad record mac' errors" do
    exception = OpenSSL::SSL::SSLError.new("SSL_read: sslv3 alert bad record mac")
    fail_then_maybe_succeed(exception: exception, fail_times: 2)

    expect($redis.get("hello")).to eq("success response")
  end

  it "fails if a few retries didn't help" do
    exception = OpenSSL::SSL::SSLError.new("SSL_read: sslv3 alert bad record mac")
    fail_then_maybe_succeed(exception: exception, fail_times: 3)

    expect { $redis.get("hello") }.to raise_error(/bad record mac/)
  end

  it "does not retry other errors" do
    fail_then_maybe_succeed(exception: "Boom", fail_times: 2)

    expect { $redis.get("hello") }.to raise_error("Boom")
  end

  private

  def fail_then_maybe_succeed(exception:, fail_times:)
    attempt_count = 0

    allow_any_instance_of(Redis::Client).to receive(:process) do |*args|
      attempt_count += 1

      if attempt_count <= fail_times
        raise exception
      else
        "success response"
      end
    end
  end
end
Другие вопросы по тегам