RabbitMQ

快速入门

1、安装
​
1)获取 rabbitmq 的镜像
​
方式一 (直接远程拉去镜像)
​
docker pull rabbitmq:3-management
​
方式二 (导入mq.tar)
​
docker load -i mq.tar
​
如果丢失镜像名称与版本,执行如下命令
docker tag rabbitmq:3-management 镜像ID
​
2)启动容器
​
docker run -d -p 15672:15672 -p 5672:5672 --name mq  --hostname mq --restart=always -v mq-plugins:/plugins 
-e RABBITMQ_DEFAULT_USER=itcast -e RABBITMQ_DEFAULT_PASS=123321 rabbitmq:3-management
​
参数说明:
5672: MQ的通信端口
15672:管控台的端口
​
如果丢失镜像名称与版本,执行如下命令
​
docker tag rabbitmq:3-management 镜像ID

SpringAMQP使用

1、导入依赖
​
<!--AMQP依赖,包含RabbitMQ-->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
​
2、配置文件
​
spring:
  rabbitmq:
    host: 192.168.138.100 # 主机名
    port: 5672 # 端口
    virtual-host: / # 虚拟主机
    username: itcast # 用户名
    password: 123321 # 密码
Simple
1、声明队列(图形化方式创建)
​
2、生产者
​
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    /**
     * simple
     * @throws Exception
     */
    @Test
    public void simpleTest() throws Exception {
        // 这个api必须先声明队列
        rabbitTemplate.convertAndSend("","simple.queue","纯爱战神");
    }
    
3、消费者
​
@Component
public class MessageListener {
​
    //消费完自动签收
    @RabbitListener(queues = "simple.queue")
    public void simpleConsume(String message){
        System.out.println("收到的消息:" + message);
    }
}
WorkQueue
1、声明队列(图形化方式创建)
​
​
2、生产者
​
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    /**
     * workQueue
     * @throws Exception
     */
    @Test
    public void workQueueTest() throws Exception {
        for (int i = 0; i < 100; i++) {
            rabbitTemplate.convertAndSend("","mySimple.queue", "我想你了+" + i);
        }
​
    }
