Настройка репликации хранилища ARTA SYNERGY при использовании сервера приложении JBoss AS7

Введение

Общая реализация механизма репликации, описанная в документе AIREP, остаётся прежней; в данном документе будет описаны особенности настройки репликации применительно к транспорту (МОМ) HornetQ, встроенному в JBoss AS7.

Схема настройки: имеется один центральный экземпляр ARTA SYNERGY (далее Center) и несколько региональных (подчинённых). С центрального экземпляра на подчинённые реплицируется три каталога хранилища в режиме «точка-многоточка» и «master → slave». Реплицируемые каталоги:

  • /company_root/Маркетинговые материалы

  • /company_root/База знаний

  • /company_root/Проекты

В примере показана настройка центрального экземпляра и одного регионального (далее Astana) — при необходимости, по такой же схеме можно настроить ещё несколько региональных экземпляров.

Настройка серверов приложений

Как уже было упомянуто выше, для передачи сообщений репликации мы будем использовать встроенный в JBoss AS7 транспорт HornetQ, а именно, такие его возможности, как:

  • Core Bridges (вместо мостов JMS, так как они имеют встроенную возможность передоставки при потерях подключения и прочие полезные вещи; кроме этого, они гораздо проще настраиваются)

  • Core Queues — для использования совместно с Core Bridges

  • JMS Queues — для использования с ARTA SYNERGY, так как реализация механизма репликации, как заявлено в [AIREP], может работать с любой JMS-совместимой МОМ

  • Netty Connectors — для использования в соединении очередей мостами через TCP/IP-сеть

Предварительные настройки

Для начала нам необходимо дать возможность серверам Center и Astana соединяться друг с другом, для этого нужно добавить соответствующий outbound-socket-binding в секции server → socket-binding-group:

  • На сервере Center:

<outbound-socket-binding name="messaging-outbound-astana-binding"> 
    <remote-destination host="192.168.1.144" port="5445"/> 
</outbound-socket-binding>
  • На сервере Astana:

<outbound-socket-binding name="messaging-outbound-center-binding"> 
    <remote-destination host="192.168.1.160" port="5445"/> 
</outbound-socket-binding>

Кроме этого, надо отключить аутентификацию в кластере HornetQ (считаем, что наша сеть доверенная)

  • На обоих серверах в секции server → profile → subsystem xmlns="urn:jboss:domain:messaging:1.2" → hornetq-server добавляем:

<security-enabled>false</security-enabled>

Добавляем коннекторы, используя свежесозданные socket-binding-и, в секции server → profile → subsystem xmlns=urn:jboss:domain:messaging:1.2 → hornetq-server → connectors

  • На сервере Center:

<netty-connector name="netty-astana" socket-binding="messaging-outbound-astana-binding"/>
  • На сервере Astana:

<netty-connector name="netty-center" socket-binding="messaging-outbound-center-binding"/>

Очереди

Так как Core Bridges не поддерживают JMS Topics, для реализации передачи типа «точка-многоточка» нам придётся использовать, мягко говоря, нестандартный подход для решения этой задачи, а именно, на сервере Center:

  • Создаётся non-durable JMS-очередь (в нашем случае, 3 штуки, для каждой реплицируемой

области), которая будет непосредственным приёмником сообщений от ARTA SYNERGY (в секции server → profile → subsystem xmlns=urn:jboss:domain:messaging:1.2 → hornetq-server →jms-destinations)

<jms-queue name="CenterBzRep"> 
    <entry name="queue/CenterBzRep"/> 
    <entry name="java:global/queue/CenterBzRep"/> 
    <entry name="java:jboss/exported/jms/queue/CenterBzRep"/> 
    <durable>false</durable> 
</jms-queue> 
<jms-queue name="CenterMmRep"> 
    <entry name="queue/CenterMmRep"/> 
    <entry name="java:global/queue/CenterMmRep"/> 
    <entry name="java:jboss/exported/jms/queue/CenterMmRep"/> 
    <durable>false</durable> 
</jms-queue> 
<jms-queue name="CenterProjectsRep"> 
    <entry name="queue/CenterProjectsRep"/> 
    <entry name="java:global/queue/CenterProjectsRep"/> 
    <entry name="java:jboss/exported/jms/queue/CenterProjectsRep"/> 
    <durable>false</durable> 
</jms-queue> 

Обратите внимание, что у этой очереди добавляется ещё одна JNDI Name Entry, с префиксом java:global. Именно это имя мы будем использовать далее при настройке ARTA SYNERGY

  • Создаются соответствующие им Core-очереди — на самом деле, эти записи описывают те же самые очереди, что и в предыдущем пункте (в секции server → profile → subsystem xmlns=urn:jboss:domain:messaging:1.2 → hornetq-server → jms-destinations):

