知识点回顾

  • 异步通讯,点对点、发布订阅模式
  • 几万个消息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();
        }

    }
}

使用消息中间注意事项

  1. 消费者代码不要抛出异常,否则activqmq默认有重试机制。
  2. 如果代码发生异常,需要发布版本才可以解决的问题,不要使用重试机制,采用日志记录方式,定时Job进行补偿。
  3. 如果不需要发布版本解决的问题,可以采用重试机制进行补偿。

activemq 消息持久化到数据库 activemq如何保证消息不重复_spring

消费者如果保证消息幂等性,不被重复消费

产生原因:网络延迟传输中,会造成进行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