Wildfly集群实现单消息触发各节点MDB接收的方案咨询
我使用Wildfly 25.0.1.preview,通过JMS Topic发送消息到集群,但每条消息仅随机触发一个节点的MDB接收。集群配置无问题,推测是默认行为,想实现单消息触发集群所有节点MDB接收的需求,也可考虑JMS替代方案。
以下是standalone-full-ha.xml中的messaging-activemq配置段:
<subsystem xmlns="urn:jboss:domain:messaging-activemq:13.0"> <server name="default" persistence-enabled="false"> <security elytron-domain="ApplicationDomain"/> <journal file-size="1024"/> <replication-master cluster-name="messaging-cluster" group-name="activemq-group"/> <security-setting name="#"> <role name="guest" send="true" consume="true" create-non-durable-queue="true" delete-non-durable-queue="true" create-durable-queue="true" delete-durable-queue="true"/> </security-setting> <address-setting name="#" dead-letter-address="jms.queue.DLQ" expiry-address="jms.queue.ExpiryQueue" max-size-bytes="10485760" page-size-bytes="2097152" message-counter-history-day-limit="10"/> <http-connector name="http-connector" socket-binding="http" endpoint="http-acceptor"/> <http-connector name="http-connector-throughput" socket-binding="http" endpoint="http-acceptor-throughput"> <param name="batch-delay" value="50"/> </http-connector> <in-vm-connector name="in-vm" server-id="0"/> <http-acceptor name="http-acceptor" http-listener="default"/> <http-acceptor name="http-acceptor-throughput" http-listener="default"> <param name="batch-delay" value="50"/> <param name="direct-deliver" value="false"/> </http-acceptor> <in-vm-acceptor name="in-vm" server-id="0"/> <jgroups-broadcast-group name="bg-group1" jgroups-cluster="activemq-cluster" connectors="http-connector"/> <jgroups-discovery-group name="dg-group1" jgroups-cluster="activemq-cluster"/> <cluster-connection name="messaging-cluster" address="jms" connector-name="http-connector" discovery-group="dg-group1"/> <jms-queue name="ExpiryQueue" entries="java:/jms/queue/ExpiryQueue"/> <jms-queue name="DLQ" entries="java:/jms/queue/DLQ"/> <jms-topic name="sfereTopic" entries="jms/topic/sfere java:jboss/exported/jms/topic/sfere"/> <connection-factory name="InVmConnectionFactory" entries="java:/ConnectionFactory" connectors="in-vm"/> <connection-factory name="RemoteConnectionFactory" entries="java:jboss/exported/jms/RemoteConnectionFactory" connectors="http-connector" ha="true" block-on-acknowledge="true" reconnect-attempts="-1"/> <pooled-connection-factory name="activemq-ra" entries="java:/JmsXA java:jboss/DefaultJMSConnectionFactory" connectors="in-vm" transaction="xa"/> </server> </subsystem>
MDB代码片段:
@MessageDriven(activationConfig = { @ActivationConfigProperty(propertyName = "destination", propertyValue = "jms/topic/sfere"), @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "jakarta.jms.Topic") }) public class SfereMessageDrivenBean implements MessageListener { @Override public void onMessage(Message message) { // 消息处理逻辑 } }
1. 调整MDB订阅配置(推荐JMS原生方案)
默认情况下,集群中同一MDB对Topic的订阅是共享非持久化订阅,消息会被负载均衡到单个节点。要让每个节点的MDB独立接收消息,需配置持久化订阅并保证每个节点的客户端ID唯一:
修改MDB的activationConfig,添加以下配置:
@MessageDriven(activationConfig = { @ActivationConfigProperty(propertyName = "destination", propertyValue = "jms/topic/sfere"), @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "jakarta.jms.Topic"), // 启用持久化订阅 @ActivationConfigProperty(propertyName = "subscriptionDurability", propertyValue = "Durable"), // 使用节点名称作为客户端ID后缀,保证每个节点唯一 @ActivationConfigProperty(propertyName = "clientId", propertyValue = "${jboss.node.name}-sfere-mdb"), // 指定订阅名称,每个节点的订阅名称可一致 @ActivationConfigProperty(propertyName = "subscriptionName", propertyValue = "sfere-topic-subscription") })
原理说明
- 持久化订阅会在Broker中注册独立的订阅者,每个节点的MDB对应一个唯一订阅者,Broker会将消息推送给所有订阅者。
${jboss.node.name}是Wildfly内置系统属性,每个集群节点的名称唯一,确保客户端ID不重复。
2. 使用Artemis广播转发配置
通过配置Artemis的divert,将Topic消息转发到每个节点的本地队列,让MDB监听本地队列实现全节点接收:
- 在
standalone-full-ha.xml的messaging-activemq subsystem中添加本地队列和divert:
<!-- 每个节点创建唯一的本地队列 --> <jms-queue name="sfereLocalQueue-${jboss.node.name}" entries="java:/jms/queue/sfereLocal"/> <!-- 创建divert,将Topic消息转发到所有节点的本地队列 --> <divert name="sfereTopicToLocalQueue" address="jms.topic.sfere" forwarding-address="jms.queue.sfereLocal-${jboss.node.name}" exclusive="false"/>
- 修改MDB监听本地队列:
@MessageDriven(activationConfig = { @ActivationConfigProperty(propertyName = "destination", propertyValue = "java:/jms/queue/sfereLocal"), @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "jakarta.jms.Queue") })
3. 替代方案:JGroups集群广播
若不需要JMS的消息持久化、事务等特性,可直接使用Wildfly内置的JGroups实现集群广播:
- 注入JGroups Channel:
@Inject @JChannel("ee") private JChannel channel;
- 发送广播消息:
channel.send(null, channel.getView().getMembers(), "集群广播消息内容".getBytes());
- 接收消息(实现Receiver接口):
public class SfereClusterReceiver implements Receiver { @Override public void receive(Message msg) { String content = new String(msg.getBuffer()); // 处理广播消息 } } // 在启动时注册Receiver channel.setReceiver(new SfereClusterReceiver());
集群名称不一致:
replication-master的cluster-name="messaging-cluster"与jgroups-broadcast-group、jgroups-discovery-group的jgroups-cluster="activemq-cluster"名称不匹配,需统一为同一名称(如messaging-cluster),否则集群节点无法正确发现。持久化关闭风险:
persistence-enabled="false"关闭了消息持久化,集群节点重启后会丢失未处理的消息。若需消息可靠性,建议设置为true并配置持久化存储路径。广播组配置优化:
jgroups-broadcast-group的connectors仅配置了http-connector,若集群节点在同一局域网,可添加in-vm-connector提升内部通信效率。
内容的提问来源于stack exchange,提问作者Faliorn