​
​
3、消费者 (模拟两个消费者消费水平不一样)
​
@Component
public class MessageListener {
​
    @RabbitListener(queues = "mySimple.queue")
    public void workQueue1Consume(String message){
        try {
            Thread.sleep(20);
            System.out.println("GZR收到:" + message);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
​
    @RabbitListener(queues = "mySimple.queue")
    public void workQueue2Consume(String message){
        try {
            Thread.sleep(80);
            System.out.println("CYT收到:" + message);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
​
​
4、配置文件 (让他们按需获取,体现其竞争关系)
​
spring:
  rabbitmq:
    listener:
      simple:
        prefetch: 1 # 每次只能获取一条消息,处理完成才能获取下一个消息
Fanout
1、声明队列与交换机(配置类方式)
​
@Configuration
public class FanoutConfig {
​
    // 声明队列
    @Bean
    public Queue itheimaFanoutQueue1(){
        return QueueBuilder.durable("itheima.fanout.queue1").build();
    }
​
    @Bean
    public Queue itheimaFanoutQueue2(){
        return QueueBuilder.durable("itheima.fanout.queue2").build();
    }
​
    //声明交换机
    @Bean
    public FanoutExchange itheimaFanout(){
        return ExchangeBuilder.fanoutExchange("itheima.fanout").build();
    }
​
    //进行绑定关系
    @Bean
    public Binding queue3FanoutExchange(Queue itheimaFanoutQueue1, FanoutExchange itheimaFanout){
        return BindingBuilder.bind(itheimaFanoutQueue1).to(itheimaFanout);
    }
​
    @Bean
    public Binding queue4FanoutExchange(Queue itheimaFanoutQueue2,FanoutExchange itheimaFanout){
        return BindingBuilder.bind(itheimaFanoutQueue2).to(itheimaFanout);
    }
}
​
​
2、生产者
​
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    /**
     * fanout
     * @throws Exception
     */
    @Test
    public void fanoutTest() throws Exception {
        rabbitTemplate.convertAndSend("itcast.fanout","","我想你了");
    }
​
    }
​
​
3、消费者
​
@Component
public class MessageListener {
​
    @RabbitListener(queues = {"fanout.queue1"})
    public void fanout1Consume(String message){
        System.out.println("GZR收到:" + message);
    }
​
    @RabbitListener(queues = {"fanout.queue2"})
    public void fanout2Consume(String message){
        System.out.println("CYT收到:" + message);
    }
}
Direct
1、声明队列(注解方式创建)
​
​
2、生产者
​
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    /**
     * direct
     * @throws Exception
     */
    @Test
    public void directTest() throws Exception {
        rabbitTemplate.convertAndSend("itcast.direct","blue","你是我的朱砂痣");
        rabbitTemplate.convertAndSend("itcast.direct","yellow","你是我的白月光");
        rabbitTemplate.convertAndSend("itcast.direct","red","你是我的遗憾");
    }
 
 
3、消费者
​
@Component
public class MessageListener {
​
   @RabbitListener(bindings = {
        @QueueBinding(value = @Queue(name = "direct.queue1",durable = "true"),
                exchange = @Exchange(name = "itcast.direct",type = ExchangeTypes.DIRECT),
        key = {"blue","red"})
    })
    public void direct1Consume(String message){
        System.out.println("GZR收到:" + message);
    }
​
    @RabbitListener(bindings = {
            @QueueBinding(value = @Queue(name = "direct.queue2",durable = "true"),
                    exchange = @Exchange(name = "itcast.direct",type = ExchangeTypes.DIRECT),
                    key = {"yellow","red"})
    })
    public void direct2Consume(String message){
        System.out.println("CYT收到:" + message);
    }
}
Topic
1、声明队列(注解方式创建)
​
​
2、生产者
​
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    /**
     * topic
     * @throws Exception
     */
    @Test
    public void topicTest() throws Exception {
        rabbitTemplate.convertAndSend("itcast.topic","china.big","你一直都是我的朱砂痣");
        rabbitTemplate.convertAndSend("itcast.topic","big.news","你一直都是我的白月光");
        rabbitTemplate.convertAndSend("itcast.topic","china.news","你一直都是我的遗憾");
    }
 
 
3、消费者
​
@Component
public class MessageListener {
​
    @RabbitListener(bindings = {
            @QueueBinding(value = @Queue(name = "topic.queue1",durable = "true"),
            exchange = @Exchange(name = "itcast.topic",type = ExchangeTypes.TOPIC),
            key = {"china.#"})
    })
    public void topic1Consume(String message){
        System.out.println("GZR收到:" + message);
    }
​
    @RabbitListener(bindings = {
            @QueueBinding(value = @Queue(name = "topic.queue2",durable = "true"),
                    exchange = @Exchange(name = "itcast.topic",type = ExchangeTypes.TOPIC),
                    key = {"#.news"})
    })
    public void topic2Consume(String message){
        System.out.println("CYT收到:" + message);
    }
}
消息转换器
1、使用JSON转换器自动转化(消费者与生产者都要导入依赖且配置bean)
​
1)导入依赖
​
<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
    <version>2.9.5</version>
</dependency>
​
2)配置bean
​
@Bean
public MessageConverter jsonMessageConverter(){
    return new Jackson2JsonMessageConverter();
}
​
3)示例
​
生产者)
​
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    /**
     * 发送对象
     * @throws Exception
     */
    @Test
    public void sendObjectTest() throws Exception {
        Map<String,Object> map = new HashMap<>();
        map.put("name","桂志蓉");
        map.put("age",24);
        rabbitTemplate.convertAndSend("","simple.queue",map);
    }
​
​
2、使用json手动转化(消费者与生产者都要导入依赖)
​
1)导入依赖
​
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>fastjson</artifactId>
  <version>1.2.83</version>
</dependency>
​
2)示例
    
生产者)
​
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    /**
     * 发送对象
     * @throws Exception
     */
    @Test
    public void sendObjectTest() throws Exception {
        Map<String,Object> map = new HashMap<>();
        map.put("name","桂志蓉");
        map.put("age",24);
        String json = JSONObject.toJSONString(map);
        rabbitTemplate.convertAndSend("","simple.queue",json);
    }

消息可靠性

生产者确认机制
1、配置文件(生产者中配置)
​
spring:
  rabbitmq:
    publisher-confirm-type: correlated
    publisher-returns: true
    template:
      mandatory: true
