HTML Diff
0 added 0 removed
Original 2026-01-01
Modified 2026-02-26
1 <p>Представьте, что вы пишете back-end интернет-магазина. Когда клиент оставляет заказ, сервер сохраняет данные в БД, отправляет чек клиенту на электронную почту и передает информацию в отдел обработки заказов. Самый простой способ реализовать этот алгоритм - выполнять все последовательно синхронно. Однако при разработке веб-приложений всегда стоит учитывать, что что-то может пойти не так. Провайдер электронных писем может иметь технические проблемы, из-за чего отправка письма задержится на несколько секунд или письмо не отправится с первого раза.</p>
1 <p>Представьте, что вы пишете back-end интернет-магазина. Когда клиент оставляет заказ, сервер сохраняет данные в БД, отправляет чек клиенту на электронную почту и передает информацию в отдел обработки заказов. Самый простой способ реализовать этот алгоритм - выполнять все последовательно синхронно. Однако при разработке веб-приложений всегда стоит учитывать, что что-то может пойти не так. Провайдер электронных писем может иметь технические проблемы, из-за чего отправка письма задержится на несколько секунд или письмо не отправится с первого раза.</p>
2 <p>Допустим, провайдер электронных писем работает штатно, и выполнение алгоритма происходит дальше. Но теперь сервис обработки заказов недоступен по какой-то причине. И снова запрос зависает на несколько секунд или не отрабатывает вовсе.</p>
2 <p>Допустим, провайдер электронных писем работает штатно, и выполнение алгоритма происходит дальше. Но теперь сервис обработки заказов недоступен по какой-то причине. И снова запрос зависает на несколько секунд или не отрабатывает вовсе.</p>
3 <p>Должен ли пользователь долго ждать или видеть ошибки в случае временных технических неполадок со стороны третьих сервисов? Вряд ли. В правильной архитектуре запросы выполняются за десятки миллисекунд, а временные технические неполадки скрываются. Реализовать такую архитектуру можно с помощью асинхронной обработки отложенных задач.</p>
3 <p>Должен ли пользователь долго ждать или видеть ошибки в случае временных технических неполадок со стороны третьих сервисов? Вряд ли. В правильной архитектуре запросы выполняются за десятки миллисекунд, а временные технические неполадки скрываются. Реализовать такую архитектуру можно с помощью асинхронной обработки отложенных задач.</p>
4 <h2>Message Brokers</h2>
4 <h2>Message Brokers</h2>
5 <p>Асинхронную обработку обычно делают с помощью специальных сервисов - брокеров сообщений. Сегодня существует множество различных брокеров сообщений и каждый из них хорошо подходит под свои задачи. Концептуально работу брокера сообщений можно описать так:</p>
5 <p>Асинхронную обработку обычно делают с помощью специальных сервисов - брокеров сообщений. Сегодня существует множество различных брокеров сообщений и каждый из них хорошо подходит под свои задачи. Концептуально работу брокера сообщений можно описать так:</p>
6 <ol><li>Back-end сервис кладет сообщения в брокера</li>
6 <ol><li>Back-end сервис кладет сообщения в брокера</li>
7 <li>С другой стороны сервис-обработчик (worker) читает сообщения из брокера и как-то их обрабатывает</li>
7 <li>С другой стороны сервис-обработчик (worker) читает сообщения из брокера и как-то их обрабатывает</li>
8 <li>Сообщения внутри брокера буферизируются и формируется очередь. Если в один момент времени нужно записать 10 тыс. сообщений, они запишутся без задержки, а потом обработаются за какой-то промежуток времени</li>
8 <li>Сообщения внутри брокера буферизируются и формируется очередь. Если в один момент времени нужно записать 10 тыс. сообщений, они запишутся без задержки, а потом обработаются за какой-то промежуток времени</li>
9 </ol><h2>Redis Message Broker</h2>
9 </ol><h2>Redis Message Broker</h2>
10 <p>Многие проекты используют Redis как брокер сообщений из-за простоты интеграции. Популярные фреймворки абстрагируют от разработчика внутреннюю реализацию очередей в Redis. Однако в некоторых языках (например, Golang) можно интегрироваться самому.</p>
10 <p>Многие проекты используют Redis как брокер сообщений из-за простоты интеграции. Популярные фреймворки абстрагируют от разработчика внутреннюю реализацию очередей в Redis. Однако в некоторых языках (например, Golang) можно интегрироваться самому.</p>
11 <h3>Простая реализация</h3>
11 <h3>Простая реализация</h3>
12 <p>Рассмотрим простую реализацию очереди в Redis на примере оформления заказа в интернет магазине. При оформлении заказ сохраняется в БД синхронно, после этого кладется сообщение в 2 очереди для отправки электронного письма и передачи заказа в сервис обработки. Каждая очередь является списком (Lists). Чтобы положить сообщение в конец очереди, используется команда rpush. С другой стороны сервисы-обработчики слушают очередь командой blpop.</p>
12 <p>Рассмотрим простую реализацию очереди в Redis на примере оформления заказа в интернет магазине. При оформлении заказ сохраняется в БД синхронно, после этого кладется сообщение в 2 очереди для отправки электронного письма и передачи заказа в сервис обработки. Каждая очередь является списком (Lists). Чтобы положить сообщение в конец очереди, используется команда rpush. С другой стороны сервисы-обработчики слушают очередь командой blpop.</p>
13 <p>Допустим, пользователь с ID 33 оставил заказ. В этом случае будут выполнены следующие команды:</p>
13 <p>Допустим, пользователь с ID 33 оставил заказ. В этом случае будут выполнены следующие команды:</p>
14 <p>Проверим, что сообщения успешно сохранились в списках:</p>
14 <p>Проверим, что сообщения успешно сохранились в списках:</p>
15 <p>Сервисы обработчики слушают очередь с помощью команды blpop key [key ...] timeout:</p>
15 <p>Сервисы обработчики слушают очередь с помощью команды blpop key [key ...] timeout:</p>
16 <p>Команда blpop - это блокирующее чтение списка. Если список пуст, то выполнение программы блокируется до наступления таймаута, указанного в последнем аргументе в секундах. Если в списке есть элементы, то команда удалит самый левый элемент списка и вернет его. Удаление и чтение происходит атомарно, то есть при нескольких одновременных запросах не может быть ситуации, когда один элемент прочитался более 1 раза.</p>
16 <p>Команда blpop - это блокирующее чтение списка. Если список пуст, то выполнение программы блокируется до наступления таймаута, указанного в последнем аргументе в секундах. Если в списке есть элементы, то команда удалит самый левый элемент списка и вернет его. Удаление и чтение происходит атомарно, то есть при нескольких одновременных запросах не может быть ситуации, когда один элемент прочитался более 1 раза.</p>
17 <p>В такой схеме есть значимый изъян. Если сервис обработчик достанет сообщение из очереди и упадет, не до конца его обработав, то сообщение потеряется навсегда. Надеяться, что система всегда будет работать как задумано (happy path), не стоит.</p>
17 <p>В такой схеме есть значимый изъян. Если сервис обработчик достанет сообщение из очереди и упадет, не до конца его обработав, то сообщение потеряется навсегда. Надеяться, что система всегда будет работать как задумано (happy path), не стоит.</p>
18 <h3>Надежные очереди в Redis</h3>
18 <h3>Надежные очереди в Redis</h3>
19 <p>Успешно обработать ситуации, когда сервис не до конца обрабатывает сообщение, можно с помощью команды blmove source destination LEFT|RIGHT LEFT|RIGHT timeout. Эта команда также блокирует выполнение при пустом списке. Если в списке есть элементы, то самый левый элемент списка возвращается, удаляется из списка<em>source</em>и сохраняется в запасном списке<em>destination</em>.</p>
19 <p>Успешно обработать ситуации, когда сервис не до конца обрабатывает сообщение, можно с помощью команды blmove source destination LEFT|RIGHT LEFT|RIGHT timeout. Эта команда также блокирует выполнение при пустом списке. Если в списке есть элементы, то самый левый элемент списка возвращается, удаляется из списка<em>source</em>и сохраняется в запасном списке<em>destination</em>.</p>
20 <p>После того как сообщение будет обработано, оно не удалится автоматически из<em>destination</em>. Ответственность за удаление элемента лежит на компоненте системы, который обрабатывает сообщения из этого списка. Это может быть:</p>
20 <p>После того как сообщение будет обработано, оно не удалится автоматически из<em>destination</em>. Ответственность за удаление элемента лежит на компоненте системы, который обрабатывает сообщения из этого списка. Это может быть:</p>
21 <ul><li><strong>Обработчик сообщений</strong>: Если у вас есть отдельный процесс или сервис, который читает сообщения из destination, он должен удалять элемент после успешной обработки.</li>
21 <ul><li><strong>Обработчик сообщений</strong>: Если у вас есть отдельный процесс или сервис, который читает сообщения из destination, он должен удалять элемент после успешной обработки.</li>
22 <li><strong>Автоматический обработчик</strong>: Если реализован автоматический механизм обработки, он также должен включать логику для удаления успешно обработанных сообщений.</li>
22 <li><strong>Автоматический обработчик</strong>: Если реализован автоматический механизм обработки, он также должен включать логику для удаления успешно обработанных сообщений.</li>
23 <li><strong>Ручное вмешательство</strong>: В случаях, когда обработка требует ручного контроля (например, для анализа ошибок), администратор или оператор может вручную удалять элементы из destination после их проверки.</li>
23 <li><strong>Ручное вмешательство</strong>: В случаях, когда обработка требует ручного контроля (например, для анализа ошибок), администратор или оператор может вручную удалять элементы из destination после их проверки.</li>
24 </ul><p>Также можно добавить дополнительный клиент для обработки списка<em>destination</em>. Как именно его обрабатывать, решается в каждом проекте по-своему. Возможные подходы включают:</p>
24 </ul><p>Также можно добавить дополнительный клиент для обработки списка<em>destination</em>. Как именно его обрабатывать, решается в каждом проекте по-своему. Возможные подходы включают:</p>
25 <ul><li>Ручной просмотр ошибок и повторная отправка на обработку.</li>
25 <ul><li>Ручной просмотр ошибок и повторная отправка на обработку.</li>
26 <li>Автоматический обработчик, который будет перемещать данные из запасной очереди обратно в основную через определенные промежутки времени.</li>
26 <li>Автоматический обработчик, который будет перемещать данные из запасной очереди обратно в основную через определенные промежутки времени.</li>
27 </ul><p>Эти подходы помогут обеспечить надежность и устойчивость системы обработки сообщений.</p>
27 </ul><p>Эти подходы помогут обеспечить надежность и устойчивость системы обработки сообщений.</p>
28 <h3>Ошибки инфраструктуры</h3>
28 <h3>Ошибки инфраструктуры</h3>
29 <p>Отметим один важный момент. Если по какой-то причине упадет сам Redis сервер, то данные за последние несколько секунд могут быть потеряны. Этого можно избежать специальной настройкой персистентности данных, которая понизит производительность. Всегда присутствует компромисс между пропускной способностью и надежностью хранения. Детальное изучение настроек Redis-сервера выходит за рамки данного курса, но при необходимости нужную информацию можно найти в интернете.</p>
29 <p>Отметим один важный момент. Если по какой-то причине упадет сам Redis сервер, то данные за последние несколько секунд могут быть потеряны. Этого можно избежать специальной настройкой персистентности данных, которая понизит производительность. Всегда присутствует компромисс между пропускной способностью и надежностью хранения. Детальное изучение настроек Redis-сервера выходит за рамки данного курса, но при необходимости нужную информацию можно найти в интернете.</p>
30 <h2>Резюме</h2>
30 <h2>Резюме</h2>
31 <ul><li>Все, что может быть выполнено асинхронно, нужно выносить в обработку через очереди.</li>
31 <ul><li>Все, что может быть выполнено асинхронно, нужно выносить в обработку через очереди.</li>
32 <li>Redis - простой брокер очередей, который покрывает нужды большинства небольших проектов.</li>
32 <li>Redis - простой брокер очередей, который покрывает нужды большинства небольших проектов.</li>
33 <li>Очереди в Redis реализуются с помощью встроенной структуры данных Lists.</li>
33 <li>Очереди в Redis реализуются с помощью встроенной структуры данных Lists.</li>
34 <li>Запись сообщения в очередь происходит командой rpush (или lpush, если очередь развернута).</li>
34 <li>Запись сообщения в очередь происходит командой rpush (или lpush, если очередь развернута).</li>
35 <li>Сервисы-обработчики читают очереди командой blmove.</li>
35 <li>Сервисы-обработчики читают очереди командой blmove.</li>
36 <li>Не всегда обработка происходит успешно, поэтому важно использовать запасные очереди, чтобы не терять сообщения.</li>
36 <li>Не всегда обработка происходит успешно, поэтому важно использовать запасные очереди, чтобы не терять сообщения.</li>
37 </ul>
37 </ul>