Статьи

ActiveMQ: понимание использования памяти


Как указывают некоторые недавние электронные письма из списка рассылки и много информации, возвращенной из Google, ActiveUQ SystemUsage и, в частности, функциональность MemoryUsage привели некоторых в замешательство.
Я попытаюсь объяснить некоторые детали, касающиеся MemoryUsage, которые могут помочь понять, как это работает. Я не буду StoreUsage и TempUsage мои colleauges были
покрыты тем ,
в некоторой глубине .

Существует раздел конфигурации activemq.xml, который вы можете использовать, чтобы указать ограничения SystemUsage, в частности, память, постоянное хранилище и временное хранилище, которое может использовать брокер. Вот пример со значениями по умолчанию, которые поставляются с ActiveMQ 5.7:

<systemUsage>
    <systemUsage>
        <memoryUsage>
            <memoryUsage limit="64 mb"/>
        </memoryUsage>
        <storeUsage>
            <storeUsage limit="100 gb"/>
        </storeUsage>
        <tempUsage>
            <tempUsage limit="50 gb"/>
        </tempUsage>
    </systemUsage>
</systemUsage>

Использование памяти

MemoryUsage, кажется, вызывает наибольшую путаницу, поэтому здесь я попытаюсь прояснить его внутреннюю работу.

Когда сообщение приходит к брокеру, оно должно куда-то идти. Сначала он размазывается по проводам в командный объект ActiveMQ типа ActiveMQMessage. В данный момент объект явно находится в памяти, но брокер не отслеживает его.

Что подводит нас к нашей первой точке.

MemoryUsage — это всего лишь счетчик байтов, который необходим брокеру и который используется для отслеживания того, сколько памяти нашей JVM используется сообщениями. Это дает брокеру некоторый способ мониторинга и гарантирует, что мы не достигнем наших пределов (подробнее об этом чуть позже). В противном случае мы могли бы принимать сообщения, не зная, где находятся наши ограничения, пока JVM не исчерпает пространство кучи.

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

Опять же, MemoryUsage — это просто объект, который подсчитывает байты сообщений, чтобы узнать, сколько памяти JVM было использовано для хранения сообщений.

Итак, теперь, когда у нас есть общее представление о том, что такое MemoryUsage, давайте подробнее рассмотрим несколько вещей:

  1. Иерархии MemoryUsage (что это за предел памяти назначения, который я могу настроить для записей политики ) ??
  2. Производитель Flow Control
  3. Распределение использования памяти между получателями и подписками (производители и потребители)?

Память главного брокера, память назначения, память подписки

Когда брокер загружается, он создает свой собственный объект SystemUsage (или использует объект, указанный в конфигурации). Как мы знаем, объект SystemUsage имеет связанные с ним MemoryUsage, StoreUsage и TempUsage. Компонент памяти будет известен как Основная память брокера. Это объект использования, который отслеживает общую память (место назначения, подписка и т. Д.).

Получатель, когда он создан, создаст свой собственный объект SystemUsage (который создает свои собственные отдельные объекты Memory, Store и Temp Usage), но его родитель будет установлен как основной объект SystemUsage брокера. Предельные значения могут иметь индивидуально настроенные пределы памяти (но не Store и Temp, они все равно будут делегироваться родителю). Чтобы установить предел памяти получателя:

<destinationPolicy>
    <policyMap>
        <policyEntries>
            <policyEntry queue=">" memoryLimit="5MB"/>
        </policyEntries>
    </policyMap>
</destinationPolicy>

Таким образом, целевые объекты использования могут использоваться для более точного управления MemoryUsage, но он всегда будет координироваться с основной памятью для всех показателей использования. Эту функцию можно использовать для ограничения количества сообщений, которые удерживает пункт назначения, чтобы один пункт назначения не мог истощить другие пункты назначения. Для очередей это также влияет на верхнюю отметку курсора магазина. В очереди есть разные курсоры для постоянных и непостоянных сообщений. Если мы достигнем максимальной отметки (порог предела памяти назначения), больше не будет кэшироваться больше сообщений, готовых к отправке, и непостоянные сообщения могут быть удалены на временный диск по мере необходимости (если StoreCursor будет использовать FilePendingMessageCursor… в противном случае это будет просто использовать VMPendingMessageCursor и не будет очищаться во временное хранилище).

Если вы не укажете ограничение памяти для отдельных назначений, SystemUsage получателя делегирует его родителю (Main SystemUsage) для всех подсчетов использования. Это означает, что он будет эффективно использовать основной SystemUsage брокера для всех подсчетов, связанных с памятью.

