RabbitMQ相关

RabbitMQ的AMQP协议是什么


AMQP(Advanced Message Queuing Protocol),高级消息队列协议,提供统一消息服务的开放标准,其核心目标是实现客户端与消息服务之前的高效、安全异步通信,并且在传递的时候不受客户端和开发语言的限制。

 

RabbitMQ消息的工作模式


简单模式:一个生产者一个队列一个消费者,先进先出,一般都是自己实验的时候玩。

##  队列配置
@Component
public class QueueConfig {

    @Bean
    public Queue queueTest1(){
        //name:队列名称
        //durable:持久化标志,true表示队列会被持久化存储(重启后不丢失),false表示队列是临时的(重启后消失)。默认值为true。
        return new Queue("queue_test1", true);
    }

}


##生产者
@Slf4j
@RestController
@RequestMapping("/queue")
public class QueueProductController {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @GetMapping("/queueToClient")
    public String  queueToClient(){
        Map<String, String> map1 = new HashMap<>();
        map1.put("测试简单模式","11111");
        rabbitTemplate.convertAndSend("queue_test1",map1);
        return "简单模式消息发送成功";
    }
}

##消费者 
@Component
public class QueueClient {

    @RabbitListener(queues = "queue_test1")
    @RabbitHandler
    public void topicQuery1(HashMap<String,String> jsonMap){
        System.out.println("我是简单模式消费者"+jsonMap);
    }
}
View Code


队列模式:一个生产者一个队列多个消费者,消费者之间竞争消息,实现了消费者的均衡,一个消息只能被一个消费者消费。

交换机模式:
直连交换机(Direct Exchange):根据指定的路由键完全匹配路由到队列。若路由键不匹配,消息不会被分发。例如,若队列绑定键为 "dog",则仅匹配路由键为 "dog" 的消息。
 ‌

@Component
public class DirectConfig {
    @Bean
    public DirectExchange directExchangeOne(){
//        name:交换机名称,用于标识该交换机在RabbitMQ中的唯一性。 ‌
//        durable:持久化标志,true表示交换机会被持久化存储(重启后不丢失),false表示临时交换机(重启后消失)。默认值为true。 ‌
//        autoDelete:自动删除标志,true表示当所有绑定的队列解绑后自动删除该交换机,false表示手动删除
        return new DirectExchange("directExchangeOne",true,false);
    }
    @Bean
    public Queue directQueueOne(){
        //name:交换机名称,用于标识该交换机在RabbitMQ中的唯一性。 ‌
        //durable:持久化标志,true表示交换机会被持久化存储(重启后不丢失),false表示临时交换机(重启后消失)。默认值为true。
        return new Queue("directQueueOne", true);
    }
    @Bean
    public Queue directQueueTwo(){
        return new Queue("directQueueTwo");
    }

    @Bean
    public Binding directBindingOne(){
        return BindingBuilder.bind(directQueueOne()).to(directExchangeOne()).with("directKey1");
    }
    @Bean
    public Binding directBindingTwo(){
        return BindingBuilder.bind(directQueueTwo()).to(directExchangeOne()).with("directKey2");
    }

}
直连交换机配置

 

扇型交换机(Fanout Exchange):将消息广播到所有绑定的队列,不依赖路由键。适用于需要消息被多个队列接收的场景。 ‌

@Component
public class FanoutConfig {

    @Bean
    public FanoutExchange fanoutExchange1(){
        return new FanoutExchange("fanoutExchange1");
    }

    @Bean
    public Queue fanoutQuery1(){
        return new Queue("fanout_queue1");
    }

    @Bean
    public Queue fanoutQuery2(){
        return new Queue("fanout_queue2");
    }
    @Bean
    public Binding fanoutBinding1(){
       return BindingBuilder.bind(fanoutQuery1()).to(fanoutExchange1());
    }

    @Bean
    public Binding fanoutBinding2(){
        return BindingBuilder.bind(fanoutQuery2()).to(fanoutExchange1());
    }
}
扇型交换机配置

 

主题交换机(Topic Exchange):支持路由键使用通配符 *(匹配单个词)和 #(匹配多个词)。例如,绑定模式为 audit.# 可接收 audit.irs.corporate 类型的消息。 ‌

@Component
public class TopicConfig {
    @Bean
    public TopicExchange topicExchange(){
        return new TopicExchange("topicExchange1");
    }

