Artemis集群允许将Artemis服务组组合在一起,便于共享消息的负载处理。集群中每个活动节点都是一个活动的Artemis服务,它管理自己的消息并处理自己的连接。集群中的每个节点都通过broker.xml配置文件来声明与其他节点关系,从而形成集群。当一个集群节点连接到另一节点时,通过在内部创建一个core bridge来建立他们之间的连接。不需要为每个节点都声明一个明确的bridge,这些集群连接允许消息在集群的节点之间流动并且进行负载均衡。
节点可以使用不同的网络拓扑来连接到一起形成一个集群。通过集群来进行客户端负载均衡,并且考虑消息重新分发机制。
注意,通常情况下配置了集群节点,通常只需将配置复制到其他节点即可生成对称集群。但是在复制配置时需注意,不要将Artemis数据(即绑定,日志和大消息目录)进行复制,当第一次启动节点并初始化日志文件时,将会在日志目录中存储一个特殊标识符。此ID必须在集群节点之间是唯一的,否则集群将无法正确形成。
服务器能通过服务发现机制传播其连接细节,称之为拓扑:
通常在建立集群拓扑时,需要一种方式来建立最初的第一个连接,可以使用如基于UDP和JGROUP之类的动态发现或者通过提供初始连接器列表来完成。
1.动态发现
使用UDP多播或者JGroups来广播服务器连接设置。
1.1 广播组
广播组是服务器通过网络广播连接器信息,连接器定义客户端(或其他服务器)可以与服务器建立连接的方式。关播组使用一组连接器对,每个连接器对包含一个活动服务和备份服务(如果存在)并且在网络上广播它们。通过在集群上配置(broker.xml)基于UDP或JGroup的方式来广播连接器对信息。
每个Artemis服务器可以有许多广播组,所有广播组都定义在broadcast-groups元素中。如下通过UDP的广播组配置例子如下:
<broadcast-groups>
<broadcast-group name="my-broadcast-group">
<local-bind-address>172.16.9.3</local-bind-address>
<local-bind-port>5432</local-bind-port>
<group-address>231.7.7.7</group-address>
<group-port>9876</group-port>
<broadcast-period>2000</broadcast-period>
<connector-ref>netty-connector</connector-ref>
</broadcast-group>
</broadcast-groups>
如下通过JGroups的广播组配置例子如下:
<broadcast-groups>
<broadcast-group name="my-broadcast-group">
<jgroups-file>test-jgroups-file_ping.xml</jgroups-file>
<jgroups-channel>activemq_broadcast_channel</jgroups-channel>
<broadcast-period>2000</broadcast-period>
<connector-ref>netty-connector</connector-ref>
</broadcast-group>
</broadcast-groups>
为了能够使用JGroups进行广播,必须指定两个属性:jgroups-file和jgroups-channel:
JGroups配置文件例子如下:
<config xmlns="urn:org:jgroups"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="urn:org:jgroups http://www.jgroups.org/schema/JGroups-3.0.xsd">
<TCP loopback="true"
recv_buf_size="20000000"
send_buf_size="640000"
discard_incompatible_packets="true"
max_bundle_size="64000"
max_bundle_timeout="30"
enable_bundling="true"
use_send_queues="false"
sock_conn_timeout="300"
thread_pool.enabled="true"
thread_pool.min_threads="1"
thread_pool.max_threads="10"
thread_pool.keep_alive_time="5000"
thread_pool.queue_enabled="false"
thread_pool.queue_max_size="100"
thread_pool.rejection_policy="run"
oob_thread_pool.enabled="true"
oob_thread_pool.min_threads="1"
oob_thread_pool.max_threads="8"
oob_thread_pool.keep_alive_time="5000"
oob_thread_pool.queue_enabled="false"
oob_thread_pool.queue_max_size="100"
oob_thread_pool.rejection_policy="run"/>
<FILE_PING location="../file.ping.dir"/>
<MERGE2 max_interval="30000"
min_interval="10000"/>
<FD_SOCK/>
<FD timeout="10000" max_tries="5" />
<VERIFY_SUSPECT timeout="1500" />
<BARRIER />
<pbcast.NAKACK
use_mcast_xmit="false"
retransmit_timeout="300,600,1200,2400,4800"
discard_delivered_msgs="true"/>
<UNICAST timeout="300,600,1200" />
<pbcast.STABLE stability_delay="1000" desired_avg_gossip="50000"
max_bytes="400000"/>
<pbcast.GMS print_local_addr="true" join_timeout="3000"
view_bundling="true"/>
<FC max_credits="2000000"
min_threshold="0.10"/>
<FRAG2 frag_size="60000" />
<pbcast.STATE_TRANSFER/>
<pbcast.FLUSH timeout="0"/>
</config>
如上配置定义了jgroups协议栈。
1.2 发现组
广播组用于定义如何从服务器中广播连接器信息。发现组定义如何从广播端点接受连接器信息。发现组维护一个连接器对列表,每个连接器对由不同的服务器广播而来。当其从特定的服务器接受到广播端点上的广播时,更新其在该服务器列表中的条目。如果一段时间内没有接收到特定服务器的广播,将从列表中删除该服务器的条目。
Artemis中有两个地址使用发现组:
注意:必须为每个发现组配置以其广播对应项匹配的广播端点。例如,如果使用UDP配置广播,则发现组必须使用和UDP相同的多播地址。
1.2.1 在服务端定义发现组
UDP发现组在配置文件broker.xml中定义,必须在discovery-groups元素内定义所有发现组,Artemis可以定义多个发现组。例子如下:
<discovery-groups>
<discovery-group name="my-discovery-group">
<local-bind-address>172.16.9.7</local-bind-address>
<group-address>231.7.7.7</group-address>
<group-port>9876</group-port>
<refresh-timeout>10000</refresh-timeout>
</discovery-group>
</discovery-groups>
JGroups发现组配置例子如下:
<discovery-groups>
<discovery-group name="my-broadcast-group">
<jgroups-file>test-jgroups-file_ping.xml</jgroups-file>
<jgroups-channel>activemq_broadcast_channel</jgroups-channel>
<refresh-timeout>10000</refresh-timeout>
</discovery-group>
</discovery-groups>
JGroups文件配置例子如下:
1.2.2 客户端的发现组
通过配置Artemis客户端来发现其可连接的服务器列表,执行此操作的方式取决于使用的JMS还是CORE客户端。
使用udp URL组合和host:port组合来匹配服务器相应的广播组的group-address和group-port,例子如下:
udp://231.7.7.7:9876
使用此URI通过监听发现组配置自动的多播地址来创建连接,通过发现组获取的服务器列表进行负载均衡。refreshTimeout参数可以直接在URI中设定。
还有一个initialWaitTimeout的URL参数,如果在创建后立即使用JMS连接工厂或者core会话工厂,则可能没有足够的时间从集群中的所有节点接收广播,在第一次使用时确保在创建第一连接之前等待这么长时间,此参数默认值为10000ms
1.2.3 使用静态连接器的发现组
有时在使用的网络上无法使用UDP,这种情况下,可以配置一个初始的连接列表,列表中的服务器至少是有一个可用的。
此并不意味着必须知道所有服务器托管地址,可以配置这些服务器以使用可靠的服务器用于连接,一旦连接建立其连接细节将通过所连接的服务器进行传播。
可以使用带()的语法在连接URI中指定用于初始连接尝试的服务器列表,例如:
(tcp://myhost:61616,tcp://myhost2:61616)?reconnectAttempts=5
2.集群连接配置
集群连接始终在broker.xml配置文件中的cluster-connection元素中定义,每个Artemis服务器可以定义0个或多个集群连接,例子如下:
<cluster-connections>
<cluster-connection name="my-cluster">
<address></address>
<connector-ref>netty-connector</connector-ref>
<check-period>1000</check-period>
<connection-ttl>5000</connection-ttl>
<min-large-message-size>50000</min-large-message-size>
<call-timeout>5000</call-timeout>
<retry-interval>500</retry-interval>
<retry-interval-multiplier>1.0</retry-interval-multiplier>
<max-retry-interval>5000</max-retry-interval>
<initial-connect-attempts>-1</initial-connect-attempts>
<reconnect-attempts>-1</reconnect-attempts>
<use-duplicate-detection>true</use-duplicate-detection>
<message-load-balancing>ON_DEMAND</message-load-balancing>
<max-hops>1</max-hops>
<confirmation-window-size>32000</confirmation-window-size>
<call-failover-timeout>30000</call-failover-timeout>
<notification-interval>1000</notification-interval>
<notification-attempts>2</notification-attempts>
<discovery-group-ref discovery-group-name="my-discovery-group"/>
</cluster-connection>
</cluster-connections>
'eu'匹配以'eu'开头的所有地址
'!eu'匹配所有地址,除了那些以'eu'开头的地址
'eu.uk,eu.de'匹配以'eu.uk'或'eu.de'开头的所有地址
'eu,!eu.uk'匹配所有以'eu'开头的地址,而不是那些以'eu.uk'开头的地址
注意:地址排除始终优先于地址包含。群集连接上的地址匹配不支持通配符匹配。
如果将此设置为OFF,则消息将永远不会转发到集群中的另一个节点。
STRICT,则即使集群的其他节点上相同队列可能根本没有消费者,或者他们有消费者但是没有匹配的消息,每个消息都会循环传入。如果其他节点上没有相同名称的队列,则Artemis不会将消息转发到其他节点。即使此参数设置为STRICT也是如此。
ON_DEMAND,Artemis当集群的其他节点中的有监听这个地址的队列并且有消费者,或者这些消费者有消息过滤器消息至少被一个消息过滤器匹配,消息将会转发到这个节点。
如果希望在集群中使用静态的服务器列表,例子如下:
<cluster-connection name="my-cluster">
...
<static-connectors>
<connector-ref>server0-connector</connector-ref>
<connector-ref>server1-connector</connector-ref>
</static-connectors>
</cluster-connection>
如上定义了两台服务器,我们保证至少有一台服务器可用,集群中可能有更多的服务器,但是一旦和其中一个连接器初始连接完成,就可以通过其进行发现。
3.集群用户凭证
当在集群之间创建集群连接时,Artemis需要在broker.xml中定义集群的用户和密码,例子如下:
<cluster-user>ACTIVEMQ.CLUSTER.ADMIN.USER</cluster-user>
<cluster-password>CHANGE ME!!</cluster-password>
注意必须更改这些默认值,否则远程客户端将能够使用默认值建立与服务器的集群连接。如果没有更改默认值,Artemis将检测到,并在每次启动时会发出告警。
4.服务端消息负载均衡
如果在集群节点之间定义了集群连接,Artemis将对到达特定节点的消息进行负载均衡。举个例子,在对称的集群中有A、B、C、D四个节点。在集群中每个节点创建了一个名为OrderQueue的队列。客户端Ca连接到节点A,发送消息到服务器。同时有消息处理程序Pa,Pb,Pc,Pd分别连接到A、B、C、D节点。如果节点A没有定义集群连接时,那么消息到达A节点时,消息全部被传送到A节点的OrderQueue队列中,只会被Pa处理。如果在节点A上定义了集群连接,那么当消息到达节点A时,并不是所有消息都进入本地OrderQueue队列中,消息在集群所有节点中以循环方式分布,消息从接受节点转发到集群的其他节点。这些都在服务器中完成。
Artemis集群可以配置为始终以循环方式负载消息,而不管其他节点上是否有任何匹配的消费者。也可以配置为仅分配到有对应消费者的节点。
5.客户端负载均衡
使用Artemis客户端负载均衡,随后使用单个会话工厂创建会话可以连接到集群的不同节点。这允许会话平滑的分布在集群的各个节点上,而不是集聚在一个特定的节点上。
客户端使用负载均衡策略是可配置的,Artemis提供了四种负载均衡策略,也可以实现自己的策略。既有的策略如下:
可以通过实现如下接口来实现自己的负载均衡策略:
org.apache.activemq.artemis.api.core.client.loadbalance.ConnectionLoadBalancingPolicy
无论使用的JMS还是CORE API指定要使用的负载均衡策略都不同,如果未指定则使用默认的轮询策略。
可以通过在URL中使用参数connectionLoadBalancingPolicyClassName来指定负载均衡策略:
tcp://localhost:61616?connectionLoadBalancingPolicyClassName=org.apache.activemq.artemis.api.core.client.loadbalance.RandomConnectionLoadBalancingPolicy
客户端决定使用哪个服务器集合用于负载均衡,可以通过如下两种方式之一进行指定:
6.明确指定集群成员
有时候会需要更明确地显示定义集群,控制集群中那些服务器互相连接。这通常用于非对称集群像如链集群或环集群。这些只能使用静态连接器列表来完成的集群拓扑结构,配置例子如下:
<cluster-connection name="my-cluster">
<address/>
<connector-ref>netty-connector</connector-ref>
<retry-interval>500</retry-interval>
<use-duplicate-detection>true</use-duplicate-detection>
<message-load-balancing>STRICT</message-load-balancing>
<max-hops>1</max-hops>
<static-connectors allow-direct-connections-only="true">
<connector-ref>server1-connector</connector-ref>
</static-connectors>
</cluster-connection>
在如上例子中,设置了属性allow-direct-connections-only,意味着此服务器只可以连接到集群中唯一服务器server1-connector上,此意味着可以显示创建所需的任何集群拓扑。
7.消息重新分发
之前我们了解了服务器端消息负载均衡如何在集群中循环消息。如果message-load-balancing配置为OFF或者ON_DEMAND则消息将不会投递到没有匹配消费者的节点。这样可以确保消息不会投递到没有消费者使用的队列。但是有一直情况无法解决,如果队列中的消费者在消息发送到节点后关闭,这样会导致消息不会被消费导致消息缺失的情况。
这种情况就会用到消息重新分发了,通过消息重新分发,Artemis可以配置消息自动重新分发,这要没有消费者队列中的消息将会重新分发到有匹配消息者的集群中的其他节点,要启用此功能,message-load-balancing必须为ON_DEMAND。
消息重新分发可以配置为在队列中最后一个消费者关闭后立即启动,或者在关闭队列中最后一个消费者等待可配置时延后再重新分发。默认情况下禁用重新分发。可以为每个基础地址设置重新分发延迟,如下broker.xml中的配置例子,显示了如何为一组队列启用消息重新分发:
<address-settings>
<address-setting match="#">
<redistribution-delay>0</redistribution-delay>
</address-setting>
</address-settings>
如上配置,对于绑定到任何地址的任何队列,将重新分发配置延迟设置为0,即实现所有地址的及时(无延迟)重新分发。redistribution-delay定义了重新分发的延迟时间(单位ms)。值-1表示永远不会重新分发,默认值为-1。
在重新分发之前引入延迟通常是有意义的,因为消费者关闭但是在同一队列上快速创建另一个消费者是很常见的。这种情形下不希望立即重新分发,因为新消费者很快到达。
8.集群拓扑结构
8.1 对称集群
对称集群是常见的集群拓扑。使用对称集群,集群中的每个节点都连接到集群中的每个其他节点。为了形成对称集群,集群中每个节点都定义了一个集群连接,其属性max-hops设置为1。集群连接将使用服务器发现,便于了解到其应该连接到集群中的其他服务器,如果UDP在网络中不可以,则可以在群集连接中也可以显式地定义每个目标服务器。
使用对称集群,每个节点都知道所有其他节点上存在的所有队列以及它们的消费者。有了这些信息,其可以确定如何在节点之间进行负载均衡和消息重新分发。
8.2 链集群
对于链集群,集群中的每个节点都不直接连接到集群中的每个节点,而是通过节点链,链的每一端都有一个节点,所有其他节点只连接到前一个节点和下一个节点。
举个例子,由节点A,B,C组成三节点链,节点A托管在网络中,并且有许多生产者客户端发送消息给它。假设消费者客户端需要托管在不同的网络中,并且网络智能通过第三个节点完了访问。在此设置中,节点B作为调解器,其上没有生产者和消费者。到达节点A的任何消息都将被转发到节点B,节点B又将它们转发到节点C,在C节点中被消费。
通过节点A定义连接到节点B,节点B定义连接到节点C的集群连接。这种情况下值希望集群连接在一个方向上,消息移动方向A->B->C,而不C->B->A这样移动消息。对于此拓扑结构,我们将max-hops设置为2,值为2时节点C上存在的队列和消费者信息将从节点C传到节点B再到节点A。然后节点A将知道吧消息分发到节点B,即使节点B没有消费者,它会自动消息的下一跳节点为节点C,节点C中有消费者。
9.缩减集群规模
Artemis支持在缩减集群而不会丢失任何消息(即使对非持久化消息)。集群的大小可能在某些环境下(比如,云)会相对频繁的改变。当扩展集群(即添加节点)时,不存在再消息丢失的风险,但是缩小集群(即,移除节点)时,除非代理将消息发送到集群中的另一个节点,否则这个节点上的消息将被丢失。
Artemis可以配置转移消息的操作。启用此行为最简单的方式是设置scale-down。如果服务器在集群中在为true的情况下,当服务器正常关闭(正常停止而不是崩溃)时,它将在集群中找到另一个节点并将其所有消息(持久和非持久)发送到该节点。消息按顺序处理并转到另一个节点上相应队列的后面(就像消息是第一次从外部客户端发送一样)。
如果需要控制消息的去向,可以指定scale-down-group-name,将消息仅发送到集群中另一个节点,该节点为正在关闭的服务器scale-down-group-name参数指定的节点。
注意,如果集群节点与不同的scale-down-group-name值组合在一起,如果单组中所有节点都被关闭,那么来自该节点/组的消息将丢失。
如果服务器使用多个群集连接,则使用scale-down-clustername来标识应该用于缩减的群集连接的名称。
注:此系列文章为Apache Artemis V2.6.2官方使用文档的简要翻译文档(非完全按照官方文档排版进行翻译,有删减),个人能力有限如有错误请谅解。源文档地址:http://activemq.apache.org/artemis/docs/latest/index.html