Потребительские подписки, с другой стороны, не имеют представления о своих собственных счетчиках SystemUsage или MemoryUsage. Они всегда будут использовать основные объекты SystemUsage брокера. Главное, на что следует обратить внимание, это то, что при использовании FilePendingMessageCursor для подписок (например, для подписки на тему) сообщения не будут выгружаться на диск до тех пор, пока не будет достигнут верхний предел курсора (по умолчанию 70%), но это означает, что 70% основной памяти потребуется. Это может занять некоторое время, и многие сообщения могут быть сохранены в памяти. И если ваша подписка содержит большинство этих сообщений, замена на диск может занять некоторое время. Когда темы рассылают сообщения по одной подписке за раз, если одна подписка останавливается из-за того, что она переписывает свои сообщения на диск,остальная часть подписки, готовая принять сообщение, также будет чувствовать замедление.

Вы можете установить верхнюю отметку курсора для подписок на тему ниже, чем по умолчанию:

<destinationPolicy>
    <policyMap>
        <policyEntries>
            <policyEntry topic="FOO.BAR.>" cursorMemoryHighWaterMark="30" />
        </policyEntries>
    </policyMap>
</destinationPolicy>

Для тех, кто заинтересован… Когда сообщение приходит в пункт назначения, в сообщении устанавливается объект MemoryUsage, чтобы когда Message.incrementReferenceCount () мог увеличивать использование памяти (при первом обращении). Таким образом, это означает, что он учитывается использованием памяти назначения (а также основной памятью, поскольку память назначения также информирует своего родителя, когда изменяется его использование) и продолжает это делать. Единственный раз, когда это изменится, если сообщение будет перенесено на диск. Когда он поменяется местами, его счетчик ссылок будет уменьшен, его использование памяти будет уменьшено, и он потеряет свой объект MemoryUsage, как только он попадет на диск. Итак, когда он вернется к жизни, какой объект MemoryUsage будет связан с ним, и где он будет учитываться? Если он был перенесен в хранилище очереди, когда он восстанавливает,это будет снова связано с использованием памяти назначения. Если он был заменен временным хранилищем в подписке (как в FilePendingMessageCursor), то при восстановлении он НЕ будет связан с использованием памяти назначения больше. Это будет связано с использованием памяти подписки (которая является основной памятью).

Производитель Flow Control

Большой выигрыш в отслеживании памяти, используемой сообщениями, — контроль потока производителя (PFC), PFC включен по умолчанию и в основном замедляет производителей при достижении пределов использования. Это удерживает брокера от превышения лимитов и нехватки ресурсов. Для производителей, отправляющих синхронно или для асинхронных отправлений с указанным окном производителя, при достижении использования системы брокер блокирует этого отдельного производителя, но не блокирует соединение. Вместо этого он временно отложит сообщение, чтобы дождаться появления свободного места. Он отправит обратно ProducerAck только после того, как сообщение будет сохранено. До этого времени клиент должен блокировать свою операцию отправки (которая не будет блокировать само соединение). Клиентские библиотеки ActiveMQ 5.x справятся с этим за вас. Однако, если асинхронная отправка отправляется без окна производителя или если производитель не ведет себя должным образом и игнорирует ProducerAcks,PFC фактически заблокирует все соединение, когда память будет достигнута. Это может привести к тупиковой ситуации, если у вас есть потребители, использующие одно и то же соединение.

Если управление потоком производителя отключено, вам нужно быть немного более осторожным в настройке использования системы. Когда управление потоком производителя отключено, это в основном означает «брокер, вы должны принимать каждое поступающее сообщение, независимо от того, могут ли потребители не отставать». Это может быть использовано для обработки всплесков входящих сообщений в пункт назначения. Если вы когда-либо видели, что использование памяти в ваших журналах значительно превышает установленные вами ограничения, вероятно, у вас отключен PFC, и это ожидаемое поведение.

Расщепление основной памяти брокера

So… I said earlier that a destination’s memory uses the broker’s main memory as a parent, and that subscriptions don’t have their own memory counters, they just use the broker’s main memory. Well this is true in the default case, but if you find a reason, you can further tune how memory is divided and limited. The idea here is you can partition the broker’s main memory into “Producer” and “Consumer” parts.

The Producer part will be used for all things related to messages coming in to the broker, therefore it will be used in destinations. So this means when a destination creates its own MemoryUsage, it will use the Producer memory as its parent, and the Producer memory will use a portion of the broker’s main memory.

On the other hand, the Consumer part will be used for all things related to dispatching messages to consumers. This means subscriptions. Instead of a subscription using the broker’s main memory directly, it will use the Consumer memory which will be a portion of the main memory. Ideally, the Consumer portion and the Producer portion will equal the entire broker’s main memory.

To split the memory between producer and consumer, set the splitSystemUsageForProducersConsumers property on the main <broker/> element:

<broker splitSystemUsageForProducersConsumers="true">

By default this will split the broker’s Main memory usage into 60% for the producers and 40% for the consumers. To tune this even further, set the producerSystemUsagePortion and consumerSystemUsagePortion on the main broker element:

<broker splitSystemUsageForProducersConsumers="true" producerSystemUsagePortion="70" consumerSystemUsagePortion="30">

There you have it. Hopefully this sheds some light into the MemoryUsage of the broker.

The topic is huge, and the tuning options are plenty, so if you have specific questions please ask in the activemq mailing list or leave a comment below.