RabbitMQ队列(1)

​ 先从概念解释上搞清楚这个定义,死信,顾名思义就是无法被消费的消息,字面意思可以这样理解,一般来说,producer将消息投递到broker或者直接到queue里了,consumer 从 queue取出消息进行消费,但某些时候由于特定的原因导致queue中的某些消息无法被消费,这样的消息如果没有后续的处理,就变成了死信,有死信自然就有了死信队列。
​ 应用场景:为了保证订单业务的消息数据不丢失,需要使用到RabbitMQ的死信队列机制,当消息消费发生异常时,将消息投入死信队列中.还有比如说:用户在商城下单成功并点击去支付后在指定时间未支付时自动失效

1.1、死信的来源
  • 消息TTL过期;
  • 队列达到最大长度(队列满了,无法再添加数据到mq.中);
  • 消息被拒绝(basic.reject 或 basic.nack)并且requeue=false;
1.2、死信实战

架构图

image-20240301203605503

1.3、消费者01
//死信队列
public class DeadMessageConsumer01 {
    //普通交换机
    public static final String NORMAL\_EXCHANGE = "normal\_exchange";
    //死信交换机
    public static final String DEAD\_EXCHANGE = "dead\_exchange";
    //普通队列
    public static final String NORMAL\_QUEUE = "normal\_queue";
    //死信队列
    public static final String DEAD\_QUEUE = "dead\_queue";

    public static void main(String[] args) throws IOException, TimeoutException {
        Channel channel = RabbitMQUtils.getChannel();
        //声明普通和死信交换机
        channel.exchangeDeclare(NORMAL\_EXCHANGE, BuiltinExchangeType.DIRECT);
        channel.exchangeDeclare(DEAD\_EXCHANGE, BuiltinExchangeType.DIRECT);
        //声明普通队列,这一次需要使用参数了
        Map<String, Object> arguments = new HashMap<>();
        //这里就要设置死信到死信交换机上了
        arguments.put("x-dead-letter-exchange",DEAD\_EXCHANGE);
        //设置死信routingKey
        arguments.put("x-dead-letter-routing-key","deadKey");
        channel.queueDeclare(NORMAL\_QUEUE,false,false,false,arguments);
        //声明死信队列
        channel.queueDeclare(DEAD\_QUEUE,false,false,false,null);
        //开始分别将死信和普通交换机与队列绑定
        channel.queueBind(NORMAL\_QUEUE,NORMAL\_EXCHANGE,"normalKey");
        channel.queueBind(DEAD\_QUEUE,DEAD\_EXCHANGE,"deadKey");

        DeliverCallback deliverCallback = (consumerTag,message) ->{
            System.out.println("死信测试普通队列消费者01收到消息:"+new String(message.getBody()));
        };
        channel.basicConsume(NORMAL\_QUEUE,true,deliverCallback,consumerTag -> {});
    }

}

1.4、生产者
//死信队列的生产者
public class DeadMessageProducers {
    //普通交换机
    public static final String NORMAL\_EXCHANGE = "normal\_exchange";

    public static void main(String[] args) throws IOException, TimeoutException {
        Channel channel = RabbitMQUtils.getChannel();
        //死信消息设置TTL过期时间,设置10秒
        AMQP.BasicProperties basicProperties = new AMQP.BasicProperties()
                .builder().expiration("10000").build();
        //发10条消息
        for (int i = 1; i < 11; i++) {
            String message = "dead"+i;
            channel.basicPublish(NORMAL\_EXCHANGE,"normalKey",basicProperties,message.getBytes());
            System.out.println("发送消息成功:"+ message);
        }
    }
}

​ 先把消费者启动,将交换机和队列先声明出来,然后关掉消费者再启动生产者,生产者一直发这10个消息,但是消费者已经停止了,没人消费,所以TTL一到期就会被放进死信队列里,我们在web管理界面就能看到,普通队列的10条消息很快就跑到死信队列中了。

image-20240302110147069

image-20240302111240369

​ 然后现在死信队列里有那10条消息,再来一个专门消费死信消息的消费者2,非常简单,只需要消费死信队列即可。

//死信队列
public class DeadMessageConsumer02 {
    //死信队列
    public static final String DEAD\_QUEUE = "dead\_queue";

