前言

MQ(Message Queue)就是消息队列,其有点有很多:解耦、异步、削峰等等,本文来聊一下RabbitMQ的一些概念以及使用。

RabbitMq

案例

Springboot整合RabbitMQ简单案例

基本概念

  • Exchange:消息交换机,它指定消息按什么规则,路由到哪个队列。
  • Queue:消息队列载体,每个消息都会被投入到一个或多个队列。
  • Binding:绑定,它的作用就是把exchange和queue按照路由规则绑定起来。
  • Routing Key:路由关键字,exchange根据这个关键字进行消息投递。
  • Producer:消息生产者,就是投递消息的程序。
  • Consumer:消息消费者,就是接受消息的程序。

发布消息到RabbitMQ需要经过两步:

  1. producer → exchange
  2. exchange 根据 exchange 的类型和 routing key 确定将消息投递到哪个队列

工作流程

了解了RabbitMQ的一些概念,我们来捋捋使用RabbitMQ的流程:

  1. 创建Exchange
  2. 创建Queue
  3. 将Queue绑定进Exchange中(此处会设置routing key)
  4. 生产者发布消息
  5. 消费者订阅消息

交换机(Exchange)

交换机可以绑定队列,绑定时可以给队列指定路由(Routing key)和参数(Arguments)

所有的消息发送都是经过交换机转发到队列的,而不是直接到队列中

交换机类型:

  • direct

    根据确定的路由(routing key)转发消息到队列中(一条消息可以发到多个队列,只要路由相同)

  • fanout

    路由无效,只要和该交换机绑定的队列,都能接收到消息

  • topic

    允许路由使用*和#来进行模糊匹配

    *表示一个单词

    表示任意数量(零个或多个)单词

    例如:如果队列的路由为com.# 那么往交换机发消息是,路由填com.ccc 队列就可以收到消息

  • headers

    忽略路由,由参数(Arguments)来确定转发的队列

消息过期时间TTL

有两种方式设置TTL,创建队列时设置整个队列的TTL或者在发送消息时单独设置每条消息的TTL,消息存活时间取两者的最小值。

  1. 创建队列时设置

    是消息的存活时间,不是队列的存活时间,别搞混了。

    @Bean
    public Queue queue(){
    Map<String, Object> args = new HashMap<>();
    args.put("x-message-ttl", 5000); // 设置队列中的消息5秒过期
    return new Queue("queueName",true, false, false, args);
    }
  2. 发送消息时设置

    public void makeOrder(String userid,String productid,int num){
    String exchangeName = "ttl_exchange";
    String routingKey = "ttlmessage";
    //给消息设置过期时间
    MessagePostProcessor messagePostProcessor = new MessagePostProcessor(){
    public Message postProcessMessage(Message message){
    // 设置消息5秒过期
    message.getMessageProperties().setExpiration("5000");
    return message;
    }
    }
    rabbitTemplate.convertAndSend(exchangeName,routingKey,"message",messagePostProcessor);
    }

死信队列

死信队列也是一个正常队列,只是当绑定了死信队列的队列满足相应条件,就会将满足条件的消息转移到死信队列中。

进入死信队列的条件:

  1. 消息被拒绝
  2. 消息过期(超时)
  3. 队列达到最大长度

死信队列的配置:

  1. 按照正常步骤定义一个队列(交换机、队列、绑定)

  2. 给需要绑定死信队列的队列添加x-dead-letter-exchange(死信队列的交换机)和x-dead-letter-routing-key(死信队列的路由)参数

    @Bean
    public Queue queue(){
    Map<String, Object> args = new HashMap<>();
    args.put("x-dead-letter-exchange", "死信队列交换机名称");
    args.put("x-dead-letter-routing-key", "死信队列路由");
    return new Queue("queueName",true, false, false, args);
    }

如何保证MQ消息正确送达与消费

可靠性生产和推送

步骤:

  1. 发送消息前数据库保存MQ消息发送日志
  2. MQ消息发送后使用回调更新日志状态

实现:

上面我们讲了,发布消息到RabbitMQ需要经过两步:

producer → exchange

exchange 根据 exchange 的类型和 routing key 确定将消息投递到哪个队列

