Несколько правил как организовать точки, очереди и как их правильно именовать, чтобы было удобно.
Каждая очередь должна представлять только один тип заданий. Не следует смешивать различные типы сообщений в одной очереди. И когда это правило соблюдается, мы можем четко назвать очередь с заданием, представленное им.
Избегайте повторной отправки сообщений в очередь. Если вы обнаружите, что ваш подписчик пытается повторно отправить какие-либо сообщения в другие очереди без реальной обработки, скорее всего что-то спроектировано не правильно. Маршрутизация — это ответственность точек обмена, а не очередей.
Поставщики не должны ничего знать об очередях. Одна из основных идей AMQP является разделение ответственности точек и очередей, так что поставщикам не нужно заботиться о том чтобы сообщение дошло до подписчика.
Предположим вы хотите спроектировать точки и очереди для «пользовательских» связанных событий записи. События записи будут запускаться в одном или нескольких приложениях, и эти сообщения будут использоваться некоторыми другими приложениями.
Первый вопрос, который обычно задают — это различные события одного объекта (объект " пользователь" в данном примере), следует ли использовать одну точку обмена для публикации всех трех событий или использовать 3 отдельных точки для каждого события? Или, короче говоря, одна точка обмена, или много?
Прежде чем ответить на этот вопрос, я хочу задать другой вопрос: действительно ли нам нужна отдельная точка для этого случая? Что если мы абстрагируем все 3 типа событий как событие «write», подтипы которого «created», «updated» и «deleted»?
Самое простое решение, создать очередь «user.write», и публиковать все сообщения событий записи пользователя в эту очередь через глобальную точку обмена.
Самое простое решение не может работать, когда есть второе приложение (имеющее другую логику обработки), которое хочет подписаться на любые сообщения, опубликованные в очереди. Когда подписаны несколько приложений, нам, по крайней мере, нужна одна точка с типом «fanout» с привязками к нескольким очередям. Таким образом, сообщения отправляются в точку, и она дублирует сообщения в каждую очередь. Каждая очередь представляет задание обработки для каждого приложения.
Второе решение отлично работает, есл�� каждый подписчик действительно хочет обрабатывать все подтипы «user.write» событий. Например, если приложение подписчика предназначено для простого хранения журнала транзакций.
С другой стороны не очень хорошо, когда некоторые подписчики находятся за пределами вашей организации, и вы хотите уведомить их только о некоторых определенных событиях, например приложение app2 должно получить сообщение о создании пользователя и не должно знать о событиях обновления и удаления.
Чтобы решить проблему выше, мы должны извлечь событие «user.created» из типа «user.write». Точка обмена с типом «topic» может нам помочь. При публикации сообщений будем использовать user.created/user.updated/user.deleted как ключи роутинга в точке, так мы сможем поставить ключ связи «user.*» в очереди «user.write.app1» и ключ связи «user.created» в очереди «user.created.app2».
Тип «topic» точки обмена является более гибким в случае, если потенциально будет больше типов событий. Но если вы четко знаете точное количество событий, вы также можете использовать тип «direct» для повышения производительности.
Возвращаемся к вопросу «одна точка, или много?». Пока все решения используют только одну точку, работает нормально, ничего плохого. В каких ситуациях мы можем нуждаться в нескольких точках?
Давайте рассмотри пример когда помимо созданных, обновленных и удаленных событий, описанных выше, у нас есть другая группа событий: вход и выход — группа событий, описывающих «поведение пользователя», а не «запись данных». Для разных групп событий могут потребоваться совершенно разные стратегии маршрутизации и соглашения о ключах и именах очередей, для этого и нужны отдельные точки обмена.
Вольный перевод статьи RabbitMQ Exchange and Queue Design Trade-off.
| exchange | type | binding_queue | binding_key |
|-------------------------------------------------------|
| user.write | topic | user.created.app2 | user.created |
Понятия
AMQP (Advanced Message Queuing Protocol) — открытый протокол для передачи сообщений между компонентами системы.
Поставщик (Publishers/Producer) — программа, отправляющая сообщения.
Подписчик (Consumer) — программа, принимающая сообщения. Обычно подписчик находится в состоянии ожидания сообщений.
Очередь (Queue) — очередь сообщений.
Точка обмена (Exchange) — изначальная точка обмена очереди, которая занимается маршрутизацией.
Поставщик (Publishers/Producer) — программа, отправляющая сообщения.
Подписчик (Consumer) — программа, принимающая сообщения. Обычно подписчик находится в состоянии ожидания сообщений.
Очередь (Queue) — очередь сообщений.
Точка обмена (Exchange) — изначальная точка обмена очереди, которая занимается маршрутизацией.
Правило 1
Каждая очередь должна представлять только один тип заданий. Не следует смешивать различные типы сообщений в одной очереди. И когда это правило соблюдается, мы можем четко назвать очередь с заданием, представленное им.
Правило 2
Избегайте повторной отправки сообщений в очередь. Если вы обнаружите, что ваш подписчик пытается повторно отправить какие-либо сообщения в другие очереди без реальной обработки, скорее всего что-то спроектировано не правильно. Маршрутизация — это ответственность точек обмена, а не очередей.
Правило 3
Поставщики не должны ничего знать об очередях. Одна из основных идей AMQP является разделение ответственности точек и очередей, так что поставщикам не нужно заботиться о том чтобы сообщение дошло до подписчика.
Примеры и решения
Предположим вы хотите спроектировать точки и очереди для «пользовательских» связанных событий записи. События записи будут запускаться в одном или нескольких приложениях, и эти сообщения будут использоваться некоторыми другими приложениями.
| object | event |
|------------------|
| user | created |
| user | updated |
| user | deleted |
Первый вопрос, который обычно задают — это различные события одного объекта (объект " пользователь" в данном примере), следует ли использовать одну точку обмена для публикации всех трех событий или использовать 3 отдельных точки для каждого события? Или, короче говоря, одна точка обмена, или много?
Прежде чем ответить на этот вопрос, я хочу задать другой вопрос: действительно ли нам нужна отдельная точка для этого случая? Что если мы абстрагируем все 3 типа событий как событие «write», подтипы которого «created», «updated» и «deleted»?
| object | event | sub-type |
|-----------------------------|
| user | write | created |
| user | write | updated |
| user | write | deleted |
Решение 1
Самое простое решение, создать очередь «user.write», и публиковать все сообщения событий записи пользователя в эту очередь через глобальную точку обмена.
Решение 2
Самое простое решение не может работать, когда есть второе приложение (имеющее другую логику обработки), которое хочет подписаться на любые сообщения, опубликованные в очереди. Когда подписаны несколько приложений, нам, по крайней мере, нужна одна точка с типом «fanout» с привязками к нескольким очередям. Таким образом, сообщения отправляются в точку, и она дублирует сообщения в каждую очередь. Каждая очередь представляет задание обработки для каждого приложения.
| queue | subscriber |
|-------------------------------|
| user.write.app1 | app1 |
| user.write.app2 | app2 |
| exchange | type | binding_queue |
|---------------------------------------|
| user.write | fanout | user.write.app1 |
| user.write | fanout | user.write.app2 |
Второе решение отлично работает, есл�� каждый подписчик действительно хочет обрабатывать все подтипы «user.write» событий. Например, если приложение подписчика предназначено для простого хранения журнала транзакций.
С другой стороны не очень хорошо, когда некоторые подписчики находятся за пределами вашей организации, и вы хотите уведомить их только о некоторых определенных событиях, например приложение app2 должно получить сообщение о создании пользователя и не должно знать о событиях обновления и удаления.
Решение 3
Чтобы решить проблему выше, мы должны извлечь событие «user.created» из типа «user.write». Точка обмена с типом «topic» может нам помочь. При публикации сообщений будем использовать user.created/user.updated/user.deleted как ключи роутинга в точке, так мы сможем поставить ключ связи «user.*» в очереди «user.write.app1» и ключ связи «user.created» в очереди «user.created.app2».
| queue | subscriber |
|---------------------------------|
| user.write.app1 | app1 |
| user.created.app2 | app2 |
| exchange | type | binding_queue | binding_key |
|-------------------------------------------------------|
| user.write | topic | user.write.app1 | user.* |
| user.write | topic | user.created.app2 | user.created |
Решение 4
Тип «topic» точки обмена является более гибким в случае, если потенциально будет больше типов событий. Но если вы четко знаете точное количество событий, вы также можете использовать тип «direct» для повышения производительности.
| queue | subscriber |
|---------------------------------|
| user.write.app1 | app1 |
| user.created.app2 | app2 |
| exchange | type | binding_queue | binding_key |
|--------------------------------------------------------|
| user.write | direct | user.write.app1 | user.created |
| user.write | direct | user.write.app1 | user.updated |
| user.write | direct | user.write.app1 | user.deleted |
| user.write | direct | user.created.app2 | user.created |
Возвращаемся к вопросу «одна точка, или много?». Пока все решения используют только одну точку, работает нормально, ничего плохого. В каких ситуациях мы можем нуждаться в нескольких точках?
Решение 5
Давайте рассмотри пример когда помимо созданных, обновленных и удаленных событий, описанных выше, у нас есть другая группа событий: вход и выход — группа событий, описывающих «поведение пользователя», а не «запись данных». Для разных групп событий могут потребоваться совершенно разные стратегии маршрутизации и соглашения о ключах и именах очередей, для этого и нужны отдельные точки обмена.
| queue | subscriber |
|----------------------------------|
| user.write.app1 | app1 |
| user.created.app2 | app2 |
| user.behavior.app3 | app3 |
| exchange | type | binding_queue | binding_key |
|--------------------------------------------------------------|
| user.write | topic | user.write.app1 | user.* |
| user.write | topic | user.created.app2 | user.created |
| user.behavior | topic | user.behavior.app3 | user.* |
Вольный перевод статьи RabbitMQ Exchange and Queue Design Trade-off.
