WeChat ищите и следите за публичной учетной записью «Капли воды и серебряные пули» и получайте высококачественную техническую галантерею как можно скорее. 7 лет исследований и разработок в области бэк-энда, демонстрирующих вам другую техническую перспективу.
Привет, меня зовут Кайто.
Я часто слышу множество дискуссий о том, уместно ли использовать Redis в качестве очереди.
Некоторые люди поддерживают, утверждая, что Redis легковесен и удобен в использовании в качестве очереди.
Некоторые люди против этого, думая, что Redis «потеряет» данные, и лучше использовать «профессиональное» промежуточное ПО очереди.
Какой план лучше?
В этой статье я расскажу вам о том, уместно ли использовать Redis в качестве очереди.
Я пойду от простого к сложному и шаг за шагом проведу вас, чтобы разобраться в деталях и сделать эту проблему действительно ясной.
Надеюсь, после прочтения этой статьи у вас появится новое понимание этого вопроса.
В конце статьи я также расскажу вам об идее «технического отбора».Статья немного длинновата, и я надеюсь, что вы сможете ее терпеливо прочитать.
Начните с самого простого: Список очередей
Во-первых, давайте начнем с самого простого сценария.
Если ваши бизнес-требования достаточно просты и вы хотите использовать Redis в качестве очереди, вы должны сначала подумать об использовании типа данных List.
Поскольку базовая реализация List представляет собой «связный список», временная сложность операционных элементов в начале и в конце составляет O (1), что означает, что он очень согласуется с моделью очереди сообщений.
Если вы думаете о List как об очереди, вы можете использовать его таким образом.
Производители используют LPUSH для публикации сообщений:
127.0.0.1:6379> LPUSH queue msg1
(integer) 1
127.0.0.1:6379> LPUSH queue msg2
(integer) 2
На стороне потребителя используйте RPOP для извлечения сообщений:
127.0.0.1:6379> RPOP queue
"msg1"
127.0.0.1:6379> RPOP queue
"msg2"
Эта модель очень проста и понятна.
Но здесь есть небольшая проблема: при отсутствии сообщения в очереди потребитель вернет NULL при выполнении RPOP.
127.0.0.1:6379> RPOP queue
(nil) // 没消息了
Когда мы пишем потребительскую логику, это обычно "бесконечный цикл". Эта логика должна постоянно извлекать сообщения из очереди для обработки. Псевдокод обычно записывается следующим образом:
while true:
msg = redis.rpop("queue")
// 没有消息,继续循环
if msg == null:
continue
// 处理消息
handle(msg)
Если в это время очередь пуста, потребители по-прежнему будут часто извлекать сообщения, что приведет к «простаю ЦП», что не только тратит ресурсы ЦП, но и оказывает давление на Redis.
Как решить эту проблему?
Это также очень просто: когда очередь пуста, мы можем немного «поспать», а затем попытаться получить сообщения. Код можно изменить следующим образом:
while true:
msg = redis.rpop("queue")
// 没有消息,休眠2s
if msg == null:
sleep(2)
continue
// 处理消息
handle(msg)
Это решает проблему холостого хода процессора.
Хотя эта проблема решена, возникает другая проблема: когда потребитель спит и ждет, а приходит новое сообщение, у потребителя будет «задержка» для обработки нового сообщения.
Предполагая, что время ожидания установлено на 2 секунды, для новых сообщений будет задержка не более 2 секунд.
Чтобы сократить эту задержку, вы можете только уменьшить время сна. Однако чем меньше время сна, тем больше вероятность возникновения проблемы с холостым ходом ЦП.
У вас не может быть и того, и другого.
Как это сделать, чтобы не только вовремя обрабатывать новые сообщения, но и чтобы не простаивал процессор?
Есть ли в Redis такой механизм: если очередь пуста, потребители будут «блокировать и ждать» при извлечении сообщений, а как только придут новые сообщения, уведомлять моих потребителей о немедленной обработке новых сообщений?
К счастью, Redis предоставляет команду «блокировки» для извлечения сообщений: BRPOP / BLPOP, где B означает блокировку.
Теперь вы можете получать сообщения следующим образом:
while true:
// 没消息阻塞等待,0表示不设置超时时间
msg = redis.brpop("queue", 0)
if msg == null:
continue
// 处理消息
handle(msg)
При использовании метода блокировки BRPOP для извлечения сообщений он также поддерживает передачу «тайм-аута».Если он установлен на 0, это означает, что тайм-аут не установлен, и он не вернется, пока не появится новое сообщение, в противном случае вернет NULL после указанного тайм-аута.
Это решение хорошее, оно не только учитывает эффективность, но и позволяет избежать проблемы холостого хода процессора, убивая двух зайцев одним выстрелом.
Примечание. Если установлен слишком большой тайм-аут и соединение не было активным в течение длительного времени, Redis Server может расценить его как недействительное, и тогда Redis Server отключит клиент от сети. Следовательно, используя эту схему, у клиента должен быть механизм переподключения.
Решена проблема несвоевременной обработки сообщений, можно еще раз подумать, какие минусы у этой модели очереди?
Давайте проанализируем это вместе:
- Повторное потребление не поддерживается: после того, как потребитель извлекает сообщение, оно удаляется из списка и не может быть повторно использовано другими потребителями, т. е. несколько потребителей не поддерживают использование одного и того же пакета данных.
- сообщение потеряно: после того, как потребитель извлечет сообщение, в случае ненормального простоя сообщение будет потеряно.
Первая проблема функциональна, используя List в качестве очереди сообщений, он поддерживает только самую простую, группа производителей соответствует группе потребителей и не может соответствовать бизнес-сценарию нескольких групп производителей и потребителей.
Вторая проблема сложнее, потому что после того, как сообщение выталкивается из списка, оно немедленно удаляется из списка. Иными словами, независимо от того, обработает ли потребитель успешно или нет, сообщение не может быть использовано снова.
Это также означает, что если потребитель ненормально выходит из строя во время обработки сообщения, сообщение эквивалентно потере.
Как решить эти две проблемы? Давайте посмотрим на них один за другим.
Модель публикации/подписки: Pub/Sub
Как видно из названия, этот модуль специально разработан Redis для модели очереди «публикация/подписка».
Это просто решает первую проблему, упомянутую ранее: повторное потребление.
То есть сценарий нескольких групп производителей и потребителей, посмотрим, как он это сделает.
Redis предоставляет команды PUBLISH/SUBSCRIBE для выполнения операций публикации и подписки.
Предположим, вы хотите открыть двух потребителей и одновременно использовать один и тот же пакет данных. Это можно сделать следующим образом.
Сначала используйте команду SUBSCRIBE, чтобы запустить двух потребителей и «подписаться» на одну и ту же очередь.
// 2个消费者 都订阅一个队列
127.0.0.1:6379> SUBSCRIBE queue
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "queue"
3) (integer) 1
В этот момент оба потребителя будут заблокированы в ожидании поступления новых сообщений.
После этого запустите другого производителя и опубликуйте сообщение.
127.0.0.1:6379> PUBLISH queue msg1
(integer) 1
В этот момент два потребителя разблокируются и получат новые сообщения от производителя.
127.0.0.1:6379> SUBSCRIBE queue
// 收到新消息
1) "message"
2) "queue"
3) "msg1"
Вы видели, что использование решения Pub/Sub не только поддерживает блокировку сообщений, но и удовлетворяет бизнес-потребности нескольких групп потребителей, использующих один и тот же пакет данных.
Кроме того, Pub/Sub также предоставляет режим «согласованной подписки», который позволяет потребителям подписываться на «несколько» интересующих их очередей в соответствии с определенными правилами.
// 订阅符合规则的队列
127.0.0.1:6379> PSUBSCRIBE queue.*
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "queue.*"
3) (integer) 1
Здесь потребитель подписывается на сообщения очереди, связанные с queue.*.
После этого производители публикуют сообщения в queue.p1 и queue.p2 соответственно.
127.0.0.1:6379> PUBLISH queue.p1 msg1
(integer) 1
127.0.0.1:6379> PUBLISH queue.p2 msg2
(integer) 1
Затем снова посмотрите на потребителя, и он может получать сообщения от этих двух производителей.
127.0.0.1:6379> PSUBSCRIBE queue.*
Reading messages... (press Ctrl-C to quit)
...
// 来自queue.p1的消息
1) "pmessage"
2) "queue.*"
3) "queue.p1"
4) "msg1"
// 来自queue.p2的消息
1) "pmessage"
2) "queue.*"
3) "queue.p2"
4) "msg2"
Мы видим, что самым большим преимуществом Pub/Sub является то, что он поддерживает несколько групп производителей и потребителей для обработки сообщений.
После разговора о его преимуществах, каковы его недостатки?
На самом деле, самая большая проблема с Pub/Sub:потерянные данные.
Потеря данных может произойти в следующих случаях:
- Потребители уходят в оффлайн
- Редис недоступен
- сообщения накапливаются
Что происходит?
На самом деле это во многом связано с реализацией Pub/Sub.
Pub/Sub очень прост в реализации. Он не основан на каком-либо типе данных и не хранит данные. Он просто устанавливает «канал пересылки данных» для производителей и потребителей и пересылает данные, соответствующие правилам, из одного конец, на другой конец.
Полный процесс обработки сообщений публикации и подписки выглядит следующим образом:
- Потребитель подписывается на указанную очередь, и Redis записывает отношение сопоставления: очередь -> потребитель
- Производитель публикует сообщение в этой очереди, затем Redis находит соответствующего потребителя из отношения сопоставления и пересылает ему сообщение.
Видите, во всем процессе нет хранения данных, все пересылается в режиме реального времени.
Такое конструктивное решение приводит к проблемам, упомянутым выше.
Например, если потребитель аварийно зависает, он сможет получать новые сообщения только после того, как снова подключится к сети.Сообщения, опубликованные производителем в период автономного режима, будут отброшены, поскольку потребитель не может быть найден.
Если все потребители находятся в автономном режиме, сообщения, опубликованные производителем, будут «отброшены», поскольку потребитель не может быть найден.
Итак, когда вы используете Pub/Sub, обязательно обратите внимание на:Потребитель должен подписаться на очередь, прежде чем производитель сможет опубликовать сообщение, иначе сообщение будет потеряно.
По этой же причине в предыдущем примере мы сначала разрешили потребителям подписаться на очередь, а затем позволили производителям публиковать сообщения.
Кроме того, поскольку Pub/Sub не реализуется на основе какого-либо типа данных, у него также нет возможности «сохранять данные».
То есть связанные операции Pub/Sub не будут записываться в RDB и AOF.Когда Redis отключается и перезапускается, все данные Pub/Sub будут потеряны.
Наконец, давайте посмотрим, почему Pub/Sub также теряет данные при работе с «незавершенными сообщениями»?
Когда скорость потребителей не может идти в ногу с производителями, это приводит к отставанию данных.
Если список используется в качестве очереди, когда сообщение задерживается, связанный список будет очень длинным.Самое непосредственное влияние заключается в том, что память Redis будет продолжать расти до тех пор, пока потребитель не удалит все данные из связанного списка.
Но Pub/Sub обрабатывается по-другому.Сбой потребления и потеря сообщения!
Как это происходит?
Вернемся к деталям реализации Pub/Sub.
Когда каждый потребитель подписывается на очередь, Redis выделяет «буфер» потребителю на сервере, который на самом деле является блоком памяти.
Когда производитель публикует сообщение, Redis сначала записывает сообщение в соответствующий буфер потребителя.
После этого потребитель непрерывно читает сообщения из буфера и обрабатывает сообщения.
Однако проблема именно с этим буфером.
Поскольку этот буфер на самом деле имеет «верхний предел» (настраиваемый), если потребитель медленно извлекает сообщения, это приведет к накоплению сообщений, опубликованных производителем в буфере, и буферная память будет продолжать расти.
Если превышен верхний предел конфигурации буфера, в это время Redis «заставит» потребителя отключиться от линии.
В это время потребитель не сможет потреблять и терять данные.
Если вы видели файл конфигурации Redis, вы можете увидеть конфигурацию этого буфера по умолчанию: client-output-buffer-limit pubsub 32mb 8mb 60.
Его параметры имеют следующие значения:
- 32 МБ: как только размер буфера превышает 32 МБ, Redis напрямую принудительно отключает потребителя.
- 8 МБ + 60: если буфер превышает 8 МБ и длится 60 секунд, Redis также отключит потребителя.
Эта функция Pub/Sub сильно отличается от функции List как очереди.
Отсюда вы должны увидеть, что,Список на самом деле относится к модели «вытягивания», а Pub/Sub — к модели «выталкивания»..
Данные в списке всегда могут храниться в памяти, и потребители могут «извлекать» их, когда захотят.
Но Pub/Sub сначала «отправляет» сообщение в буфер потребителя на сервере Redis, а затем ждет, пока потребитель его извлечет.
Когда скорости производства и потребления не совпадают, память буфера начинает расширяться.Чтобы контролировать верхний предел буфера, Redis имеет упомянутый выше механизм, чтобы принудительно отключить потребителя.
Хорошо, теперь давайте суммируем плюсы и минусы Pub/Sub:
- Поддержка публикации/подписки, поддержка нескольких групп производителей и потребителей для обработки сообщений
- Потребители отключаются, данные будут потеряны
- Сохранение данных не поддерживается, Redis не работает, и данные потеряны
- Сообщения накапливаются, буфер переполняется, потребители отключаются от сети, а данные теряются.
Вы обнаружили, что, кроме первого преимущества, все остальные являются недостатками.
Поэтому, увидев характеристики Pub/Sub, многие считают эту функцию «курицей».
Именно по вышеуказанным причинам Pub/Sub мало используется в сценариях практических приложений.
В настоящее время при взаимодействии кластера Sentinel с экземпляром Redis используется только схема Pub/Sub, поскольку Sentinel как раз подходит для бизнес-сценария обмена мгновенными сообщениями.
Давайте еще раз посмотрим, решил ли Pub/Sub проблему аварийного простоя при обработке сообщений и невозможности их повторного использования?
На самом деле это не работает.После того, как Pub/Sub забирает данные из буфера, данные удаляются из буфера Redis.Если у потребителя возникает исключение, естественно, его нельзя переиспользовать повторно.
Хорошо, теперь давайте реорганизуем наши потребности при использовании очередей сообщений.
Когда мы используем очередь сообщений, мы хотим, чтобы она функционировала следующим образом:
- Поддержка блокировки ожидания сообщений о вытягивании
- Поддержка модели публикации/подписки
- Потребление не удается, может быть использовано повторно, сообщения не теряются
- Экземпляр не работает, сообщение не потеряно, и данные могут быть сохранены
- Сообщения могут быть сложены
Имеются ли в Redis типы данных, отличные от List и Pub/Sub, соответствующие этим требованиям?
На самом деле, автор Redis также видел вышеперечисленные проблемы и усердно работал в этих направлениях.
Во время разработки Redis автор Redis также разработал диск проекта с открытым исходным кодом.
Позиционирование этого проекта — промежуточное ПО распределенной очереди сообщений на основе памяти.
Но по разным причинам проект был прохладным.
Наконец, в Redis 5.0 автор перенес функцию disque в Redis и определил для нее новый тип данных:Stream.
Давайте посмотрим, соответствует ли он указанным выше требованиям?
Зрелая очередь: поток
Давайте посмотрим, как Stream решает вышеуказанные проблемы.
Мы по-прежнему идем от простого к сложному и смотрим, как Stream поочередно обрабатывает очереди сообщений?
Во-первых, Stream завершает простейшую модель производства и потребления через XADD и XREAD:
- XADD: сообщение об освобождении
- XREAD: прочитать сообщение
Производитель публикует 2 сообщения:
// *表示让Redis自动生成消息ID
127.0.0.1:6379> XADD queue * name zhangsan
"1618469123380-0"
127.0.0.1:6379> XADD queue * name lisi
"1618469127777-0"
Используйте команду XADD, чтобы опубликовать сообщение, где «*» означает, что Redis автоматически создает уникальный идентификатор сообщения.
Формат этого идентификатора сообщения — «отметка времени — автоматически увеличивающийся порядковый номер».
Потребитель тянет сообщение:
// 从开头读取5条消息,0-0表示从开头读取
127.0.0.1:6379> XREAD COUNT 5 STREAMS queue 0-0
1) 1) "queue"
2) 1) 1) "1618469123380-0"
2) 1) "name"
2) "zhangsan"
2) 1) "1618469127777-0"
2) 1) "name"
2) "lisi"
Если вы хотите продолжить получение сообщений, вам нужно передать идентификатор предыдущего сообщения:
127.0.0.1:6379> XREAD COUNT 5 STREAMS queue 1618469127777-0
(nil)
Если сообщения нет, Redis вернет NULL.
Выше приведено простейшее производство и потребление Stream.
Я не буду здесь заострять внимание на различных параметрах команды Stream.Когда я демонстрирую в примере, все слова в верхнем регистре являются «фиксированными» параметрами, а все слова в нижнем регистре могут быть определены сами по себе, например, имя очереди, длина сообщения и т. д. , следующие правила примера одинаковы, для облегчения вашего понимания необходимо напомнить здесь.
Давайте посмотрим, как Stream решает вышеупомянутые требования к очереди сообщений?
1) Поддерживает ли Stream «блокировку» пулл-сообщений?
Да, при чтении сообщения нужно только увеличить параметр BLOCK.
// BLOCK 0 表示阻塞等待,不设置超时时间
127.0.0.1:6379> XREAD COUNT 5 BLOCK 0 STREAMS queue 1618469127777-0
В это время потребитель будет блокироваться и ждать, пока производитель опубликует новое сообщение, прежде чем вернуться.
2) Поддерживает ли Stream модель публикации/подписки?
Нет проблем, Stream завершает публикацию и подписку с помощью следующих команд:
- XGROUP: создать группу потребителей
- XREADGROUP: в указанной группе потребителей разрешить потребителям извлекать сообщения.
Давайте посмотрим, как это сделать?
Во-первых, производитель все же публикует 2 сообщения:
127.0.0.1:6379> XADD queue * name zhangsan
"1618470740565-0"
127.0.0.1:6379> XADD queue * name lisi
"1618470743793-0"
После этого, если мы хотим открыть 2 группы потребителей для обработки одного и того же пакета данных, нам нужно создать 2 группы потребителей:
// 创建消费者组1,0-0表示从头拉取消息
127.0.0.1:6379> XGROUP CREATE queue group1 0-0
OK
// 创建消费者组2,0-0表示从头拉取消息
127.0.0.1:6379> XGROUP CREATE queue group2 0-0
OK
После создания группы потребителей мы можем прикрепить «потребителя» к каждой «группе потребителей» и позволить им соответственно обрабатывать один и тот же пакет данных.
Первая группа потребителей начинает потреблять:
// group1的consumer开始消费,>表示拉取最新数据
127.0.0.1:6379> XREADGROUP GROUP group1 consumer COUNT 5 STREAMS queue >
1) 1) "queue"
2) 1) 1) "1618470740565-0"
2) 1) "name"
2) "zhangsan"
2) 1) "1618470743793-0"
2) 1) "name"
2) "lisi"
Точно так же вторая группа потребителей начинает потреблять:
// group2的consumer开始消费,>表示拉取最新数据
127.0.0.1:6379> XREADGROUP GROUP group2 consumer COUNT 5 STREAMS queue >
1) 1) "queue"
2) 1) 1) "1618470740565-0"
2) 1) "name"
2) "zhangsan"
2) 1) "1618470743793-0"
2) 1) "name"
2) "lisi"
Мы видим, что обе группы потребителей могут получить один и тот же пакет данных для обработки.
Таким образом достигается цель «подписного» потребления несколькими группами потребителей.
3) Когда сообщение обрабатывается ненормально, может ли Stream гарантировать, что сообщение не будет потеряно и повторно использовано?
В дополнение к идентификатору сообщения, используемому при извлечении сообщения выше, этот идентификатор сообщения также используется здесь, чтобы обеспечить повторное использование.
Когда группа потребителей завершила обработку сообщения, им необходимо выполнить команду XACK, чтобы сообщить об этом Redis, после чего Redis пометит сообщение как «обработка завершена».
// group1下的 1618472043089-0 消息已处理完成
127.0.0.1:6379> XACK queue group1 1618472043089-0
Если потребитель аварийно отключен, XACK точно не будет отправлен, тогда Redis все равно сохранит это сообщение.
После того, как эта группа потребителей вернется в сеть, Redis повторно отправит данные, которые не были успешно обработаны, потребителю. Таким образом, даже если потребитель ненормальный, данные не будут потеряны.
// 消费者重新上线,0-0表示重新拉取未ACK的消息
127.0.0.1:6379> XREADGROUP GROUP group1 consumer1 COUNT 5 STREAMS queue 0-0
// 之前没消费成功的数据,依旧可以重新消费
1) 1) "queue"
2) 1) 1) "1618472043089-0"
2) 1) "name"
2) "zhangsan"
2) 1) "1618472045158-0"
2) 1) "name"
2) "lisi"
4) Будут ли потоковые данные записываться в RDB и AOF для сохранения?
Поток — это недавно добавленный тип данных.Как и другие типы данных, каждая операция записи также будет записываться в RDB и AOF.
Нам нужно только настроить стратегию сохраняемости, чтобы даже если Redis отключился и перезапустился, данные в Stream можно было восстановить из RDB или AOF.
5) Как Stream справляется с накоплением сообщений?
На самом деле, когда сообщения накапливаются в очереди сообщений, обычно есть только 2 решения:
- Текущее ограничение производителя: избегайте несвоевременной обработки потребителями, что приводит к постоянному отставанию
- Отбрасывать сообщения: ПО промежуточного слоя отбрасывает старые сообщения и сохраняет только новые сообщения фиксированной длины.
Когда Redis реализует Stream, применяется вторая схема.
При публикации сообщения вы можете указать максимальную длину очереди, чтобы предотвратить взрыв памяти, вызванный невыполненной очередью.
// 队列长度最大10000
127.0.0.1:6379> XADD queue MAXLEN 10000 * name zhangsan
"1618473015018-0"
Когда длина очереди превышает верхний предел, старые сообщения будут удаляться, и будут сохраняться только новые сообщения фиксированной длины.
С этой точки зрения, если в потоке есть задолженность по сообщениям, если указана максимальная длина, все равно возможна потеря сообщений.
В дополнение к представленным выше командам Stream также поддерживает такие команды, как просмотр длины сообщения (XLEN) и просмотр статуса потребителя (XINFO).
Что ж, из приведенного выше введения мы видим, что Stream of Redis охватывает почти все виды сценариев очереди сообщений.Как вы думаете, это идеально?
Поскольку его функции настолько мощны, означает ли это, что Redis действительно можно использовать в качестве промежуточного программного обеспечения для профессиональных очередей сообщений?
Но это все еще «почти», даже если Redis может сделать вышеперечисленное, это всего лишь «ближе» к профессиональной очереди сообщений.
Причина в том, что есть некоторые проблемы с самим Redis, если он и позиционируется как очередь сообщений, то все же чего-то не хватает.
На данный момент мы должны сравнить Redis с профессиональным промежуточным программным обеспечением для очередей.
Давайте посмотрим, каковы недостатки Redis при использовании в качестве очереди?
По сравнению с профессиональными очередями сообщений
На самом деле профессиональная очередь сообщений должна делать две вещи:
- Сообщение не потеряно
- Сообщения могут быть сложены
В центре нашего обсуждения ранее, много места вращается вокруг первого пункта.
Здесь давайте посмотрим на это под другим углом и проанализируем с точки зрения «модели использования» очереди сообщений, как мы можем гарантировать, что данные не будут потеряны?
Использование очереди сообщений фактически разделено на три части:Производитель, промежуточное ПО очереди, потребитель.
Будет ли сообщение потеряно или нет, зависит от следующих трех ссылок:
- Будут ли продюсеры терять сообщения?
- Будут ли потребители терять сообщения?
- Будет ли ПО промежуточного слоя очереди терять сообщения?
1) Будет ли производитель терять сообщения?
Когда производитель публикует сообщение, могут возникнуть следующие исключения:
- Сообщение не было отправлено: сбой сети или другие проблемы привели к сбою публикации, и промежуточное ПО напрямую вернуло ошибку.
- Не уверен, что сообщение было успешным: из-за проблем с сетью время ожидания сообщения истекло, возможно, данные были отправлены успешно, но время ожидания ответа на чтение истекло.
Если это случай 1, то сообщение вообще не отправляется, то хорошо отправить его еще раз.
Если это случай 2, производитель не может узнать, успешно ли отправлено сообщение? Поэтому, чтобы избежать потери сообщения, он может продолжать попытки только до тех пор, пока публикация не будет успешной.
Производитель обычно устанавливает максимальное количество повторных попыток.Если оно превышает верхний предел, все равно происходит сбой, и ему необходимо записать в журнал обработку аварийного сигнала.
То есть, чтобы избежать потери сообщения, производитель может справиться с этим, только повторив попытку в случае неудачи.
Но не нашел? Это также означает, что сообщения могут отправляться повторно.
Да, при использовании очередей сообщений необходимо следить за тем, чтобы сообщения не терялись, и лучше переслать их, чем отбрасывать.
Со стороны потребителя необходимо больше логики.
Для конфиденциального бизнеса, когда потребители получают дублирующиеся данные, должна быть разработана идемпотентная логика, чтобы обеспечить правильность бизнеса.
С этой точки зрения, потеряет ли производитель сообщение, зависит от того, разумно ли производитель справляется с нештатной ситуацией.
Поэтому, будь то Redis или профессиональное ПО промежуточного слоя очередей, производитель может гарантировать, что сообщения не будут потеряны на этом этапе.
2) Будут ли потребители терять сообщения?
Это та ситуация, о которой мы упоминали ранее.После того, как потребитель получает сообщение, оно аварийно отключается до завершения обработки.Может ли потребитель повторно использовать ошибочное сообщение?
Чтобы решить эту проблему, потребитель должен «уведомить» промежуточное ПО очереди после обработки сообщения, и промежуточное ПО очереди пометит его как обработанное, иначе данные все равно будут отправлены потребителю.
Это решение требует, чтобы потребители и промежуточное ПО взаимодействовали друг с другом, чтобы гарантировать, что сообщения на стороне потребителя не будут потеряны.
Будь то Redis Stream или профессиональное промежуточное ПО для очередей, такое как RabbitMQ и Kafka, это действительно делается.
Итак, с этой точки зрения Redis также подходит.
3) Будет ли ПО промежуточного слоя очереди терять сообщения?
С первыми двумя проблемами справиться относительно легко: если клиент и сервер хорошо взаимодействуют друг с другом, это гарантирует, что ни производитель, ни потребитель не потеряют сообщения.
Но что, если промежуточное ПО очереди по своей природе ненадежно?
Ведь на него полагаются и производители, и потребители, если он ненадежен, то что бы ни делали производители и потребители, нет никакой гарантии, что данные не будут потеряны.
В этом отношении Redis фактически не соответствует требованиям.
Redis приведет к потере данных в следующих двух сценариях.
- Постоянство AOF настроено на запись на диски каждую секунду, но этот процесс записи на диски является асинхронным, и существует вероятность потери данных при сбое Redis.
- Репликация master-slave также является асинхронной, при переключении master-slave также существует вероятность потери данных (данные, отправленные из master БД, не были завершены синхронно из slave БД, и они будут переданы в master база данных)
По вышеуказанным причинам мы видим, чтоСам Redis не может гарантировать строгую целостность данных..
Поэтому, если Redis используется в качестве очереди сообщений, это может привести к потере данных в этом отношении.
Давайте посмотрим, как профессиональное ПО промежуточного слоя очереди сообщений решает эту проблему?
Когда используется профессиональное промежуточное ПО очереди, такое как RabbitMQ или Kafka, обычно развертывается кластер.Когда производитель публикует сообщение, промежуточное ПО очереди обычно записывает «несколько узлов», чтобы обеспечить целостность сообщения. Таким образом, даже если один из узлов выйдет из строя, можно гарантировать, что данные кластера не будут потеряны.
Из-за этого rabbitmq и кафка также являются более сложными в дизайне. В конце концов, они специально предназначены для сценариев очереди.
Но позиционирование Redis отличается.Его позиционирование больше используется как кеш.В этом отношении между ними определенно есть различия.
Наконец, давайте посмотрим, что делать с бэклогом сообщений?
4) Что делать с бэклогом сообщений?
Поскольку данные Redis хранятся в памяти, это означает, что после возникновения очереди сообщений объем памяти Redis будет продолжать расти.
Поэтому Redis Stream предоставляет функцию указания максимальной длины очереди, чтобы этого не происходило.
Однако очереди сообщений, такие как Kafka и RabbitMQ, отличаются друг от друга. Их данные будут храниться на диске, а стоимость диска намного меньше, чем стоимость памяти. Когда сообщения задерживаются, они просто занимают больше места на диске. , Так же будет более "спокойно" перед лицом отставаний.
Подводя итог, мы видим, что при использовании Redis в качестве очереди всегда возникают две проблемы:
- Сам Redis может потерять данные
- Перед лицом невыполненных сообщений ресурсы памяти Redis ограничены
На данный момент, можно ли использовать Redis в качестве очереди, я думаю, вы должны быть более ясны с этим ответом.
Если ваш бизнес-сценарий достаточно прост, он не чувствителен к потере данных, а вероятность бэклога сообщений мала, вполне возможно использовать Redis в качестве очереди.
Более того, по сравнению с Kafka и RabbitMQ, Redis проще в развертывании, эксплуатации и обслуживании.
Если ваш бизнес-сценарий очень чувствителен к потере данных, а объем записи очень велик, а невыполненные сообщения будут занимать много машинных ресурсов, тогда я рекомендую вам использовать профессиональное промежуточное ПО для очередей сообщений.
Суммировать
Хорошо, подытожим. В этой статье мы начнем с точки зрения «Можно ли использовать Redis в качестве очереди» и расскажем, как List, Pub/Sub и Stream используются в качестве очередей, а также их соответствующие преимущества и недостатки.
Затем я сравнил Redis с профессиональным ПО промежуточного слоя очередей сообщений и обнаружил недостатки Redis.
Наконец, мы придумываем подходящий сценарий для Redis для создания очередей.
Здесь я также включил таблицу, суммирующую их плюсы и минусы.
постскриптум
Наконец, я хочу снова поговорить с вами о "Выбор технического решения"Эта проблема.
Вы также должны были видеть, что, хотя эта статья начинается с Redis, она не останавливается на Redis.
Когда мы анализируем детали Redis, мы задаем вопросы, а затем ищем лучшие решения.В конце статьи мы говорили о том, что должна делать профессиональная очередь сообщений.
На самом деле, когда мы обсуждаем выбор технологий, речь идет о том, как выбирать.
И сообщение, которое я хочу донести до вас, заключается в следующем:Столкнувшись с выбором технологии, не думайте, какая из них хорошая, а какая плохая, не подумав..
Вам нужно анализировать его в соответствии с конкретной сценой.Здесь я разделяю процесс анализа на 2 уровня:
- перспектива бизнес-функции
- технические ресурсы
Контент, упомянутый в этой статье, сделан с точки зрения бизнес-функций.
А вот второй момент здесь, с точки зрения технических ресурсов, на самом деле очень важен.
С точки зрения технических ресурсов,Могут ли окружающая среда и технические ресурсы вашей компании соответствовать этим техническим решениям?.
Как это объяснить?
Проще говоря, это то, есть ли у вашей компании и команды соответствующие ресурсы для реализации этих технических решений.
Все мы знаем, что Kafka и RabbitMQ — очень профессиональное промежуточное ПО для обмена сообщениями, но их развертывание, эксплуатация и обслуживание сложнее, чем Redis.
Если вы работаете в крупной компании, и в самой компании есть отличная команда по эксплуатации и обслуживанию, то использование этого промежуточного программного обеспечения, безусловно, не проблема, потому что есть достаточно хороших людей, чтобы владеть этим промежуточным программным обеспечением, и компания также будет инвестировать рабочую силу и время. в этом направлении.
Но если вы находитесь в начинающей компании, бизнес находится в периоде быстрого развития, и нет команды или человека, который может временно держать эти промежуточные компоненты, Если вы используете эти компоненты необдуманно, когда произойдет сбой, он станет очень сложно устранить проблему, это может даже помешать развитию бизнеса.
В этом случае, если технический персонал компании хорошо знаком с Redis, и по всесторонней оценке Redis может в основном удовлетворить 90% потребностей бизнеса, то выбор Redis на данный момент может быть не самым удачным решением.
так,Выбор технологии — это не только техническая проблема, но и связанная с людьми, командами, управлением и организационной структурой..
Именно по этим причинам, когда вы обсуждаете выбор технологии с другими, вы обнаружите, что каждая компания делает это по-своему.
В конце концов, каждая компания работает в разной среде и культуре, и, конечно, принимаемые решения будут разными.
Если вы не понимаете логики этого, то при выборе технологии она будет тяготеть только к поверхностным явлениям, и вы не сможете глубоко проникнуть в корень проблемы.
Как только вы поймете эту логику, то при рассмотрении этой проблемы у вас будет не только более глубокое понимание технологии, но и более четкое представление о технических ресурсах и людях.
Я надеюсь, что вы сможете принять во внимание эти факторы при выборе технологии в будущем, что будет очень полезно для развития вашей технологии.
Хотите увидеть больше хардкорных технических статей? Добро пожаловать, чтобы обратить внимание на мой публичный номер "капли воды и серебряные пули".
Я Кайто, старший back-end программист, который думает о технологиях, в своей статье я не только расскажу, что такое технический момент, но и расскажу, зачем вы им занимаетесь? Я также попытаюсь превратить эти мыслительные процессы в общую методологию, которую вы сможете применять в других областях, чтобы делать выводы друг из друга.