​
​
2、定义ConfirmCallback(ConfirmCallback可以在发送消息时指定,因为每个业务处理confirm成功或失败的逻辑不一定相同)
​
        String uuid = UUID.randomUUID().toString().replaceAll("-", "");
        CorrelationData correlationData = new CorrelationData(uuid);
        correlationData.getFuture().addCallback(new ListenableFutureCallback<CorrelationData.Confirm>() {
            @Override
            public void onFailure(Throwable throwable) {
                // 出异常,走这里
                log.error("发送信息出问题了:" + throwable.getMessage() + correlationData.getId());
                // 记录日志,重发
                log.info("出异常,重新发信息---->111");
                //rabbitTemplate.convertAndSend("itcast.direct", "blue","GoodsMessage",correlationData);
            }
​
            @Override
            public void onSuccess(CorrelationData.Confirm confirm) {
                //没出异常,走这里
                if(confirm.isAck()){
                    log.warn("消息正确到达交换机---->666" + correlationData.getId());
                }else {
                    log.error("发送失败了,没有到达交换机");
                    // 记录日志,重发
                    log.info("发送失败,重新发信息---->222");
                    //消息发送失败回调的方法,一个消息对应一个
                    //rabbitTemplate.convertAndSend("itcast.direct", "blue","GoodsMessage111",correlationData);
                }
            }
        });
        rabbitTemplate.convertAndSend("itcast.direct", "blue","GoodsMessage",correlationData);
​
​
3)定义Return回调(每个RabbitTemplate只能配置一个ReturnCallback,因此需要在项目加载时配置)  
​
@Configuration
@Slf4j
public class CommonConfig implements ApplicationContextAware {
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        // 获取RabbitTemplate
        RabbitTemplate rabbitTemplate = applicationContext.getBean(RabbitTemplate.class);
        // 设置ReturnCallback
        rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
            // 投递失败,记录日志
            log.info("消息发送失败,应答码{},原因{},交换机{},路由键{},消息{}",
                    replyCode, replyText, exchange, routingKey, message.toString());
            // 如果有业务需要,可以重发消息
            log.warn("重发消息");
            //rabbitTemplate.convertAndSend(exchange,routingKey,message.getBody());
        });
    }
}
持久化机制
1、交换机持久化
​
默认就是持久化
        
2、队列持久化
​
默认就是持久化
        
3、消息持久化
​
默认就是持久化
    
*在发送消息时,使用Message对象,并设置delivery-mode为持久化
​
MessageProperties messageProperties = new MessageProperties();
// 设置消息持久化
messageProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
//设置消息的唯一id
String uuid = UUID.randomUUID().toString().replaceAll("-", "");
messageProperties.setMessageId(uuid);
Message message = new Message("GoodsMessage".getBytes(StandardCharsets.UTF_8),messageProperties);           rabbitTemplate.send("itcast.direct", "blue", message, correlationData);
消费者ack机制
1、none:只要消息到达消费者,Spring直接返回ack到MQ(一般不使用)
MQ收到ack,会把队列中的消息删除
消息会丢失
    
消费者配置
​
spring:
  rabbitmq:
    listener:
      simple:
        acknowledge-mode: none # 关闭ack
        
​
2、manual:手动ack
消费成功,调用API给MQ返回ack
消费失败,调用API给MQ返回nack,并且让消息重回队列
​
消费者配置
spring:
  rabbitmq:
    listener:
      simple:
        acknowledge-mode: manual  #手动ack
​
消费者代码
​
    @RabbitListener(bindings = @QueueBinding(
            value =@Queue(name = "direct.queue1"),
            exchange = @Exchange(name = "itcast.direct",type = ExchangeTypes.DIRECT),
            key = {"blue","red"}
    ))
    public void directQueue1(String message, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) Long tag){
        try {
            log.info("direct.queue1收到的消息: {}",message);
            Thread.sleep(2000);
            int i = 1/0;
            //手动ack
            channel.basicAck(tag,false);
        } catch (Exception e) {
            //e.printStackTrace();
            try {
                //手动nack,让消息重回队列。参数三表示是否重回队列
                channel.basicNack(tag,false,true);
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }
    
    
*3、auto:自动ack。消费消息不出异常,返回ack给MQ。消费消息出异常了,返回nack,把消息重回队列
​
1)本地重试
​
spring:
  rabbitmq:
    listener:
      simple:
        acknowledge-mode: auto  #自动ack
        retry:
          enabled: true  #开启消费者失败重试
          initial-interval: 1000  #初始的失败等待时长为1秒
          multiplier: 2  #失败的等待时长倍数,下次等待时长 = multiplier * last-interval
          max-attempts: 3   #最大重试次数
          stateless: true    #true无状态;false有状态。如果业务中包含事务,这里改为false
​
2)失败策略
​
1、RejectAndDontRequeueRecoverer:重试耗尽后,直接reject,丢弃消息。默认就是这种方式
​
2、ImmediateRequeueMessageRecoverer:重试耗尽后,返回nack,消息重新入队
​
3、RepublishMessageRecoverer:重试耗尽后,将失败消息投递到指定的交换机
​
3)使用 RepublishMessageRecoverer
​
编写配置类
​
@Configuration
@Slf4j
public class RabbitConfig {
​
    //定义错误队列
    @Bean
    public Queue errorQueue(){
        return QueueBuilder.durable("error.queue").build();
    }
​
    //定义错误交换机
    @Bean
    public DirectExchange errorExchange(){
        return ExchangeBuilder.directExchange("error.direct").build();
    }
​
    //进行绑定
    @Bean
    public Binding errorQueueDirect(Queue errorQueue,DirectExchange errorExchange){
        return BindingBuilder.bind(errorQueue).to(errorExchange).with("error");
    }
    