    @Bean
    public Queue topicQueue1(){
        return new Queue("topicQueue1");
    }

    @Bean
    public Queue topicQueue2(){
        return new Queue("topicQueue2");
    }

    @Bean
    public Binding topicBinding1(){
        return BindingBuilder.bind(topicQueue1()).to(topicExchange()).with("hello.world");
    }

    @Bean
    public Binding topicBinding2(){
         return BindingBuilder.bind(topicQueue2()).to(topicExchange()).with("hello.#");
    }
}
主题交换机配置

 

MQ如何保证消息不丢失

消息丢失分为五个场景

消息丢失(1)

1.生产者到队列过程中
2.交换机到队列的过程
3.队列到消费者的过程
4.交换机未持久化
5.队列未持久化


消息丢失解决办法

application.properties相关配置

spring.application.name=demo
## MQ
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
## 开启生成者消息到交换机回调
spring.rabbitmq.publisher-confirm-type=correlated
## 设置消息强制路由模式,确保消息无法投递到队列时返回给生产者,避免消息丢失 true:回调 false:丢弃
spring.rabbitmq.template.mandatory=true
## 开启交换机发送消息到队列中失败回调
spring.rabbitmq.publisher-returns=true
## 开启消费者手动确认机制
spring.rabbitmq.listener.simple.acknowledge-mode=manual

## 消费失败开启重试 最大重试三次、每次1000ms,时间每次成2,最大时间间隔5000ms
spring.rabbitmq.listener.direct.retry.enabled=true
spring.rabbitmq.listener.direct.retry.max-attempts=3
spring.rabbitmq.listener.direct.retry.initial-interval=1000
spring.rabbitmq.listener.direct.retry.multiplier=2
spring.rabbitmq.listener.direct.retry.max-interval=5000

## 日志
logging.level.com.sunny= info
View Code

1.生产者到队列过程中防止消息丢失
防止生产者到交换机过程消息丢失可以开启publisher-confirm属性,然后在配置RabbitTemplate的时候监听成功失败的回调

## 开启生成者消息到交换机回调
spring.rabbitmq.publisher-confirm-type=correlated

@Configuration
@Slf4j
public class RabbitMQConfig {

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        rabbitTemplate.setMessageConverter(jsonMessageConverter()); // 使用Jackson进行消息转换
        rabbitTemplate.setReplyTimeout(10000); // 设置回复超时时间

        //开启消息发送到交换机回调
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                if(ack){
                    log.info("消息发送到交换机成功,相关消息:{}",correlationData);
                }else {
                    log.info("消息发送到交换机失败,相关消息{},失败原因:{}",correlationData,cause);
                }
            }
        });

        //开启交换机发送消息到队列中失败回调
//        rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() {
//            @Override
//            public void returnedMessage(ReturnedMessage returnedMessage) {
//                log.info("消息退回: {}, 状态码: {}, 原因: {}, 交换机: {}, 路由键: {}",
//                        returnedMessage.getMessage(),
//                        returnedMessage.getReplyCode(),
//                        returnedMessage.getReplyText(),
//                        returnedMessage.getExchange(),
//                        returnedMessage.getRoutingKey());
//            }
//        });

        return rabbitTemplate;
    }

    @Bean
    public MessageConverter jsonMessageConverter() {
        return new Jackson2JsonMessageConverter();
    }

}
View Code

2.交换机到队列的过程防止消息丢失
防止交换机到队列的过程消息丢失可以开启mandatory和publisher-returns属性,然后在配置RabbitTemplate的时候监听失败的回调

## 设置消息强制路由模式,确保消息无法投递到队列时返回给生产者,避免消息丢失 true:回调 false:丢弃
spring.rabbitmq.template.mandatory=true
## 开启交换机发送消息到队列中失败回调
spring.rabbitmq.publisher-returns=true



@Configuration
@Slf4j
public class RabbitMQConfig {

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        rabbitTemplate.setMessageConverter(jsonMessageConverter()); // 使用Jackson进行消息转换
        rabbitTemplate.setReplyTimeout(10000); // 设置回复超时时间