    public static void main(String[] args) throws IOException, TimeoutException {
        Channel channel = RabbitMQUtils.getChannel();
        DeliverCallback deliverCallback = (consumerTag,message) ->{
            System.out.println("死信测试普通队列消费者02收到消息:"+new String(message.getBody()));
        };
        channel.basicConsume(DEAD\_QUEUE,true,deliverCallback,consumerTag -> {});
    }
}

启动它消费死信队列

image-20240302111857175

消费完成,看看管理界面,死信消息归0

image-20240302111933926

1.5、队列达到最大长度的死信

​ 在原来的消费者基础上增加一条

//设置队列最大长度
arguments.put("x-max-length",6);

​ 原来的生产者也不需要给消息设置过期时间了直接发送消息即可。

​ 测试之前先将原来的普通队列删除再启动消费者

image-20240302134737969

​ 仍然把消费者停掉,生产者启动,发送10条消息6条正常4条超过了限制会被放到死信队列

image-20240302134915165

1.6、消息被拒死信队列

我们先把原来的消息全部消费掉或者直接删除原来的队列,再演示新的。

消费者做一些修改

DeliverCallback deliverCallback = (consumerTag,message) ->{
    String msg = new String(message.getBody());
    if (msg.equals("dead5")){
        System.out.println("拒绝第5个消息:"+ msg);
        //basicReject第二个参数是是否还放回队列
        channel.basicReject(message.getEnvelope().getDeliveryTag(),false);
    } else {
        System.out.println("死信测试普通队列消费者01收到消息:" + msg);
        channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
    }

};
channel.basicConsume(NORMAL\_QUEUE,false,deliverCallback,consumerTag -> {});

生产者发送10条消息,看到消费者01拒绝了第5个消息,其他的正常收到

image-20240302143440745

通过管理界面看到第5条消息放到了死信队列中

image-20240302143507611

启动专门消费死信队列的消费者02将第5个消息消费。

image-20240302143537357

2、延迟队列

2.1、延迟队列概念

​ 延时队列,队列内部是有序的,最重要的特性就体现在它的延时属性上,延时队列中的元素是希望在指定时间到了以后或之前取出和处理,简单来说,延时队列就是用来存放需要在指定时间被处理的元素的队列。

其实,TTL过期导致的死信队列就是延迟队列

应用场景:

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

​ 这些场景都有一个特点,需要在某个事件发生之后或者之前的指定时间点完成某一项任务,如:发生订单生成事件,在十分钟之后检查该订单支付状态,然后将未支付的订单进行关闭;看起来似乎使用定时任务,一直轮询数据,每秒查一次,取出需要被处理的数据,然后处理不就完事了吗?

​ 如果数据量比较少,确实可以这样做,比如:对于“如果账单一周内未支付则进行自动结算”这样的需求,如果对于时间不是严格限制,而是宽松意义上的一周,那么每天晚上跑个定时任务检查一下所有未支付的账单,确实也是一个可行的方案。

​ 但对于数据量比较大,并且时效性较强的场景,如:“订单十分钟内未支付则关闭“,短期内未支付的订单数据可能会有很多,活动期间甚至会达到百万甚至千万级别,对这么庞大的数据量仍旧使用轮询的方式显然是不可取的,很可能在一秒内无法完成所有订单的检查,同时会给数据库带来很大压力,无法满足业务要求而且性能低下。

2.1.1、RabbitMQ 中的 TTL

​ TTL 是什么呢?TTL 是 RabbitMQ 中一个消息或者队列的属性,表明一条消息或者该队列中的所有 消息的最大存活时间, 单位是毫秒。

​ 换句话说,如果一条消息设置了 TTL 属性或者进入了设置 TTL 属性的队列,那么这 条消息如果在 TTL 设置的时间内没有被消费,则会成为"死信"。如果同时配置了队列的 TTL 和消息的 TTL,那么较小的那个值将会被使用,有两种方式设置 TTL。

  • 消息设置 TTL

例如:

 rabbitTemplate.convertAndSend("X","XC",message,msg -> {
        //发送消息设置消息的TTL
        msg.getMessageProperties().setExpiration(ttl+"000");
        return msg;
    });

  • **队列设置 TTL **