所以,发布消息的确认也分两个部分,以下是确认步骤:

  1. 修改MQ应答机制(yml)

    spring:
    rabbitmq:
    username: rmq
    password: 123456
    virtual-host: /
    host: localhost
    port: 5672
    # 发送消息确认,producer -> exchange
    publisher-confirm-type: correlated
    # 发送消息确认,exchange -> queue
    publisher-returns: true
  2. 新增mq的回调方法

    /**
    * PostConstruct注解好多人以为是Spring提供的。其实是Java自己的注解。
    * Java中该注解的说明:@PostConstruct该注解被用来修饰一个非静态的void()方法。
    * 被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。
    * PostConstruct在构造函数之后执行,init()方法之前执行。
    * Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注释的方法)
    */
    @PostConstruct
    private void regCallBack() {
    // producer -> exchange 成功或失败都会触发此回调
    rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
    // 这个id是在消息发送的时候传入的
    String id = correlationData.getId();
    // 如果ack为true代表消息被mq成功接收
    if (!ack) {
    // 应答失败,修改日志状态
    System.out.println("exchange 应答失败,做失败处理!");
    } else {
    // 应答成功,修改日志状态
    System.out.println("exchange 成功处理");
    }
    }
    }); // 这个回调只有exchange -> queue 失败时才会触发
    rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
    @Override
    public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
    System.out.println("exchange -> queue 发送失败");
    }
    });
    }
  3. 修改MQ发送消息的方法,增加日志id的传递

    String correlationId = "这是日志id";
    rabbitTemplate.convertAndSend(exchange, routeKey, message, new MessagePostProcessor() {
    @Override
    public Message postProcessMessage(Message message) throws AmqpException {
    // 消费者需要correlationId才做这个处理
    message.getMessageProperties().setCorrelationId(correlationId);
    return message;
    }
    }, new CorrelationData(correlationId));
    // 如果消费者不需要获取correlationId,则用下面这种即可
    rabbitTemplate.convertAndSend(exchange, routeKey, msg, new CorrelationData(correlationId));

可靠性消费

步骤:

  1. 开启手动应答
  2. 监听器增加手动应答逻辑

实现:

  1. 开启手动应答

    spring:
    rabbitmq:
    username: rmq
    password: 123456
    virtual-host: /
    host: localhost
    port: 5672
    listener:
    simple:
    acknowledge-mode: manual # 将自动应答ack模式改成手动应答

    acknowledge-mode有三种类型:

    • nome:不进行ack,rabbitmq默认消费者正确处理所有请求
    • munual:手动确认
    • auto:自动确认消息(默认类型)。如果消费者抛出异常,则消息重回队列。
  2. 监听器增加手动应答逻辑

    @RabbitListener(queues = {"队列名字"})
    public void messageConsumer(String orderMsg, Channel channel, @Headers Map<String,Object> headers) throws Exception{
    // 需要producer做相应处理,consumer才能拿到correlationId
    String correlationId = messages.getMessageProperties().getCorrelationId();
    System.out.println("消息为:" + orderMsg);
    long tag = Long.parseLong(headers.get(AmqpHeaders.DELIVERY_TAG).toString());
    try {
    // 消费成功,进行确认
    channel.basicAck(tag, false);
    } catch (IOException e) {
    // 消费失败,重发
    // requeue代表是否重发,为false则直接将消息丢弃,有死信就进入死信队列
    channel.basicNack(tag, false, true);
    }
    }

总结

本文介绍了RabbitMQ的一些概念和简单使用,有不少东西其实是没有讲清楚的,比如publisher-confirm-type和acknowledge-mode的几种类型的区别等等。主要是在官方文档找不到相关的细致描述,查文档的能力还是有待提高。。。


参考资料

RabbitMq 技术文档 - 腾讯云开发者社区-腾讯云 (tencent.com)

Spring AMQP