        //开启消息发送到交换机回调
//        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
//            @Override
//            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
//                if(ack){
//                    log.info("消息发送到交换机成功,相关消息:{}",correlationData);
//                }else {
//                    log.info("消息发送到交换机失败,相关消息{},失败原因:{}",correlationData,cause);
//                }
//            }
//        });
        //开启交换机发送消息到队列中失败回调
        rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() {
            @Override
            public void returnedMessage(ReturnedMessage returnedMessage) {
                log.info("消息退回: {}, 状态码: {}, 原因: {}, 交换机: {}, 路由键: {}",
                        returnedMessage.getMessage(),
                        returnedMessage.getReplyCode(),
                        returnedMessage.getReplyText(),
                        returnedMessage.getExchange(),
                        returnedMessage.getRoutingKey());
            }
        });

        return rabbitTemplate;
    }

    @Bean
    public MessageConverter jsonMessageConverter() {
        return new Jackson2JsonMessageConverter();
    }

}
View Code

3.队列到消费者的过程防止消息丢失
防止队列到消费者的过程防止消息丢失可以开启acknowledge-mode属性,让消费确认机制改为手动确认模式, 默认情况下自动确认

## 开启消费者手动确认机制
spring.rabbitmq.listener.simple.acknowledge-mode=manual


@Component
@Slf4j
public class DirectClient {

    @RabbitListener(queues = "directQueueOne")
    @RabbitHandler
    public void directClientOne(Message message, Channel channel, @Payload HashMap<String,String> map){
        log.info("消费者消费消息message:{},channel:{},map:{}",message,channel,map);
        try {
            if(CollectionUtils.isEmpty(map) || map.get("name").equals("error")){
                log.info("消费者消费业务异常");
                //‌deliveryTag‌:消息的唯一标识符(单调递增),用于指定需要拒绝的消息。 ‌
                //‌multiple‌:布尔值,表示是否批量拒绝消息。若为true,则拒绝当前消息及之前所有未确认的消息;若为false,仅拒绝当前消息。 ‌
                //‌requeue‌:布尔值,决定被拒绝的消息是否重新入队。若为true,消息会重新放回队列;若为false,消息将被丢弃或进入死信队列
                channel.basicNack(message.getMessageProperties().getDeliveryTag(),true,true);
                return;
            }
        } catch (Exception e) {
            log.error("消费者消费消息NACK异常:",e);
        }

        try {
            log.info("消费者消费业务正常");
            channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);
        } catch (Exception e) {
            log.error("消费者消费消息ACK异常:",e);
        }
    }

    @RabbitListener(queues = "directQueueTwo")
    @RabbitHandler
    public void directClientTwo(@Payload HashMap<String,String>map){
        System.out.println("直连队列消息2:" + map);
    }
}
View Code

 

4.交换机持久化
在配置交换机时候直接设置交换机持久化durable=true

    @Bean
    public DirectExchange directExchangeOne(){
//        name:交换机名称,用于标识该交换机在RabbitMQ中的唯一性。 ‌
//        durable:持久化标志,true表示交换机会被持久化存储(重启后不丢失),false表示临时交换机(重启后消失)。默认值为true。 ‌
//        autoDelete:自动删除标志,true表示当所有绑定的队列解绑后自动删除该交换机,false表示手动删除
        return new DirectExchange("directExchangeOne",true,false);
    }
View Code

 

5.队列持久化
在配置队列的时候直接设置队列持久化durable=true

    @Bean
    public Queue directQueueOne(){
        //name:队列名称
        //durable:持久化标志,true表示队列持久化存储(重启后不丢失),false表示临时队列(重启后消失)。默认值为true。
        return new Queue("directQueueOne", true);
    }
View Code

 

 

消息防重复

生产者在进行数据分布的时候透传唯一Id,订单Id,消息Id,UUID,消费端消费的时候判断唯一Id是否被消费过,消费过则直接过滤掉,没有消费则进行消费。在存唯一Id的时候要给过期时间,可以结合redis进行缓存。

 

RabbitMQ如何保证消息的顺序


1.一个生产者一个队列一个消费者。
2.一个消费者消费完成后才进行下一个生产。
3.加时间字段业务内部自己排序。
4.使用优先级队列,可以保证单个队列安装优先级消费。
5.使用延迟队列,优先级高的队列设置短的延迟时间。

 

 

延迟队列/死信队列

场景:

1.订单在十分钟之内未支付则自动取消
2.新创建的店铺,如果在十天内都没有上传过商品,则自动发送消息提醒。
3.用户注册成功后,如果三天内没有登陆则进行短信提醒。
4.用户发起退款,如果三天内没有得到处理则通知相关运营人员。
5.预定会议后,需要在预定的时间点前十分钟通知各个与会人员参加会议

 

