1rabbit介绍
(1)linux获取已经下载好的rabbit镜像tar包

docker load -i mq.tar

(2)启动容器,5672:MQ的通信端口,15672:管控台的端口

docker run \
 -e RABBITMQ_DEFAULT_USER=itheima \
 -e RABBITMQ_DEFAULT_PASS=itheima \
 -v mq-plugins:/plugins \
 --name mq \
 --hostname mq \
 --restart=always \
 -p 15672:15672 \
 -p 5672:5672 \
 -d \
 rabbitmq:3-management

(3)结构与角色
image.png
2、rabbit入门
介绍:rabbitmq的五种模式
(1)Simple简单模式:一个生产者,一个消费者,没有交换机
image.png
导入依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-amqp</artifactId>
    </dependency>

配置文件:

spring:
  rabbitmq:
   host: 192.168.138.100 # 主机名
      port: 5672 # 端口
      virtual-host: / # 虚拟主机
      username: itheima # 用户名
      password: itheima # 密码

(2)WorkQueue:一个生产者,多个消费者,消费者之间是竞争关系
image.png
配置消费者:

spring:
  rabbitmq:
    listener:
      simple:
        prefetch: 1 # 每次只能获取一条消息,处理完成才能获取下一个消息

(3)Fanout:一个生产者多个消费者,通过交换机,把消息路由到队列,实现群发
image.png
(4)Direct:一个生产者,多个消费者,通过交换机,通过路由key的精确名称把消息路由到队列,实现群发
image.png
(5)Topic:一个生产者,多个消费者,通过交换机,通过路由key的部分名称把消息路由到队列,实现群发.*:匹配单个字符,#:匹配一个或多个字符
image.png

3、实现
简单队列创建持久化队列和交换机并绑定

@Configuration
public class RabbitConfig {

@Bean
public Queue errorQueue(){
    return QueueBuilder.durable("error.queue").build();
}
@Bean
public DirectExchange errorExchange(){
    return new DirectExchange("error.direct",true,false);
}
@Bean
public Binding errorQueueDirect(Queue errorQueue,DirectExchange errorExchange){
   return BindingBuilder.bind(errorQueue).to(errorExchange).with("error");
    }
}

等价于,交换机默认持久化

@Component
@Slf4j
public class MessageListener {

@RabbitListener(bindings = {
        @QueueBinding(
                value = @Queue(name = "direct.queue", durable = "true"),
                exchange = @Exchange(name = "itcast.direct"),
                key = {"red", "blue"}
        )
})
public void listenerSimpleQueue(String message) {

}

4、保障消息可靠性
(1)、生产者开启confirm机制,保证消息正确到达交换机

String s = UUID.randomUUID().toString().replace("-","");
    CorrelationData correlationData = new CorrelationData(s);
    correlationData.getFuture().addCallback(new ListenableFutureCallback<CorrelationData.Confirm>() {
        @Override
        public void onFailure(Throwable ex) {
            log.error("发现送消息出问题了"+ex.getMessage()+correlationData.getId());
            log.info("发现送消息出异常,重新发送消息");
        }

        @Override
        public void onSuccess(CorrelationData.Confirm result) {
            if (result.isAck()){
                log.info("消息发送成功");
            }
            else {
                log.error("消息发送失败");
                log.info("重新发送消息");
            }
        }
    });
    MessageProperties messageProperties = new MessageProperties();
    messageProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
    Message message = new Message("GoodMessage".getBytes(StandardCharsets.UTF_8), messageProperties);
    rabbitTemplate.send("itcast.direct","blue",message,correlationData);

(2)、生产者开启return机制,保证消息正确到达队列

@Slf4j
@Configuration
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());
        // 如果有业务需要,可以重发消息
    });
}
}

(3)、交换机、队列、消息进行持久化,示例代码为消息持久化,剩下两个在3里提到

 MessageProperties messageProperties = new MessageProperties(); 
messageProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
    Message message = new Message("GoodMessage".getBytes(StandardCharsets.UTF_8), messageProperties);

(4)、消费者开启手动ack,或者自动ack + 重试耗尽的失败策略,定义错误交换机队列,后期通过人工进行干预
完整配置文件:

spring:
  rabbitmq:
    host: 192.168.138.100
    port: 5672
    virtual-host: /
    username: itheima
    password: itheima
    listener:
      simple:
        #每次拉取一条消息,等消费完了,再拉取另一条消息
        prefetch: 1
        #acknowledge-mode: manual  #手动ack
        acknowledge-mode: auto  #自动ack
        retry:
          enabled: true  #开启消费者失败重试
          initial-interval: 1000  #初始的失败等待时长为1秒
          multiplier: 2  #失败的等待时长倍数,下次等待时长 = multiplier * last-interval
          max-attempts: 3   #最大重试次数
          stateless: true    #true无状态;false有状态。如果业务中包含事务,这里改为false

手动ack:

public void listenerSimpleQueue(String message,Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) Long tag) {   
            try {
        log.info("direct.queue收到的消息:{}", message);
        Thread.sleep(2000);
        int i = 1 / 0;
        channel.basicAck(tag, false);
    } catch (Exception e) {
        try {
            channel.basicNack(tag, false,true);
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
}

自动ack

   public void listenerSimpleQueue(String message) {
    log.info("direct.queue收到消息{}",message);
    int i = 1 / 0;
}

5、消息重复消费问题
(1)幂等性:多次执行同一个操作,最终的结果是一样的、例如查询差多少次都是一样的,但是增删改就不可以不具有幂等性。

对于幂等性操作,多次消费消息,除开性能的影响 ,其他没有什么大问题,可以不管它

对于非幂等性操作,多次消费消息,会造成数据一致性的问题,所以要保证重复消费消息的问题

6、消息积压问题
(1)产生的原因?
生产者生产消息的速度 远高于 消费者消费消息的速度?于是就会造成消息积压在MQ中
(2)分析为什么会有消息积压?
<1>生产者消费者比例设计是否有问题?如果是,重新设置生产者与消费者数量匹配
<2>消费者出异常了,第一步:修复宕机的情况,第二步:临时开启多个消费者,来以多倍速消费积压的消息。当积压的消息消费的差不多的情况,关闭临时消费者
<3>惰性队列:可以放很多消息,还可以把多的消息持久化到本地

7、死信交换机
(1)死信:顾名思义就是无法被消费的消息,字面意思可以这样理解,一般来说,生产者直接将消息投递给交换机或者队列,消费者从队列取出消息进行消费,但某些时候由于特定的原因导致队列中的某些消息无法被消费,这样的消息如果没有后续的处理,就变成了死信,有死信自然就有了死信队列。
可能导致的原因:
<1>消费者使用basic.reject 或 basic.nack声明消费失败,并且消息的requeue参数设置为false
<2>消息是一个过期消息,超时无人消费
<3>要投递的队列消息满了,无法投递
(2)死信交换机本质上就是一个普通的交换机,给他绑定一个属性


留胡子的饼干
1 声望0 粉丝