后段基础_11 | 青训营笔记

109 阅读3分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 13 天

消息队列

简单,工作,发布确认,发布订阅,路由,主题

生产者

  1. 连接工厂
  2. 信道
  3. 队列
  4. 发送(basicPublish)
// 创建连接工厂
         // 连接工厂 工厂模式
        ConnectionFactory connectionFactory = new ConnectionFactory();
        //
        connectionFactory.setHost(host);
        connectionFactory.setUsername(UserName);
        connectionFactory.setPassword(passWorld);
        // 获取连接对象
        Connection connection = connectionFactory.newConnection();
        // 获取信道
        Channel channel = connection.createChannel();


        // 生成队列
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        // 发送消息
        Scanner sc =new Scanner(System.in);
        while (sc.hasNext()){
            // 循环发送
            String message = sc.next();
            channel.basicPublish("", QUEUE_NAME,null,message.getBytes());
            System.out.println("发送完成:"+message);
        }

消费者

  1. 工厂
  2. 信道
  3. 发送消息(basicConsume)
 Channel channel = RabbitMqUtils.getChannel("127.0.0.1", "root", "root");

        // 消息接收
        DeliverCallback deliverCallback = (consumerTag, message) -> {
            System.out.println("接收到的消息:" + new String(message.getBody()));
        };

        // 取消接收消息
        CancelCallback cancelCallback = (cosumerTag) -> {
            System.out.println("取消逻辑:" + cosumerTag);
        };

        // 接收消息
        System.out.println("c2启动线程。。。。。。。。。");
        channel.basicConsume(QUEUE_NAME, true, deliverCallback, cancelCallback);

消息应答

自动应答

手动应答

  1. channel.basicAck
  2. channel.basicNAck
  3. channel.basicRejecr

与 Channel.basicNack 相比少一个参数 不处理该消息了直接拒绝,可以将其丢弃了

在消费方的回调函数中,手动确认消息

消息持久化

如果队列突然宕机,或者消费方无法响应,就会导致消息丢失的现象

队列持久化

在生产者创建队列时,dur选项改为true

// 生成队列
channel.queueDeclare(QUEUE_NAME,true,false,false,null);

消息持久化

在生产者发送消息的时候

MessageProperties.PERSISTENT_TEXT_PLAIN

channel.basicPublish("", QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN,message.getBytes());

不公平分发

能者多劳,在消费者里设置

channel.basicQos(1);

欲取值

信道里最多能存几条

channel.basicQos(几条);

消息确认

消息发布在队列里,持久化需要保存在磁盘里。如果保存成功了,给生产者发个消息

  • 消息持久化
  • 队列持久化
  • 消息确认
// 开启发布确认模式
channel.confirmSelect();

模式

  • 单个确认发布

一个一个确认了在发,太慢了

  • 批量确认发布

发完了或者每多少条确认一次,不知道丢失的是那个

  • 异步确认发布

你只管发,我开一个回调函数线程去记录哪些成功哪些失败

// 开启发布确认
channel.confirmSelect();

// 监听器,开启单调的线程进行监听
channel.addConfirmListener

// 发送消息
channel.basicPublish("",QUEUE_NAME,null,message.getBytes()  );

使用线程安全的ConcurrentSkipListMap在发送时进行存储,

回调函数中,如果处理成功,则删除。

最终返回未处理成功的map集合

Exchange

工作原理

交换机根据RoutingKey绑定队列,发给指定的消费者

类型

直接(direct)

  • 路由交换机

交换机发送给指定RoutingKey的对象

主题(topic)

在绑定RuoutingKey的时候,加上前后通配符*..,只要符合就能接收到

当一个队列绑定未#,则接收所有消息。没有符号出现则与direct一样

标题(headers)

扇出(fanout)

  • 发布交换机

将消息发送给所有绑定自己的队列,无论RoutingKey是否相同

channel.exchangeDeclare("Name", "fanout");
String queue = channel.queueDeclare().getQueue();
// 队列名 交换机名 RoutingKey
channel.exchangeBind(queue, "Name", "");

死信队列

消息发生异常

  • TTl到期
  • 队列达到最大长度
  • 消息被拒绝

// 创建普通队列,如果想要在失败后加入死信队列,需要加入一个map集合存放的arg参数,
Map<String, Object> map = new HashMap<>();
map.put("x-dead-letter-exchange", "dead");
// 绑定队列到死信RoutinKey
map.put("x-dead-letter-routing-key", "list");