方式:

1.通过死信队列(DLX)实现

2.通过延迟插件 rabbitmq-delayed-message-exchange实现。

 

死信队列代码实现:

 

逻辑示意图

1763085444495

 

队列和交换机配置

@Component
public class TtlDirectConfig {
    //普通交换directExchangeA  普通队列queueA  普通路由键routingA
    //死信交换机ttlDirectExchangeA 死信队列ttlQueueA  路由键ttlRoutingA

    @Bean
    public  DirectExchange directExchangeA(){
        return  new DirectExchange("directExchangeA");
    }

    @Bean
    public  DirectExchange ttlDirectExchangeA(){
        return  new DirectExchange("ttlDirectExchangeA");
    }

    //配置普通队列A并和延迟交换机绑定
    @Bean
    public  Queue queueA(){
        Map<String, Object> args = new HashMap<>(3);
        //声明当前队列绑定的死信交换机
        args.put("x-dead-letter-exchange", "ttlDirectExchangeA");
        //声明当前队列的死信路由 key
        args.put("x-dead-letter-routing-key", "ttlRoutingA");
        //声明队列的 TTL
        args.put("x-message-ttl", 10000);
        return QueueBuilder.durable("queueA").withArguments(args).build();
    }

    @Bean
    public  Queue ttlQueueA(){
        return new Queue("ttlQueueA");
    }

    @Bean
    public  Binding directBindingA(){
        return BindingBuilder.bind(queueA()).to(directExchangeA()).with("routingA");
    }

    @Bean
    public Binding ttlDirectBindingA(){
        return BindingBuilder.bind(ttlQueueA()).to(ttlDirectExchangeA()).with("ttlRoutingA");
    }
}
View Code

 

生产者配置

 

@RestController
@RequestMapping("/ttlDirect")
public class TtlDirectProductController {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @GetMapping("/ttlDirectToClient")
    public String ttlDirectToClient(){
        log.info("当前时间:{},发送死信队列信息", new Date().toString());
        rabbitTemplate.convertAndSend("directExchangeA","routingA","一条延迟10的消息");
        return "直连队列消息发送成功";
    }
}
View Code

 

消费者配置

@Component
@Slf4j
public class TtlDirectConClient {

    @RabbitListener(queues = "ttlQueueA")
    @RabbitHandler
    public void directClientOne(Message message, Channel channel){
        String msg = new String(message.getBody());
        log.info("当前时间:{},收到死信队列信息{}", new Date().toString(), msg);
    }
}
View Code

 

RabbitMQ可视化页面显示

1763084956531

 

 

执行结果

 

1763084887288

 

 

RabbitMQ如何保证高可用

rabbitMq集群有三种模式:


单机模式:
所有组件(生产者、消费者、队列、交换机)都运行在同一台机器上,配置和管理相对简单,但缺乏高可用性,一旦节点故障将导致服务不可用。
普通集群模式:

普通集群模式

 

1.在多台机器上部署rabbitMq实例,但是创建的队列(queue)只会在一台机器实例上,其余机器保存队列的元数据(队列放在哪台broker上,是否持久化等),但不包含发送到队列中的消息。
2.如果消费的时候,实际上如果连接到了另外一个实例,那么那个实例会从 queue 所在实例上拉取数据过来。
3.如何其中一条节点宕机了,会导致其他实例都无法拉取数据,只有等机器恢复了才能继续拉取数据。
4.所以普通集群模式并没有提供高可用,这种方案只是提高了吞吐量。

 

镜像集群模式:

镜像集群模式

1.在多台机器上部署rabbitMq实例,每台机器上都有这个队列的完整镜像,包括元数据和消息数据,每次写消息到队列的时候,都会自动把消息通过到多个实例的队列上。
2.这种模式的好处在于,任何一台机器宕机了,其他的机器还可以使用。
3.这种模式性能消耗太大,所有机器都要进行消息的同步,导致网络压力和消耗很大。 没有扩展性可言,如果有一个queue负载很重,就算加了机器,新增的机器还是包含了这个queue的所有数据,并没有办法扩展queue。

 

posted @ 2025-11-12 21:10  爵士灬  阅读(12)  评论(0)    收藏  举报