rabbitmq消费消息:结合redis保证消息不被重复消费

  • 通用解决方案是在消息实体中添加全局唯一的id,例如 msg_id(消息ID),在代码中保证消息的幂等性,
  • 消费者在收到消息之后,根据 msg_id 从缓存或者数据库中查询是否存在已有消息;
  • 如果不存在已有消息,那么消费之后,将 msg_id 对应的消息实体或者序列化对象写入缓存或者数据库;
  • 如果存在已有消息,说明这条消息已被消费过,丢弃消息并且打一条告警日志。
  • 并且可以根据重复消费的容忍程度以及性能要求选择使用缓存还是使用数据库,
  • 如果对判断的速度要求高,可以使用 Redis 作为缓存;
  • 如果对判断的稳定性和鲁棒性要求高,使用数据库存储消息实体,同时将 msg_id 作为数据库表的唯一键,插入重复记录一定会抛出异常,避免数据库因为并发问题产生脏数据,保证了消息消费的不可重复性。
  • 这里使用stringRedisTemplate.opsForHash()保证消息不被重复消费
    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue(value = "queue.name.user", declare = "true"), // 创建info队列,declare默认队列持久化
                    key = {"route.user"}, // 路由key
                    exchange = @Exchange(type = "direct", name = "exchange-directs-user")
            )})
    public void receive12211(Message message, Student student, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag) throws IOException {
        System.out.println("message:"+message);
        MessageProperties messageProperties = message.getMessageProperties();
        long tag = deliveryTag;
        Map headers = messageProperties.getHeaders();
        String messageId = headers.get("spring_returned_message_correlation").toString();
        System.out.println("message_id: "+messageId);
        if(stringRedisTemplate.opsForHash().entries("rabbitmq_log").containsKey(messageId)){
            //redis中包含该key,说明已经被消费过了
            System.out.println(messageId+"消息已经被消费过一次");
            //确认消息已被消费
            channel.basicAck(tag,false);
            return;
        }
        try {
            System.out.println("路由模式message1 = " + student);
            stringRedisTemplate.opsForHash().put("rabbitmq_log",messageId,"v");
            channel.basicAck(tag,false);
            System.out.println(messageId+"消息消费成");
        }catch (Exception e){
            e.printStackTrace();
            System.out.println("消息消费异常");
            channel.basicNack(tag,false,true);
        }
    }
server:
  port: 8071

spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: admin
    password: 123456
    virtual-host: /ems
    template:
      retry: #重试,消息发送失败会重试
        enabled: true # 开启重试
        initial-interval: 10000ms  #第一次十秒重试
        max-interval: 80000ms  #最后一次是八秒重试
        multiplier: 2  #重试翻倍率
    publisher-confirms: true #发送者开启 confirm 确认机制
    publisher-returns: true  # 发送者开启 return 确认机制
    listener:
      simple:
        acknowledge-mode: manual #开启手动ack

  datasource:
    url: jdbc:mysql://localhost:3306/one?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&AllowPublicKeyRetrieval=True
    username: root
    password: root


  redis:
    host: localhost
    port: 6379
    password:


mybatis-plus:
  mapper-locations: classpath*:com/test/mapper/xml/*.xml
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    map-underscore-to-camel-case: true
  type-aliases-package: com.test.domain

你可能感兴趣的:(spring,reids,mysql,java-rabbitmq,rabbitmq,redis)