知识点回顾
- 异步通讯,点对点、发布订阅模式
- 几万个消息mq不会宕机,可处理高并发
本节问题
- 保证jms可靠消息
- 保证消息中间件重试机制,消费者不会重复消费
- 保证消息中间件幂等性:通过全局id去处理解决
- 分布式事务:数据不一致,定时job重试数据健康检查
- 消息中间件集群:必须使用zookeeper作为注册中心实现集群
MQ持久化机制
默认不仅持久化
为了解决宕机消息丢失,必须进行消息持久化
在生产者端进行消息持久化
开启持久化:
producer.setDeliveryMode(DeliveryMode.PERSISTENT);
jms可靠消息
activemq(类似快递)
1、自动签收:消费者监听时,直接传给消费者,不论是否消费者成功消费
2、事务消息:
生产者:完成发送后,必须提交给队列,保存在消息中间件内存中
消费者:获取事务消息,如何消费没有提交事务,默认表示没有消费,自动重试
3、手动签收:消费者手动签收,默认表示没有进行消费,还在队列
具体配置
参数1:是否使用事务提交
参数2:是否自动签收
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
.
设置手动签收模式:
Session.CLIENT_ACKNOWLEDGE
手动签收处理:
TextMessage textMessage ;
textMessage.acknowledge();
.
使用事务模式,必须手动提交到队列(消费者类似配置)
Session session = connection.createSession(Boolean.TRUE, Session.AUTO_ACKNOWLEDGE);
.session.cmmit();
springboot整合activeMQ
- 项目依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
<!--<dependency>-->
<!--<groupId>org.apache.activemq</groupId>-->
<!--<artifactId>activemq-pool</artifactId>-->
<!--<version>5.7.0</version>-->
<!--</dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
- application.yml
spring:
activemq:
broker-url: tcp://127.0.0.1:61616
user: admin
password: admin
queue: springboot-queue
server:
port: 8080
- MQConfig
@Configuration
public class MQConfig {
@Value("${queue}")
private String queue;
@Bean
public Queue queue() {
return new ActiveMQQueue(queue) ;
}
@Bean
public JmsTemplate jmsTemplate(ActiveMQConnectionFactory activeMQConnectionFactory, Queue queue) {
JmsTemplate jmsTemplate = new JmsTemplate();
// 进行持久化配置 1表示非持久化,2表示持久化
jmsTemplate.setDeliveryMode(2);
jmsTemplate.setConnectionFactory(activeMQConnectionFactory);
// 此处可不设置默认,在发送消息时也可设置队列
jmsTemplate.setDefaultDestination(queue);
// 客户端签收模式
jmsTemplate.setSessionAcknowledgeMode(4);
return jmsTemplate;
}
@Bean(name = "jmsQueueListener")
public DefaultJmsListenerContainerFactory jmsQueueListenerContainerFactory(
ActiveMQConnectionFactory activeMQConnectionFactory) {
// 定义一个消息监听器连接工厂,这里定义的是点对点模式的监听器连接工厂
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(activeMQConnectionFactory);
// 设置连接数
factory.setConcurrency("1-10");
// 重连间隔时间
factory.setRecoveryInterval(1000L);
// 客户端签收模式
factory.setSessionAcknowledgeMode(4);
return factory;
}
}
- 生产者
@Component
@EnableScheduling
public class Producer {
@Autowired
private JmsMessagingTemplate jmsMessagingTemplate;
@Autowired
private Queue queue;
@Scheduled(fixedDelay = 5000)
public void send() {
jmsMessagingTemplate.convertAndSend(queue, "测试消息队列" + System.currentTimeMillis());
}
}
- 消费者
@Component
public class Consumer {
@JmsListener(destination = "${queue}")
public void receive(TextMessage msg, Session session) {
try {
// int id = 1 / 0;
System.out.println("监听器收到msg:" + msg.getText());
// 手动签收
msg.acknowledge();
} catch (JMSException e) {
// 如果代码发生异常,需要发布版本才可以解决的问题,不要使用重试机制,采用日志记录方式,定时Job进行补偿。
// 如果不需要发布版本解决的问题,可以采用重试机制进行补偿。
// session.recover();// 继续重试
e.printStackTrace();
}
}
}
使用消息中间注意事项
- 消费者代码不要抛出异常,否则activqmq默认有重试机制。
- 如果代码发生异常,需要发布版本才可以解决的问题,不要使用重试机制,采用日志记录方式,定时Job进行补偿。
- 如果不需要发布版本解决的问题,可以采用重试机制进行补偿。
消费者如果保证消息幂等性,不被重复消费
产生原因:网络延迟传输中,会造成进行MQ重试中,在重试过程中,可能会造成重复消费。
解决办法:
1.使用全局MessageID 判断消费方使用同一个,解决幂等性。
2.使用JMS可靠消息机制伪代码:
// 网络延迟幂等性
String jmsMessageID = msg.getJMSMessageID();
if (redis.get(jmsMessageID)){
// 已经消费的直接签收,避免重试
msg.acknowledge();
}
//消费存在
*****
//消费成功,存入缓存
msg.acknowledge();
redis.add(jmsMessageID)
mq消费者集群处理
- 消费者集群不会产生幂等性问题,mq进行分发处理
- mq集群需要处理幂等性:实现mq+zookeeper