<queue name="jms.queue.CenterBzRep"> 
    <address>jms.queue.CenterBzRep</address> 
    <durable>false</durable> 
</queue> 
<queue name="jms.queue.CenterMmRep"> 
    <address>jms.queue.CenterMmRep</address> 
    <durable>false</durable> 
</queue> 
<queue name="jms.queue.CenterProjectsRep"> 
    <address>jms.queue.CenterProjectsRep</address> 
    <durable>false</durable> 
</queue> 

Затем для каждой реплицируемой области (M, в нашем случае M=3) и каждого подчинённого сервера (N, в нашем случае N=1) создаём соответствующую Core-очередь (всего должно получиться MxN, в нашем случае 3 очереди), используя такой трюк: задаём для каждой очереди, соответствующей реплицируемой области, не уникальный адрес, а адрес соответствующей JMS-очереди (он формируется как jms.queue.Name, где Name — атрибут name JMS-очереди) — в секции server → profile → subsystem xmlns=urn:jboss:domain:messaging:1.2→ hornetq-server → core-queues:

<queue name="jms.queue.CenterBzRepAstana"> 
    <address>jms.queue.CenterBzRep</address> 
    <filter string="airClientId IN ('Astana', 'ALL')"/> 
    <durable>true</durable> 
</queue> 
<queue name="jms.queue.CenterMmRepAstana"> 
    <address>jms.queue.CenterMmRep</address> 
    <filter string="airClientId IN ('Astana', 'ALL')"/> 
    <durable>true</durable> 
</queue> 
<queue name="jms.queue.CenterProjectsRepAstana"> 
    <address>jms.queue.CenterProjectsRep</address> 
    <filter string="airClientId IN ('Astana', 'ALL')"/> 
    <durable>true</durable> 
</queue> 

Таким образом, сообщения, отправленные по указанном адресу, попадут в MxN+M Core-очереди — очереди, соответствующие JMS-очередям, будут использованы как ретрансляторы сообщений в Per-Replica-Per-Client-очереди. Эти последние очереди будут использованы непосредственно в мостах, доставляющих сообщения до таких же удалённых очередей, поэтому они durable. Обратите внимание, что для каждой такой очереди указан фильтр по ClientId — это идентификатор подчинённого сервера. В итоге практически всё готово, однако остался вопрос о том, куда будут деваться сообщения из самих очередей-ретрансляторов. Для этого мы создаём специальную «/dev/null» Core и JMS-очередь; Core-очередь для того, чтобы, используя Core-мост, перемещать в неё сообщения из ретрансляционных очередей, а JMS-обёртку к ней — чтобы задействовать имеющийся в ARTA SYNERGY специальный NullListener JMS-очереди (который просто берет сообщения из очереди и выбрасывает их). Итак, в секции server → profile → subsystem xmlns=urn:jboss:domain:messaging:1.2 → hornetq-server → core-queues:

<queue name="jms.queue.Null"> 
    <address>jms.queue.Null</address> 
    <durable>false</durable> 
</queue> 

И в секции server → profile → subsystem xmlns=urn:jboss:domain:messaging:1.2 → hornetq-server → jms-destinations:

<jms-queue name="Null"> 
    <entry name="queue/Null"/> 
    <entry name="java:jboss/exported/jms/queue/Null"/> 
    <durable>false</durable> 
</jms-queue> 

Кроме этого, проверяем наличие JMS-очереди с именем REQ, которая нам потом потребуется для обратной связи Astana → Center.

Для связи «точка-точка» таких извращений не требуется: одна durable и JMS/Core очередь для одной репликации + соответствующая JMS-очередь на удалённом сервере.

Далее, на сервере Astana всё чуть проще: необходимо добавить M JMS-очередей и одну очередь для связи с очередью REQ на Center (в секции server → profile → subsystem xmlns=urn:jboss:domain:messaging:1.2 → hornetq-server → jms-destinations):

<jms-queue name="CenterBzRepAstana"> 
    <entry name="queue/CenterBzRepAstana"/> 
    <entry name="java:global/queue/CenterBzRepAstana"/> 
    <entry name="java:jboss/exported/jms/queue/CenterBzRepAstana"/> 
    <durable>true</durable> 
</jms-queue> 
<jms-queue name="CenterMmRepAstana"> 
    <entry name="queue/CenterMmRepAstana"/> 
    <entry name="java:global/queue/CenterMmRepAstana"/> 
    <entry name="java:jboss/exported/jms/queue/CenterMmRepAstana"/> 
    <durable>true</durable> 
