Я пытаюсь извлечь кучу записей, хранящихся в очереди Redis, и записать их в базу данных партиями по 1000.

Идея состоит в том, чтобы обернуть транзакцию redis внутри транзакции базы данных, чтобы в случае сбоя фиксации базы данных транзакция redis также взорвалась, чтобы элементы, которые я lpop'ed, не исчезли, вот простой пример для 10 записей:

  REDIS.with do |redis|
    redis.multi do |multi|
      MyRailsModel.transaction do
        10.times do
          attrs = JSON.parse(multi.lpop("foo"))
          MyRailsModel.create(attrs)
        end
      end
    end
  end

В примере используются соглашения ActiveRecord, но он будет работать с любой настройкой.

Проблема, которую я получаю, заключается в том, что multi.lpop("foo") на самом деле возвращает не значение, а Redis::Future - и если я попытаюсь просто поставить SON.parse(multi.lpop("foo").value), я получу ошибку Redis::FutureNotReady: Value will be available once the pipeline executes..

У меня начинает возникать ощущение от API Redis, что то, что я пытаюсь сделать, может оказаться неосуществимым, но мне трудно поверить, что что-то настолько простое, как получение значения, невозможно в транзакции Redis, поэтому я Я надеюсь, что кто-то знает что-то, что мне не хватает

1
bbozo 7 Ноя 2019 в 23:02

1 ответ

Транзакции в Redis сильно отличаются от транзакций в Postgres. Из документов Redis:

Транзакция Redis вводится с помощью команды MULTI. Команда всегда отвечает OK. На этом этапе пользователь может выполнить несколько команд. Вместо выполнения этих команд Redis ставит их в очередь. Все команды выполняются после вызова EXEC.

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

И это не транзакции ACID; они обеспечивают изоляцию, но не атомарность. Невозможно отменить изменение и, что еще хуже:

даже в случае сбоя команды все остальные команды в очереди обрабатываются — Redis не останавливает обработку команд.

(Конечно, для всего этого есть веская причина: Redis является однопоточным, поэтому команды изолированы просто потому, что они никогда не выполняются параллельно, а «транзакция» — не что иное, как гарантия того, что ваш блок команд не будет чередоваться с чьими-либо еще. И, конечно же, он хочет выполнить этот блок как можно быстрее физически, и он не собирается тратить время на ожидание возврата к вашему клиенту или создание версий всех ваших данных для удовлетворения периодический откат.)


Redis на самом деле не предоставляет высокоуровневые абстракции, такие как транзакции ACID, он предоставляет набор низкоуровневых команд, которые вы можете (надеюсь) комбинировать в соответствии с вашими потребностями. И если вам нужна функция отката для очереди задач, основная интересующая вас команда — RPOPLPUSH :

Атомарно возвращает и удаляет последний элемент (хвост) списка, хранящегося в source, и помещает элемент в первый элемент (голову) списка, хранящегося в destination.

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

Если вы не хотите заниматься всеми этими проблемами самостоятельно, существует миллион реализаций очередей сообщений, многие из них работает поверх Redis.

2
Nick Barnes 8 Ноя 2019 в 15:09