RabbitMQ个人实践的更多相关文章

  1. RabbitMQ双活实践(转)

    有货RabbitMQ双活实践   消息服务中间件在日常工作中用途很多,如业务之间的解耦,其中 RabbitMQ 是比较容易上手且企业使用比较广泛的一种,本文主要介绍有货在使用 RabbitMQ 的一些 ...

  2. RabbitMQ安装实践

    背景: 最近一个项目的测试环境需要用到rabbitMQ,但运维和开发都没时间,于是自己试着安装了一发,发现安装很简单,记一笔如下: 安装步骤 查看官网上有不同的安装方法,可使用下载安装包或者直接通过其 ...

  3. Java 小记 — RabbitMQ 的实践与思考

    前言 本篇随笔将汇总一些我对消息队列 RabbitMQ 的认识,顺便谈谈其在高并发和秒杀系统中的具体应用. 1. 预备示例 想了下,还是先抛出一个简单示例,随后再根据其具体应用场景进行扩展,我觉得这样 ...

  4. RabbitMQ最佳实践

    在使用消息机制时,我们通常需要考虑以下几个问题: 消息不能丢失 保证消息一定能投递到目的地 保证业务处理和消息发送/消费的一致性 本文以RabbitMQ为例,讨论如何解决以上问题. 消息持久化 如果希 ...

  5. 后端开发实践系列之三——事件驱动架构(EDA)编码实践

    在本系列的前两篇文章中,笔者分别讲到了后端项目的代码模板和DDD编码实践,在本文中,我将继续以编码实践的方式分享如何落地事件驱动架构. 单纯地讲事件驱动架构(Event Driven Architec ...

  6. 2.RabbitMQ 的可靠性消息的发送

      本篇包含 1. RabbitMQ 的可靠性消息的发送 2. RabbitMQ 集群的原理与高可用架构的搭建 3. RabbitMQ 的实践经验   上篇包含 1.MQ 的本质,MQ 的作用 2.R ...

  7. RabbitMQ入门指南

    消息队列(Message Queue,以下简称MQ)常用于异步系统的数据传递.若不用MQ,我们只能[在应用层]使用轮询或接口回调等方式处理,这在效率或耦合度上是难以让人满意的.当然我们也可以在系统间保 ...

  8. Java进阶专题(二十) 消息中间件架构体系(2)-- RabbitMQ研究

    前言 接上文,这个继续介绍RabbitMQ,并理解其底层原理. 介绍 RabbitMQ是由erlang语言开发,基于AMQP(Advanced Message Queue 高级消息队列协议)协议实现的 ...

  9. 消息队列面试题、RabbitMQ面试题、Kafka面试题、RocketMQ面试题 (史上最全、持续更新、吐血推荐)

    文章很长,建议收藏起来,慢慢读! 疯狂创客圈为小伙伴奉上以下珍贵的学习资源: 疯狂创客圈 经典图书 : <Netty Zookeeper Redis 高并发实战> 面试必备 + 大厂必备 ...

  10. 【Java进阶面试系列之一】哥们,你们的系统架构中为什么要引入消息中间件?

    转: [Java进阶面试系列之一]哥们,你们的系统架构中为什么要引入消息中间件? **这篇文章开始,我们把消息中间件这块高频的面试题给大家说一下,也会涵盖一些MQ中间件常见的技术问题. 这里大家可以关 ...

随机推荐

  1. [Python]-字典-实践经验总结

    字典是Python中常用的一个数据类型. 与列表有相似的用法,表现在列表的下标和字典的键值可以通过相似的方式读取数据: list_name[0] = value dict_name['key'] = ...

  2. k8s实际操作中的小知识点

    1.批量执行yaml文件 # 把所有要执行的yaml文件放在同一个目录下,并且切换到这个目录下 kubectl apply -f . 2.利用pod的亲和和反亲和功能把pod调度到不同的node上 亲 ...

  3. 使用 Dockerfile 的一些最佳实践

  4. CentOS7部署FastDFS+nginx模块

    软件下载 # 已经事先把所需软件下载好并上传到/usr/local/src目录了 https://github.com/happyfish100/libfastcommon/archive/V1.0. ...

  5. 查看pod创建时使用yaml文件内容

    除了 kubectl describe pod 以外,另一种获取 Pod 额外信息(除了 kubectl get pod)的方法 是给 kubectl get pod 增加 -o yaml 输出格式参 ...

  6. Elasticsearch准实时索引实现(数据写入到es分片并存储到文件中的过程)

    溢写到文件系统缓存 当数据写入到ES分片时,会首先写入到内存中,然后通过内存的buffer生成一个segment,并刷到文件系统缓存中,数据可以被检索(注意不是直接刷到磁盘) ES中默认1秒,refr ...

  7. NAT模式下的虚拟机连接主机网络

    基于NAT模式的VMware虚拟机(Linux CentOS 7)连接主机(Windows 11)网络 一.什么是NAT模式 虚拟机连接主机网络的三种方式: Bridged(桥接) NAT(网络地址转 ...

  8. ABC260 作战总结

    ABC260 作战总结 今后开始写一些模拟赛外的其他比赛的总结(也许有题解?). 开场点到另一场\(\text{ARC}\)去了,耽误了点时间,切完前四题发现已经过了\(37\)分钟了,看来自己读题+ ...

  9. Java注解(2):实现自己的ORM

    搞过Java的码农都知道,在J2EE开发中一个(确切地说,应该是一类)很重要的框架,那就是ORM(Object Relational Mapping,对象关系映射).它把Java中的类和数据库中的表关 ...

  10. YOLOv5】LabVIEW+OpenVINO让你的YOLOv5在CPU上飞起来

    前言 上一篇博客给大家介绍了使用opencv加载YOLOv5的onnx模型,但我们发现使用CPU进行推理检测确实有些慢,那难道在CPU上就不能愉快地进行物体识别了吗?当然可以啦,这不LabVIEW和O ...