channel.queueDeclare("queue", false, false, false, map);
AMQP.Basicproperties priperties = 
    new AMQP.Basicproperties.builder().expiration("时间).buid

channel.baicPublish(,,,,,)

延迟队列

到规定的时间获取数据

整合SpringBoot

  • 引入依赖
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
  • 配置文件
// 创建交换机
@Bean("xExchange") // 直接交换机
public DirectExchange xExchange() {
   return new DirectExchange(NORMAL_EXCHANGE);
}

// 创建队列,创建时添加条件,包括绑定死信交换机和RoutingKey,但是正常的绑定在Binding方法中
@Bean("queueA")
public Queue queueA() {
    // 用map存放条件
    Map<String, Object> map = new HashMap<>(3);
    // 绑定死信交换机,设置RoutingKey
    map.put("x-dead-letter-exchange", DEAD_EXCHANGE);
    map.put("x-dead-letter-routing-key", "YD");
    // 设置存活时间
    map.put("x-message-ttl", 10000);
    return QueueBuilder.durable(NORMAL_QUEUE1).withArguments(map).build();
}

//绑定
@Bean
public Binding queueABindX(@Qualifier("queueA") Queue q1,
                            @Qualifier("xExchange") DirectExchange d1){
    return BindingBuilder.bind(q1).to(d1).with("XA");
}
  • 发送消息
@Resource
private RabbitTemplate rabbitTemplate;

@GetMapping("/sendMsg/{msg}")
public void sendMsg(@PathVariable String msg){
    // 开始时间
    log.info("当前时间:{},发送一条信息给两个TTl队列:{}", new Date().toString(),msg);

    // 发送消息 交换机 routingKey 发送的消息
    rabbitTemplate.convertAndSend(NORMAL_EXCHANGE,"XA","消息来自TTL为10秒的消息:"+msg);
    rabbitTemplate.convertAndSend(NORMAL_EXCHANGE,"XB","消息来自TTL为40秒的消息:"+msg);
}
  • 接收消息
@RabbitListener(queues = "QD")
public void receiveD(Message message, Channel channel)throws Exception {
    String msg = new String(message.getBody());
    log.info("当前时间:{},收到死信队列的消息:{}",new Date().toString(),msg);
}

注意:

用死信队列作为延迟队列,他会以第一个进入队列的过期时间为准,

如果第一个任务的过期时间很长。则会导致后米娜过期时间失效。

使用插件解决

使用步骤

  • 配置交换机、队列、绑定routingKey
public class DelayQueueConfig {
    // 死信交换机
    public static final String DELAY_EXCHANGE_NAME = "delayed.exchange";
    // 队列
    public static final String DELAY_QUEUE_NAME = "delayed.queue";
    // routingKey
    public static final String DELAY_ROUTING_KEY = "delayed.routingKey";

    /**
     * 声明交换机
     * 交换机名 、 类型 、 是否需要持久化 、 是否需要自动删除 、 其他的参数
     * @return 死信交换机
     */
    @Bean
    public CustomExchange delayedExchange(){
        Map<String,Object> arg = new HashMap<>();
        // 以直接发送形式的死信延迟队列
        arg.put("x-delayed-message","direct");
        return new CustomExchange(DELAY_EXCHANGE_NAME,"x-delayed-message",
                false,false,arg);
    }

    // 创建队列
    @Bean
    public Queue delayedQueue(){
        return new Queue(DELAY_QUEUE_NAME);
    }

    // 队列绑定
    public Binding delayedQueueBindingDelayedExchange(@Qualifier Queue delayedQueue,
                                                      @Qualifier CustomExchange delayedExchange){
        return BindingBuilder.bind(delayedQueue).to(delayedExchange).with(DELAY_ROUTING_KEY).noargs();
    }
}
  • 发送消息
@GetMapping("/delayed/{msg}/{delayTime}")
    public void sendDelayedMsg(@PathVariable String msg,
                               @PathVariable Integer delayTime){
        log.info("当前时间:{},发送一条{}浩渺的信息给延迟队列{}", new Date().toString(),delayTime,msg);
        //发送消息
        rabbitTemplate.convertAndSend(DelayQueueConfig.DELAY_EXCHANGE_NAME,
                DelayQueueConfig.DELAY_ROUTING_KEY, msg,message->{
                   message.getMessageProperties().setDelay(delayTime);
                   return message;
                });
    }
  • 接收消息
@Component
@Slf4j
public class DelayedQueueConsumer {
    // 监听消息
    @RabbitListener(queues = DelayQueueConfig.DELAY_QUEUE_NAME)
    public void receiveDelayQueue(Message message){
        String msg = new String(message.getBody());
        log.info("当前时间:{},收到消息{}",new Date().toString(),msg);
    }
}

消息确认和回退

消息确认:生产者和交换机之间的

消息回退:交换机和信道之间的

  • 配置文件中打开消息确认
#    消息确认
    publisher-confirm-type: correlated

#    消息回退
    publisher-returns: true
  • 自定义配置类,注入进spring框架

实现RabbitTemplate的ConfirmCallback和ReturnCallback方法,并通过set方法注入回RabbitTemplate中