Статьи

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

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

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

01
02
03
04
05
06
07
08
09
10
11
12
13
<systemUsage>
    <systemUsage>
        <memoryUsage>
            <memoryUsage limit="64 mb"/>
        </memoryUsage>
        <storeUsage>
            <storeUsage limit="100 gb"/>
        </storeUsage>
        <tempUsage>
            <tempUsage limit="50 gb"/>
        </tempUsage>
    </systemUsage>
</systemUsage>

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

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

Когда сообщение приходит к брокеру, оно должно куда-то идти. Вначале он распаковывается по проводам в объект команды ActiveMQMessage типа 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, они все равно будут делегироваться родителю). Чтобы установить предел памяти получателя:

1
2
3
4
5
6
7
<destinationPolicy>
    <policyMap>
        <policyEntries>
            <policyEntry queue=">" memoryLimit="5MB"/>
        </policyEntries>
    </policyMap>
</destinationPolicy>

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

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

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

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

1
2
3
4
5
6
7
<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, и это ожидаемое поведение.

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

Итак… Ранее я говорил, что память назначения использует основную память посредника в качестве родительской, и что подписки не имеют своих собственных счетчиков памяти, они просто используют основную память посредника. Что ж, это верно для случая по умолчанию, но если вы найдете причину, вы можете дополнительно настроить разделение и ограничение памяти. Идея в том, что вы можете разделить основную память брокера на части «Производитель» и «Потребитель».

Партнерская часть будет использоваться для всех вещей, связанных с сообщениями, поступающими к брокеру, поэтому она будет использоваться в местах назначения. Таким образом, это означает, что когда пункт назначения создает свой собственный MemoryUsage, он будет использовать память источника в качестве своего родителя, а память источника будет использовать часть основной памяти брокера.

С другой стороны, потребительская часть будет использоваться для всего, что связано с отправкой сообщений потребителям. Это означает подписку. Вместо подписки, использующей основную память брокера напрямую, он будет использовать потребительскую память, которая будет частью основной памяти. В идеале, часть Consumer и часть Producer будут равны основной памяти всего брокера.

Чтобы разделить память между производителем и потребителем, установите свойство splitSystemUsageForProducersConsumers в главном элементе <broker/> :

1
<broker splitSystemUsageForProducersConsumers='true'>

По умолчанию это разделит использование основной памяти брокера на 60% для производителей и 40% для потребителей. Чтобы настроить это еще дальше, установите в качестве основного элемента посредника ManufacturerSystemUsagePortion и consumerSystemUsagePortion :

1
<broker splitSystemUsageForProducersConsumers='true' producerSystemUsagePortion='70' consumerSystemUsagePortion='30'>

Там у вас есть это. Надеемся, что это проливает некоторый свет на MemoryUsage брокера.

Ссылка: ActiveMQ: Понимание использования памяти от нашего партнера JCG Кристиана Поста в блоге