例如:在创建队列的时候设置队列的“x-message-ttl”属性

 //声明普通队列 TTL 为40秒
    @Bean("queueB")
    public Queue queueB(){
        HashMap<String, Object> map = new HashMap<>(3);
        //设置死信交换机
        map.put("x-dead-letter-exchange",Y\_DEAD\_LETTER\_EXCHANGE);
        //设置死信RoutingKey
        map.put("x-dead-letter-routing-key","YD");
        //设置过期时间,10秒
        map.put("x-message-ttl",40000);
        return QueueBuilder.durable(QUEUE\_B).withArguments(map).build();
    }

2.2、整合SpringBoot

添加依赖项


    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <!-- 添加 Spring Boot 版本 -->
        <spring.boot.version>2.3.4.RELEASE</spring.boot.version>
        <!-- 添加 Lombok 版本 -->
        <lombok.version>1.18.20</lombok.version>
        <!-- 添加 Spring AMQP 版本 -->
        <spring.amqp.version>2.1.4.RELEASE</spring.amqp.version>
        <!-- 添加 Springfox Swagger 版本 -->
        <springfox.version>2.9.2</springfox.version>
    </properties>

    <dependencies>
        <!-- RabbitMQ 依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
            <version>${spring.boot.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>${spring.boot.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <version>${spring.boot.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.taobao.arthas</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.80-fix</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
        </dependency>
        <!-- Swagger -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>${springfox.version}</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>${springfox.version}</version>
        </dependency>
        <!-- RabbitMQ 测试依赖 -->
        <dependency>
            <groupId>org.springframework.amqp</groupId>
            <artifactId>spring-rabbit-test</artifactId>
            <version>${spring.amqp.version}</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

创建application.properties文件

spring.rabbitmq.host=192.168.111.28
spring.rabbitmq.port=5672
spring.rabbitmq.username=admin
spring.redis.password=123

swagger2的配置类SwaggerConfig

@Configuration
@EnableSwagger2
public class SwaggerConfig {
    @Bean
    public Docket webApiConfig(){
        return new Docket(DocumentationType.SWAGGER\_2)
                .groupName("webApi")
                .apiInfo(webApiInfo())
                .select()
                .build();
    }
    private ApiInfo webApiInfo(){
        return new ApiInfoBuilder()
                .title("rabbitmq 接口文档")
                .description("本文档描述了 rabbitmq 微服务接口定义")
                .version("1.0")
                .contact(new Contact("zm", "http://zmblog.vip",
                        "3339332352@qq.com"))
                .build();
    }
}

2.3、队列TTL

​ 创建两个队列 QA 和 QB,两者队列 TTL 分别设置为 10S 和 40S,然后在创建一个交换机 X 和死信交 换机 Y,它们的类型都是 direct,创建一个死信队列 QD,它们的绑定关系如下:

image-20240302163555580

2.4、配置文件类代码

TtlQueueConfig

//TTL队列,配置类代码
@Configuration
public class TtlQueueConfig {
    //普通交换机名称
    public static final String X\_EXCHANGE = "X";
    //死信交换机名称
    public static final String Y\_DEAD\_LETTER\_EXCHANGE = "Y";
    //普通队列名称,两个
    public static final String QUEUE\_A = "QA";
    public static final String QUEUE\_B = "QB";
    //死信队列名称
    public static final String DEAD\_LETTER\_QUEUE = "QD";

    //声明普通交换机X\_EXCHANGE,起一个别明xExchange注入bean
    @Bean("xExchange")
    public DirectExchange xExchange(){
        return new DirectExchange(X\_EXCHANGE);
    }
    //声明死信交换机Y\_EXCHANGE,起一个别明yExchange注入bean
    @Bean("yExchange")
    public DirectExchange yExchange(){
        return new DirectExchange(Y\_DEAD\_LETTER\_EXCHANGE);
    }
    //声明普通队列 TTL 为10秒
    @Bean("queueA")
    public Queue queueA(){
        HashMap<String, Object> map = new HashMap<>(3);
        //设置死信交换机
        map.put("x-dead-letter-exchange",Y\_DEAD\_LETTER\_EXCHANGE);
        //设置死信RoutingKey
        map.put("x-dead-letter-routing-key","YD");
        //设置过期时间,10秒
        map.put("x-message-ttl",10000);
        return QueueBuilder.durable(QUEUE\_A).withArguments(map).build();
    }
    //声明普通队列 TTL 为40秒
    @Bean("queueB")
    public Queue queueB(){
        HashMap<String, Object> map = new HashMap<>(3);
        //设置死信交换机
        map.put("x-dead-letter-exchange",Y\_DEAD\_LETTER\_EXCHANGE);
        //设置死信RoutingKey
        map.put("x-dead-letter-routing-key","YD");
        //设置过期时间,10秒
        map.put("x-message-ttl",40000);
        return QueueBuilder.durable(QUEUE\_B).withArguments(map).build();
    }
    //死信队列
    @Bean("queueD")
    public Queue queueD(){
        return QueueBuilder.durable(DEAD\_LETTER\_QUEUE).build();
    }
    //绑定
    @Bean
    public Binding QABindingX(@Qualifier("queueA") Queue queueA,
                              @Qualifier("xExchange") DirectExchange xExchange){
        return BindingBuilder.bind(queueA).to(xExchange).with("XA");
    }
    @Bean
    public Binding QBBindingX(@Qualifier("queueB") Queue queueB,
                              @Qualifier("xExchange") DirectExchange xExchange){
        return BindingBuilder.bind(queueB).to(xExchange).with("XB");
    }
    @Bean
    public Binding QDBindingY(@Qualifier("queueD") Queue queueD,
                              @Qualifier("yExchange") DirectExchange yExchange){
        return BindingBuilder.bind(queueD).to(yExchange).with("YD");
    }
}

生产者controller,DeadLetterProducers

//生产者发送延迟消息
@Slf4j
@RestController
public class DeadLetterProducers {
    //自动注入
    @Autowired
    private RabbitTemplate rabbitTemplate;

    //发消息
    @GetMapping("/sendMsg/{message}")
    public void sendMsg(@PathVariable String message){
        log.info("当前时间:{},发送一条消息给两个TTL队列:{}",new Date().toString(),message);
        rabbitTemplate.convertAndSend("X","XA","消息来自于TTL为10秒的队列:"+message);
        rabbitTemplate.convertAndSend("X","XB","消息来自于TTL为40秒的队列:"+message);
    }
}

消费者:

//消费者
@Slf4j
@Component
public class DeadLetterQueueConsumer {
    //接收消息,定义一个监听器来监听死信队列中的消息
    @RabbitListener(queues = "QD")
    public void receiveD(Message message, Channel channel){
        String s = new String(message.getBody());
        log.info("当前时间:{},收到死信队列的消息:{}",new Date().toString(),s);
    }

}

成功启动

image-20240302203312306

​ 在浏览器上输入localhost:8080/sendMsg/哈哈哈哈哈

​ 发现后台开始输出信息,先是发送的提示信息,然后过了10秒消费者收到了一个信息,然后再过30秒收到了第二条信息。

image-20240302203706023

​ 结果是没有问题的,第一条消息在10S后变成了死信消息,然后被消费者消费掉,第二条消息在40S之后变成了死信消息,然后被消费掉,这样一个延时队列就打造完成了。

​ 不过,如果这样使用的话,岂不是每增加一个新的时间需求,就要新增一个队列,这里只有10S和40S两个时间选项,如果需要一个小时后处理,那么就需要增加TTL为一个小时的队列,如果是预定会议室然后提前通知这样的场景,岂不是要增加无数个队列才能满足需求?

2.5、延迟队列优化

​ 在这里新增了一个队列 QC,它是通用的延迟队列,生产者掌握时间(这是设定消息的过期时间,之前是设定队列的过期时间),绑定关系如下,该队列不设置 TTL 时间

image-20240303104749887

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数大数据工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年大数据全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上大数据开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加VX:vip204888 (备注大数据获取)
img

成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**

因此收集整理了一份《2024年大数据全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
[外链图片转存中…(img-0AN8AuZe-1712859262644)]
[外链图片转存中…(img-yaSTZ7Jo-1712859262645)]
[外链图片转存中…(img-AKehHLNC-1712859262645)]
[外链图片转存中…(img-Qk5wntmh-1712859262645)]
[外链图片转存中…(img-wqLG8vWH-1712859262646)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上大数据开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加VX:vip204888 (备注大数据获取)
[外链图片转存中…(img-PO9Zuzle-1712859262646)]

  • 27
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值