在 JMS(Java Message Service)里,事务与确认机制是保证消息可靠传递和处理的关键。下面详细介绍它们在 JMS 中的具体实现方式。
事务机制
事务性会话
JMS 借助事务性会话达成事务操作。创建会话时,可将其设定为事务性会话,即把 createSession
方法的第一个参数设为 true
。在事务性会话中,消息的发送和接收操作会被封装在一个事务里,只有当事务提交时,这些操作才会生效;若事务回滚,操作将被撤销。
生产者事务示例
import javax.jms.*;
import org.apache.activemq.ActiveMQConnectionFactory;
public class JMSProducerTransactionExample {
public static void main(String[] args) {
String brokerUrl = "tcp://localhost:61616";
ConnectionFactory factory = new ActiveMQConnectionFactory(brokerUrl);
try (Connection connection = factory.createConnection();
// 创建事务性会话
Session session = connection.createSession(true, Session.SESSION_TRANSACTED);
Destination destination = session.createQueue("testQueue");
MessageProducer producer = session.createProducer(destination)) {
connection.start();
// 创建并发送多条消息
for (int i = 0; i < 5; i++) {
TextMessage message = session.createTextMessage("Message " + i);
producer.send(message);
}
// 提交事务
session.commit();
System.out.println("Messages sent successfully.");
} catch (JMSException e) {
try {
// 若发生异常,回滚事务
session.rollback();
System.out.println("Transaction rolled back.");
} catch (Exception ex) {
ex.printStackTrace();
}
e.printStackTrace();
}
}
}
上述代码创建了一个事务性会话,生产者在该会话中发送了 5 条消息。若整个过程没有异常,就会提交事务,消息会被真正发送到队列;若出现异常,则回滚事务,消息不会被发送。
消费者事务示例
import javax.jms.*;
import org.apache.activemq.ActiveMQConnectionFactory;
public class JMSConsumerTransactionExample {
public static void main(String[] args) {
String brokerUrl = "tcp://localhost:61616";
ConnectionFactory factory = new ActiveMQConnectionFactory(brokerUrl);
try (Connection connection = factory.createConnection();
// 创建事务性会话
Session session = connection.createSession(true, Session.SESSION_TRANSACTED);
Destination destination = session.createQueue("testQueue");
MessageConsumer consumer = session.createConsumer(destination)) {
connection.start();
// 接收消息
Message message = consumer.receive();
if (message instanceof TextMessage) {
TextMessage textMessage = (TextMessage) message;
System.out.println("Received: " + textMessage.getText());
}
// 处理消息(此处可添加业务逻辑)
// 提交事务
session.commit();
System.out.println("Message processed and transaction committed.");
} catch (JMSException e) {
try {
// 若发生异常,回滚事务
session.rollback();
System.out.println("Transaction rolled back.");
} catch (Exception ex) {
ex.printStackTrace();
}
e.printStackTrace();
}
}
}
此代码创建了一个事务性会话,消费者在该会话中接收消息。若消息处理过程没有异常,就会提交事务,消息会被标记为已消费;若出现异常,则回滚事务,消息不会被标记为已消费,后续可再次被消费。
确认机制
自动确认(Session.AUTO_ACKNOWLEDGE
)
在自动确认模式下,消费者接收到消息后,JMS 会自动把消息标记为已消费。这是最简单的确认模式,但存在一定风险,若消费者在处理消息时出现异常,消息已被标记为已消费,就会导致消息丢失。
import javax.jms.*;
import org.apache.activemq.ActiveMQConnectionFactory;
public class JMSAutoAcknowledgeExample {
public static void main(String[] args) {
String brokerUrl = "tcp://localhost:61616";
ConnectionFactory factory = new ActiveMQConnectionFactory(brokerUrl);
try (Connection connection = factory.createConnection();
// 创建非事务性会话,使用自动确认模式
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Destination destination = session.createQueue("testQueue");
MessageConsumer consumer = session.createConsumer(destination)) {
connection.start();
Message message = consumer.receive();
if (message instanceof TextMessage) {
TextMessage textMessage = (TextMessage) message;
System.out.println("Received: " + textMessage.getText());
}
} catch (JMSException e) {
e.printStackTrace();
}
}
}
手动确认(Session.CLIENT_ACKNOWLEDGE
)
手动确认模式下,消费者需要显式调用 acknowledge()
方法来确认消息已消费。这样消费者就能在处理完消息后再确认,避免消息丢失。
import javax.jms.*;
import org.apache.activemq.ActiveMQConnectionFactory;
public class JMSClientAcknowledgeExample {
public static void main(String[] args) {
String brokerUrl = "tcp://localhost:61616";
ConnectionFactory factory = new ActiveMQConnectionFactory(brokerUrl);
try (Connection connection = factory.createConnection();
// 创建非事务性会话,使用手动确认模式
Session session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
Destination destination = session.createQueue("testQueue");
MessageConsumer consumer = session.createConsumer(destination)) {
connection.start();
Message message = consumer.receive();
if (message instanceof TextMessage) {
TextMessage textMessage = (TextMessage) message;
System.out.println("Received: " + textMessage.getText());
// 手动确认消息
message.acknowledge();
}
} catch (JMSException e) {
e.printStackTrace();
}
}
}
事务确认(Session.SESSION_TRANSACTED
)
事务确认模式与事务性会话结合使用。在事务性会话中,消息的确认操作会在事务提交时进行。若事务回滚,消息不会被确认,后续可再次被消费。前面的生产者和消费者事务示例中就采用了这种确认模式。
预取确认(Session.DUPS_OK_ACKNOWLEDGE
)
预取确认模式允许消费者批量确认消息,以提高性能。在这种模式下,消费者可能会收到重复的消息,因此需要在业务逻辑中处理消息的重复问题。
import javax.jms.*;
import org.apache.activemq.ActiveMQConnectionFactory;
public class JMSDupsOkAcknowledgeExample {
public static void main(String[] args) {
String brokerUrl = "tcp://localhost:61616";
ConnectionFactory factory = new ActiveMQConnectionFactory(brokerUrl);
try (Connection connection = factory.createConnection();
// 创建非事务性会话,使用预取确认模式
Session session = connection.createSession(false, Session.DUPS_OK_ACKNOWLEDGE);
Destination destination = session.createQueue("testQueue");
MessageConsumer consumer = session.createConsumer(destination)) {
connection.start();
Message message = consumer.receive();
if (message instanceof TextMessage) {
TextMessage textMessage = (TextMessage) message;
System.out.println("Received: " + textMessage.getText());
}
} catch (JMSException e) {
e.printStackTrace();
}
}
}
综上所述,JMS 的事务与确认机制提供了多种方式来保证消息的可靠传递和处理,开发者可根据具体的业务需求选择合适的机制。
JMS(Java Message Service)定义了两种主要的消息模型:点对点(P2P)模型 和 发布/订阅(Pub/Sub)模型。这两种模型分别对应 队列(Queue) 和 主题(Topic),它们在消息传递方式、消息接收者数量、消息持久性等方面存在显著区别。以下是队列和主题的详细对比:
1. 消息传递方式
-
队列(Queue):
- 点对点模型:生产者将消息发送到队列,消费者从队列中接收消息。
- 一对一:每个消息只能被一个消费者接收,确保消息不会重复处理。
- 适用场景:适用于需要确保消息只被处理一次的场景,如订单处理、任务分配等。
-
主题(Topic):
- 发布/订阅模型:生产者将消息发布到主题,多个订阅者可以接收同一消息。
- 一对多:每个消息可以被多个订阅者接收,适用于广播消息的场景。
- 适用场景:适用于需要将消息广播给多个消费者的场景,如日志监控、事件通知等。
2. 消息接收者数量
-
队列:
- 每个消息只能被一个消费者接收。
- 如果有多个消费者订阅同一个队列,消息会被分发给其中一个消费者,其他消费者不会接收到该消息。
-
主题:
- 每个消息可以被多个订阅者接收。
- 如果有多个订阅者订阅同一个主题,每个订阅者都会接收到该消息。
3. 消息持久性
-
队列:
- 消息的持久性由队列的持久化属性决定。
- 生产者可以设置消息的持久性属性(
deliveryMode
),确保消息在系统故障时不会丢失。 - 消费者处理完消息后发送ACK确认,队列中的消息才会被删除。
-
主题:
- 消息的持久性由主题的持久化属性决定。
- 持久化订阅(Durable Subscription):订阅者即使在离线时,也能接收到消息。
- 非持久化订阅(Non-Durable Subscription):订阅者必须在线才能接收到消息,否则消息会被丢弃。
4. 消息存储与生命周期
-
队列:
- 消息存储在队列中,直到被消费者接收并确认。
- 消息的生命周期由队列管理,消息在被消费后从队列中删除。
-
主题:
- 消息存储在主题中,直到被所有订阅者接收。
- 消息的生命周期由主题管理,消息在所有订阅者接收后才会被删除。
- 对于持久化订阅,消息会一直存储在主题中,直到订阅者上线并接收。
5. 消费者行为
-
队列:
- 消费者通过
MessageConsumer
从队列中接收消息。 - 消费者可以采用同步(
receive()
)或异步(MessageListener
)方式接收消息。 - 消费者处理完消息后发送ACK确认,队列中的消息才会被删除。
- 消费者通过
-
主题:
- 消费者通过
MessageConsumer
从主题中接收消息。 - 消费者可以采用同步(
receive()
)或异步(MessageListener
)方式接收消息。 - 持久化订阅的消费者即使离线,也能在上线后接收到消息。
- 消费者通过
6. 适用场景
-
队列:
- 任务分配:将任务放入队列,由多个消费者并行处理,确保任务只被处理一次。
- 订单处理:确保订单消息只被处理一次,避免重复处理。
- 日志处理:将应用产生的日志发送到队列,由专门的日志处理服务进行分析和存储。
-
主题:
- 事件通知:将事件消息发布到主题,多个订阅者可以接收并处理。
- 监控上报:将服务器的监控数据发布到主题,多个监控系统可以接收并处理。
- 广播消息:将消息广播给多个消费者,确保每个消费者都能接收到。
7. 性能与资源占用
-
队列:
- 通常用于处理单个任务,资源占用相对较低。
- 适合处理高并发任务,通过多个消费者分担负载。
-
主题:
- 通常用于广播消息,资源占用相对较高。
- 适合处理低频广播消息,避免消息积压。
总结
- 队列:适用于需要确保消息只被处理一次的场景,如任务分配、订单处理等。
- 主题:适用于需要将消息广播给多个消费者的场景,如事件通知、监控上报等。
通过理解队列和主题的区别,可以更好地选择适合业务需求的消息模型,实现高效、可靠的消息传递和处理。