    //失败策略:重试次数耗尽后,会把消息投递到错误的队列,由错误队列去消费
    @Bean
    public MessageRecoverer publishMessageRecoverer(RabbitTemplate rabbitTemplate){
        return new RepublishMessageRecoverer(rabbitTemplate,"error.direct","error");
    }
}
​
处理错误队列中的消息
​
    @RabbitListener(queues = "error.queue")
    public void errorMessage(String msg, Message message){
        log.info("error.queue 收到的消息" + msg);
        //记录日志,后面人工干预,向运维人员不断发邮件
        //可以通过message拿到各种消息
    }
保证消息可靠性总结
1、生产者开启confirm机制,保证消息正确到达交换机
​
2、生产者开启return机制,保证消息正确到达队列
​
3、交换机、队列、消息进行持久化
​
4、消费者开启手动ack,或者自动ack + 重试耗尽的失败策略,定义错误交换机队列,后期通过人工进行干预

消息重复消费问题

 使用redis做确认机制,防止在非幂等性操作下多次消费信息,造成数据一致性的问题
 
 代码演示
 
 @Autowired
    private StringRedisTemplate stringRedisTemplate;
    
    //============手动ack
    @RabbitListener(bindings = @QueueBinding(
            value =@Queue(name = "direct.queue1"),
            exchange = @Exchange(name = "itcast.direct",type = ExchangeTypes.DIRECT),
            key = {"blue","red"}
    ))
    public void directQueue1(String mes, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) Long tag, Message message){
        try {
            log.info("direct.queue1收到的消息: {}",mes);
            //获取消息的唯一id
            String messageId = message.getMessageProperties().getMessageId();
            //0代表这个消息正在消费,1代表这个消息被消费完了,设置过期时间预防死锁的情况
            Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(messageId, "0", 10, TimeUnit.SECONDS);
            if(flag){
                //这个消息没有被消费过,进行消费
                log.info("directQueue1正在处理业务");
                int i = 1/0;
                stringRedisTemplate.opsForValue().set(messageId, "1", 10, TimeUnit.SECONDS);
                //手动ack
                channel.basicAck(tag,false);
            }else {
                //消息已经被消费过
                String value = stringRedisTemplate.opsForValue().get(messageId);
                if("1".equals(value)){
                    //手动ack,1代表这个消息被消费完了,可能还没被签收过,这个签收一下
                    channel.basicAck(tag,false);
                }
                // 正在被消费,这个就不做操作
            }
        } catch (Exception e) {
//            e.printStackTrace();
            try {
                //手动nack,让消息重回队列。参数三表示是否重回队列
                channel.basicNack(tag,false,true);
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }

消息积压问题

生产者生产消费的速度 远高于 消费者消费消息的速度?于是就会造成消息积压在MQ
​
1、设计有问题
​
重新设置生产者与消费者数量匹配(如多个生产者、一个消费者)
​
2、消费者有问题
​
1)消费者出异常了
​
解决消费者代码
​
2)消费者宕机了
​
第一步:修复宕机的情况
​
第二步:临时开启多个消费者,来以倍速消费积压的消息。当积压的消息消费的差不多的情况,关闭临时消费者
​

死信交换机