</jms-queue> 
<jms-queue name="CenterProjectsRepAstana"> 
    <entry name="queue/CenterProjectsRepAstana"/> 
    <entry name="java:global/queue/CenterProjectsRepAstana"/> 
    <entry name="java:jboss/exported/jms/queue/CenterProjectsRepAstana"/> 
    <durable>true</durable> 
</jms-queue> 
<jms-queue name="REQCenter"> 
    <entry name="queue/REQCenter"/> 
    <entry name="java:global/queue/REQCenter"/> 
    <entry name="java:jboss/exported/jms/queue/REQCenter"/> 
    <durable>true</durable> 
</jms-queue> 

Кроме этого, последнюю очередь необходимо также описать и как Core-очередь для использования в Core-мосте (в секции server → profile → subsystem xmlns=urn:jboss:domain:messaging:1.2 → hornetq-server → core-queues):

    <queue name="jms.queue.REQCenter"> 
    <address>jms.queue.REQCenter</address> 
    <durable>true</durable> 
</queue> 

Мосты

Теперь необходимо соединить все соответствующие друг другу очереди мостами. Мосты настраиваются в секции server → profile → subsystem xmlns=urn:jboss:domain:messaging:1.2 → hornetq-server → bridges.

Для сервера Center: * Мосты, соединяющие очереди-ретрансляторы с Null-очередью:

<bridge name="CenterBzRepBridge"> 
    <queue-name>jms.queue.CenterBzRep</queue-name> 
    <forwarding-address>jms.queue.Null</forwarding-address> 
    <reconnect-attempts>-1</reconnect-attempts> 
    <static-connectors> 
        <connector-ref>netty</connector-ref> 
    </static-connectors> 
</bridge> 
<bridge name="CenterMmRepBridge"> 
    <queue-name>jms.queue.CenterMmRep</queue-name> 
    <forwarding-address>jms.queue.Null</forwarding-address> 
    <reconnect-attempts>-1</reconnect-attempts> 
    <static-connectors> 
        <connector-ref>netty</connector-ref> 
    </static-connectors> 
</bridge> 
<bridge name="CenterProjectsRepBridge"> 
    <queue-name>jms.queue.CenterProjectsRep</queue-name> 
    <forwarding-address>jms.queue.Null</forwarding-address> 
    <reconnect-attempts>-1</reconnect-attempts> 
    <static-connectors> 
        <connector-ref>netty</connector-ref> 
    </static-connectors> 
</bridge> 
<bridge name="CenterBzRepAstanaBridge"> 
    <queue-name>jms.queue.CenterBzRepAstana</queue-name> 
    <forwarding-address>jms.queue.CenterBzRepAstana</forwarding-address> 
    <reconnect-attempts>-1</reconnect-attempts> 
    <static-connectors> 
        <connector-ref>netty-astana</connector-ref> 
    </static-connectors> 
</bridge> 
  • И, наконец, мосты, соединяющие локальные Per-Replica-Per-User-очереди с удалёнными. Чтобы не возникало путаницы, названия удалённых очередей у нас такие же, как и названия локальных (см. выше «Очереди», пункт про JMS-очереди на Astana). Обратите внимание на connector-ref:

<bridge name="CenterBzRepAstanaBridge"> 
    <queue-name>jms.queue.CenterBzRepAstana</queue-name> 
    <forwarding-address>jms.queue.CenterBzRepAstana</forwarding-address> 
    <reconnect-attempts>-1</reconnect-attempts> 
    <static-connectors> 
        <connector-ref>netty-astana</connector-ref> 
    </static-connectors> 
</bridge> 
<bridge name="CenterMmRepAstanaBridge"> 
    <queue-name>jms.queue.CenterMmRepAstana</queue-name> 
    <forwarding-address>jms.queue.CenterMmRepAstana</forwarding-address> 
    <reconnect-attempts>-1</reconnect-attempts> 
    <static-connectors> 
        <connector-ref>netty-astana</connector-ref> 
    </static-connectors> 
</bridge> 
<bridge name="CenterProjectsRepAstanaBridge"> 
    <queue-name>jms.queue.CenterProjectsRepAstana</queue-name> 
    <forwarding-address>jms.queue.CenterProjectsRepAstana</forwarding-address> 
    <reconnect-attempts>-1</reconnect-attempts> 
    <static-connectors> 
        <connector-ref>netty-astana</connector-ref> 
    </static-connectors> 
</bridge> 

Для сервера Astana требуется всего один мост, соединяющий очередь REQCenter с очередью REQ на Center:

