-
Notifications
You must be signed in to change notification settings - Fork 2
FAQ и советы по отладке
A: Убедитесь в следующих моментах:
- вы зарегистрировали обработчик через
Builder.RegisterHandler
. Если этого не сделать, Messenger не узнает о вашем обработчике, и вы получите предупреждение“no handlers registered for message type”
в логах или ошибку приDispatch
. - проверьте, что тип сообщения, которое вы Dispatch-ите, точно соответствует типу, ожидаемому обработчиком. Например, если обработчик определён для
*MyMessage
, а вы отправляете значениеMyMessage (не указатель)
, то Messenger увидит другой тип. Обычно лучше отправлять указатель на структуру, если обработчик объявлен с указателем (как в примерах выше). - если сообщение отправляется через внешний транспорт (например, RabbitMQ), убедитесь, что вы вызвали
messenger.Run(ctx)
– без запуска, потребители не активируются и сообщения в очередь не будут обработаны. Если Messenger работает в режиме нескольких bus, проверьте, что ваш обработчик либо наdefault_bus
, либо реализуетGetBusName()
правильно, и что сообщение направляется в нужный bus. - проверьте журналы (логгер): встроенный логгер slog на уровне
Debug
может дать подсказки, что происходит с сообщением (например, оно отправлено во внешний транспорт и потому локально не обработано, или не найдены обработчики).
A: В YAML-конфигурации в секции routing нужно использовать имя типа сообщения. Messenger пытается сопоставить эту строку с зарегистрированными типами обработчиков. Если ваш тип находится не в пакете main и несколько пакетов могут иметь одноимённые структуры, необходимо указать полное имя (например, pkg.MessageType). Ошибки сопоставления приводят к сообщению “failed to resolve message type 'X' in routing configuration”
при вызове Build()
. Если вы получили такую ошибку, вы можете:
- проверить орфографию/пространство имён типа в конфиге,
- убедиться, что соответствующий обработчик зарегистрирован до вызова
Build()
, - в крайнем случае – явно зарегистрировать сообщение через
RegisterMessage(&MyMessage{})
(это заполнитTypeResolver
информацией о типе, даже без обработчика). Последний способ полезен для сервисов, которые только отправляют сообщения, но не обрабатывают их – чтобы routing не падал на неизвестном типе.
A: Метод Run(ctx)
будет возвращать ошибку, когда ctx
отменён (например, если вы передали context.Background()
без таймаута и сразу его отменили, или если ctx
был привязан к завершению программы). Обычно Run
запускают в отдельной горутине и используют context.Background()
или другой долгоживущий контекст. Убедитесь, что ctx
не отменяется раньше времени. Например, не делайте так:
ctx, cancel := context.WithCancel(context.Background())
cancel()
messenger.Run(ctx) // сразу же вернётся ctx.Err()
В этом случае Run
выйдет почти мгновенно. Правильно – вызывать Run
и затем отменять контекст при завершении приложения (например, при получении сигнала OS).
A: Если разные сервисы (процессы) должны обмениваться сообщениями через брокер, убедитесь, что:
- они настроены на один и тот же брокер (DSN, exchange, очереди, binding ключи). Проверьте, что exchange.name и ключи binding совпадают. * * в конфигурации транспорта amqp опция
auto_setup
включена (или вы самостоятельно заранее создали exchange/queue). Еслиauto_setup: false
, вам нужно вручную объявить exchange и очереди в RabbitMQ, иначе ничего не привяжется. - проверьте, что тип exchange правильный (например, topic) и binding ключ соответствует routing key отправляемых сообщений.
- убедитесь, что сервис-потребитель действительно запустил
Run
и слушает очередь (при запуске Messenger сauto_setup
иconsumer_pool_size > 0
, вы должны увидеть, что подключились потребители; можно проверить в RabbitMQ Management, что имеются активные consumers).
Q: Как реализован механизм повторных попыток и что делать, если сообщение так и не удалось обработать?
A: Механизм ретраев (retry
) в Messenger работает на уровне транспорта. Встроенный amqp
транспорт реализует интерфейс RetryableTransport
(метод Retry
). Когда отправка сообщения в RabbitMQ завершается ошибкой (например, брокер недоступен), Messenger генерирует событие SendFailedMessageEvent
. В билдере регистрируется слушатель, который перехватывает это событие и решает, делать ли повторную попытку отправки. Этот стандартный слушатель – SendFailedMessageForRetryListener
– использует MultiplierRetryStrategy
: он отсчитывает попытки и задержки между ними. Он вызывает transport.Retry(ctx, env)
для повторной отправки. Повторы продолжаются, пока не превысится max_retries
или пока сообщение не будет успешно отправлено. Если после максимального числа попыток отправка так и не удалась, слушатель проверит, указан ли FailureTransport
. Если да – сообщение будет отправлено в этот резервный транспорт (как правило, это настроенная "dead-letter" очередь). Если failure_transport
не задан, сообщение просто останется неотправленным; в логах вы увидите ошибку отправки на последней попытке. Для обработки таких ситуаций: вы можете настроить failure_transport
в конфиге (и отдельный consumer на эту "отбойную" очередь для ручной обработки или алертов), либо добавить своего слушателя на SendFailedMessageEvent
, который, например, будет логировать или уведомлять разработчиков о проблеме.
A: Да, для этого удобно повысить уровень логирования. Messenger использует log/slog
. По умолчанию slog.Default()
– инфо-уровень. Вы можете настроить свой slog.Logger
с Level=Debug
и передать его в NewBuilder
. Messenger уже содержит некоторые debug-логи: например, EventDispatcher
логирует добавление слушателя и диспетчинг события (на debug), SendMessageMiddleware
логирует, когда не находит транспорты или отправляет сообщение, HandleMessageMiddleware
логирует отсутствие обработчиков и успешную обработку сообщения.
Эти сообщения на уровне Debug
помогут понять, проходит ли сообщение по цепочке, какие транспорты выбираются, вызываются ли слушатели.
Кроме того, вы всегда можете добавить собственные middleware или слушатели для отладки. Например, ваш LoggerMiddleware
(из примера выше) выведет в консоль каждый этап. Или слушатель на SendMessageToTransportsEvent
покажет, что сообщение собирается отправляться и куда. Такие инструменты позволяют просветить "внутренности" Messenger во время исполнения.
A: Messenger предъявляет строгие требования к сигнатурам методов Handle
. Для Handlers: метод должен принимать контекст и сообщение, и возвращать либо error
, либо (что-то, error)
. Если, например, вы случайно написали метод без context.Context
или с pointers не совпали, RegisterHandler выдаст ошибку вроде “Handle method must accept exactly 2 parameters”
или “last return value must be error”
. Исправьте сигнатуру в соответствии с требованиями (см. Быстрый старт). Для Listeners
: убедитесь, что ваша функция/метод принимает 1 или 2 аргумента (Context опционален, событие – обязателен). Например, если вы написали метод Handle()
без параметров или с неправильным типом параметра, Dispatcher
его не вызовет. В логах может быть сообщение “listener does not have Handle method”
. Проверьте внимательнее определение слушателя.
A: Для корректной остановки нужно отменить контекст, переданный в messenger.Run(ctx)
. Когда ctx.Done()
закроется, метод Run
инициирует остановку: он останавливает все транспорты (Manager стопорит их receive-циклы) и выйдет с ctx.Err() == context.Canceled
или другой причиной закрытия. В вашем коде, если вы запустили Run
в горутине, вы можете, например, перехватить сигнал OS (SIGINT/SIGTERM)
, вызвать cancel()
у контекста, затем дождаться, когда Run
завершится (например, через WaitGroup
или простую паузу
). Messenger не хранит неотправленные сообщения на выходе (кроме тех, что уже в транспорте). Если нужны дополнительные действия при завершении (скажем, дождаться завершения всех текущих обработок сообщений), вам, возможно, придется следить за состоянием самостоятельно (Messenger сразу прекращает чтение новых сообщений, но текущие обработки могут ещё выполняться). Как правило, достаточно вызвать cancel
и дать приложению несколько секунд завершиться.