1、死信
​
1)消费者使用basic.reject 或 basic.nack声明消费失败,并且消息的requeue参数设置为false
​
2)消息是一个过期消息,超时无人消费
​
3)要投递的队列消息满了,无法投递
​
​
2、死信交换机
​
如果这个包含死信的队列配置了 dead-letter-exchange 属性,指定了一个交换机,那么队列中的死信就会投递到这个交换机中,而这个交换机称为 死信交换机 (Dead Letter Exchange,检查DLX)。
​
​
3、TTL
​
超时未消费,消息变成死信的两种情况
​
1)消息所在的队列设置了超时时间
​
2)消息本身设置了超时时间
​
如果两者都设置了,以短的时间为优先
​
4、延迟【时】队列(一种实现 消费者延迟收到消息 的模式)
​
在RabbitMQ中,没有延迟队列的功能。可以使用 TTL + 死信队列 的方式实现延迟队列
​
1)声明死信交换机和死信队列
​
    //声明死信交换机跟死信队列
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "dl.ttl.queue"),
            exchange = @Exchange(name = "dl.ttl.direct", type = ExchangeTypes.DIRECT),
            key = {"ttl"}
    ))
    public void dlTtlQueueMessage(String message) {
        log.info("死信队列dl.ttl.queue收到的消息: {}", message);
        //订单超时未处理代码
    }
​
2)声明普通交换机和普通队列、绑定关系
​
@Configuration
public class TtlQueueConfig {
​
    //普通队列:这个队列的消息是无人消费,到达超时时间,才会变成死信,路由死信交换机,最终到达死信队列
    @Bean
    public Queue ttlQueue(){
        return QueueBuilder.durable("ttl.queue")
                .deadLetterExchange("dl.ttl.direct")//死信交换机
                .deadLetterRoutingKey("ttl")//死信路由key
                .ttl(5000)//死信过期时间
                .build();
    }
​
    //普通交换机
    @Bean
    public DirectExchange ttlDirect(){
        return new DirectExchange("ttl.direct");
    }
​
​
    @Bean
    public Binding ttlQueueTtlDirect(Queue ttlQueue,DirectExchange ttlDirect){
        return BindingBuilder.bind(ttlQueue).to(ttlDirect).with("ttl");
    }
}
​
3)发消息
​
不指定消息过期时间
​
    public void dalayTest() throws Exception {
        MessageProperties properties = new MessageProperties();
        Message message = new Message("goodBoy".getBytes(StandardCharsets.UTF_8),properties );
        rabbitTemplate.convertAndSend("ttl.direct", "ttl", message);
    }
​
指定消息过期时间
​
    public void dalayTest() throws Exception {
        MessageProperties properties = new MessageProperties();
        //设置消息过期时间
        properties.setExpiration("3000");
        Message message = new Message("goodBoy".getBytes(StandardCharsets.UTF_8),properties );
        rabbitTemplate.convertAndSend("ttl.direct", "ttl", message);
    }
​
5、DelayExchange插件(实现延时队列)
    
官网
​
https://blog.rabbitmq.com/posts/2015/04/scheduling-messages-with-rabbitmq
    
1)安装
​
1)把插件拖动到服务器中mq插件目录
​
docker volume inspect 数据卷名称 (进入放插件的地方,再将插件放进去)
​
2)开启插件
​
2.1进入容器内部
​
docker exec -it mq bash
​
2.2应用插件
​
rabbitmq-plugins enable rabbitmq_delayed_message_exchange
​
3)原理
​
1、DelayExchange需要将一个交换机声明为delayed类型
​
2、发送消息到delayExchange时的执行过程
​
1、接收消息
2、判断消息是否具备x-delay属性
3、如果有x-delay属性,说明是延迟消息,持久化到硬盘,读取x-delay值,作为延迟时间
4、返回routing not found结果给消息发送者
5、x-delay时间到期后,重新投递消息到指定队列
​
4)实现
​
1、声明DelayExchange交换机
注解方式【推荐】
    
    //使用插件完成延时队列
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "delay.queue"),
            exchange = @Exchange(name = "delay.direct", type = ExchangeTypes.DIRECT,delayed = "true"),
            key = {"delay"}
    ))
    public void delayQueueMessage(String message) {
        log.info("延时队列delay.queue收到的消息: {}", message);
        //订单超时未处理代码
    }
            
2、生产者发送消息
        
    @Test
    public void delayPluginTest() throws Exception {
        MessageProperties properties = new MessageProperties();
        //设置消息过期时间
        properties.setHeader("x-delay",8000);
        Message message = new Message("PluginBoy".getBytes(StandardCharsets.UTF_8),properties);
        rabbitTemplate.convertAndSend("delay.direct", "delay", message);
    }       
  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值