<bridge name="REQCenterBridge"> 
    <queue-name>jms.queue.REQCenter</queue-name> 
    <forwarding-address>jms.queue.REQ</forwarding-address> 
    <reconnect-attempts>-1</reconnect-attempts> 
    <static-connectors> 
        <connector-ref>netty-center</connector-ref> 
    </static-connectors> 
</bridge> 

Настройки адресов

Для всех очередей, участвующих в механизме репликации, необходимо указать настройку, обеспечивающую доставку постоянную и без задержек. В секции server → profile → subsystem xmlns=urn:jboss:domain:messaging:1.2 → hornetq-server → address-settings:

<address-setting match="jms.queue.Center#"> 
    <redelivery-delay>0</redelivery-delay> 
    <max-delivery-attempts>-1</max-delivery-attempts> 
</address-setting>

(Ранее мы предусмотрительно добавили ко всем именам очередей, которые участвуют в репликации, префикс Center, поэтому мы можем воспользоваться оконечным wild-card-ом «#»).

Настройка ARTA SYNERGY

Способ настройки ARTA SYNERGY для репликации ничем не отличается от кого, который применялся при использовании JBoss AS5.

На сервере Center, файл ${jboss.server.config.dir}ai/rrc.xml:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?> 
<configuration xmlns="http://www.arta.kz/xml/ns/ai" 
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
               xsi:schemaLocation="http://www.arta.kz/xml/ns/ai http://www.arta.kz/xml/ns/ai/rrc.xsd"> 
  <broadcast-interval>60000</broadcast-interval> 
  <!-- Multicast master-to-slave replications --> 
  <replication> 
    <name>CENTERBZREP</name> 
    <path>/company_root/База знаний</path> 
    <!-- Помните про java:global в описании очередей? -->
    <jms-destination>java:global/queue/CenterBzRep</jms-destination> 
  </replication> 
  <replication> 
    <name>CENTERMMREP</name> 
    <path>/company_root/Маркетинговые материалы</path> 
    <jms-destination>java:global/queue/CenterMmRep</jms-destination> 
  </replication> 
  <replication> 
    <name>CENTERPROJECTSREP</name> 
    <path>/company_root/Проекты</path> 
    <jms-destination>java:global/queue/CenterProjectsRep</jms-destination> 
  </replication> 
  <!-- END Multicast master-to-slave replications --> 
</configuration> 

На сервере Astana, файл ${jboss.server.base.dir}/deployments/Synergy.ear/rr.jar/META-INF/ejb-jar.xml

<?xml version="1.0" encoding="UTF-8"?> 
<ejb-jar xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
         version="3.0" 
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd"> 
  <enterprise-beans> 
    <message-driven> 
      <ejb-name>RepositoryExporterSubscriberCenterProjectsRep</ejb-name> 
      <ejb-class>arta.information.replication.subscriber.RepositoryExporterSubscriber</ejb-class> 
      <activation-config> 
        <activation-config-property> 
          <activation-config-property-name>destination</activation-config-property-name> 
          <activation-config-property-value>queue/CenterProjectsRepAstana</activation-config-property-value> 
        </activation-config-property> 
        <activation-config-property> 
          <activation-config-property-name>destinationType</activation-config-property-name> 
          <activation-config-property-value>javax.jms.Queue</activation-config-property-value> 
        </activation-config-property> 
        <activation-config-property> 
          <activation-config-property-name>maxSession</activation-config-property-name> 
          <activation-config-property-value>1</activation-config-property-value> 
        </activation-config-property> 
        <activation-config-property> 
          <activation-config-property-name>useDLQ</activation-config-property-name> 
          <activation-config-property-value>false</activation-config-property-value> 
        </activation-config-property> 
      </activation-config> 
      <env-entry> 
        <env-entry-name>param/dest</env-entry-name> 
        <env-entry-type>java.lang.String</env-entry-type> 
        <env-entry-value>/company_root</env-entry-value> 
      </env-entry> 
      <env-entry> 
        <env-entry-name>param/clientId</env-entry-name> 
        <env-entry-type>java.lang.String</env-entry-type> 
        <env-entry-value>Astana</env-entry-value> 
      </env-entry> 
      <env-entry> 
        <env-entry-name>param/needImport</env-entry-name> 
        <env-entry-type>java.lang.String</env-entry-type> 
        <!-- false — не использовать инициализацию --> 
        <env-entry-value>true</env-entry-value> 
      </env-entry> 
      <message-destination-ref> 
        <message-destination-ref-name>jms/Outbox</message-destination-ref-name> 
        <message-destination-type>javax.jms.Queue</message-destination-type> 
    <mapped-name>java:global/queue/REQCenter</mapped-name> 
      </message-destination-ref> 
    </message-driven> 
  <message-driven> 
      <ejb-name>RepositoryExporterSubscriberCenterBzRep</ejb-name> 
      <ejb-class>arta.information.replication.subscriber.RepositoryExporterSubscriber</ejb-class> 
      <activation-config> 
        <activation-config-property> 
          <activation-config-property-name>destination</activation-config-property-name> 
          <activation-config-property-value>queue/CenterBzRepAstana</activation-config-property-value> 
        </activation-config-property> 
        <activation-config-property> 
          <activation-config-property-name>destinationType</activation-config-property-name> 
          <activation-config-property-value>javax.jms.Queue</activation-config-property-value> 
        </activation-config-property> 
        <activation-config-property> 
          <activation-config-property-name>maxSession</activation-config-property-name> 
          <activation-config-property-value>1</activation-config-property-value> 
        </activation-config-property> 
        <activation-config-property> 
          <activation-config-property-name>useDLQ</activation-config-property-name> 
          <activation-config-property-value>false</activation-config-property-value> 
        </activation-config-property> 
      </activation-config> 
      <env-entry> 
        <env-entry-name>param/dest</env-entry-name> 
        <env-entry-type>java.lang.String</env-entry-type> 
        <env-entry-value>/company_root</env-entry-value> 
      </env-entry> 
      <env-entry> 
        <env-entry-name>param/clientId</env-entry-name> 
        <env-entry-type>java.lang.String</env-entry-type> 
        <env-entry-value>Astana</env-entry-value> 
      </env-entry> 
      <env-entry> 
        <env-entry-name>param/needImport</env-entry-name> 
        <env-entry-type>java.lang.String</env-entry-type> 
        <!-- false — не использовать инициализацию --> 
        <env-entry-value>true</env-entry-value> 
      </env-entry> 
      <message-destination-ref> 
        <message-destination-ref-name>jms/Outbox</message-destination-ref-name> 
        <message-destination-type>javax.jms.Queue</message-destination-type> 
    <mapped-name>java:global/queue/REQCenter</mapped-name> 
      </message-destination-ref> 
    </message-driven> 
  <message-driven> 
      <ejb-name>RepositoryExporterSubscriberCenterMmRep</ejb-name> 
      <ejb-class>arta.information.replication.subscriber.RepositoryExporterSubscriber</ejb-class> 
      <activation-config> 
        <activation-config-property> 
          <activation-config-property-name>destination</activation-config-property-name> 
          <activation-config-property-value>queue/CenterMmRepAstana</activation-config-property-value> 
        </activation-config-property> 
        <activation-config-property> 
          <activation-config-property-name>destinationType</activation-config-property-name> 
          <activation-config-property-value>javax.jms.Queue</activation-config-property-value> 
        </activation-config-property> 
        <activation-config-property> 
          <activation-config-property-name>maxSession</activation-config-property-name> 
          <activation-config-property-value>1</activation-config-property-value> 
        </activation-config-property> 
        <activation-config-property> 
          <activation-config-property-name>useDLQ</activation-config-property-name> 
          <activation-config-property-value>false</activation-config-property-value> 
        </activation-config-property> 
      </activation-config> 
      <env-entry> 
        <env-entry-name>param/dest</env-entry-name> 
        <env-entry-type>java.lang.String</env-entry-type> 
        <env-entry-value>/company_root</env-entry-value> 
      </env-entry> 
      <env-entry> 
        <env-entry-name>param/clientId</env-entry-name> 
        <env-entry-type>java.lang.String</env-entry-type> 
        <env-entry-value>Astana</env-entry-value> 
      </env-entry> 
      <env-entry> 
        <env-entry-name>param/needImport</env-entry-name> 
        <env-entry-type>java.lang.String</env-entry-type> 
        <!-- false — не использовать инициализацию --> 
        <env-entry-value>true</env-entry-value> 
      </env-entry> 
      <message-destination-ref> 
        <message-destination-ref-name>jms/Outbox</message-destination-ref-name> 
        <message-destination-type>javax.jms.Queue</message-destination-type> 
    <mapped-name>java:global/queue/REQCenter</mapped-name> 
      </message-destination-ref> 
    </message-driven> 
  </enterprise-beans> 
</ejb-jar>

Не забудьте добавить архив rr.jar в application.xml Synergy.ear-а.

Настройка multi-master «точка-точка» репликации оставляется в качестве упражнения читателю.

Документы

AIREP: Игорь Жуков , AI Репликация