消息中间件RabbitMQ个人使用的一些心得

Tips

欢迎指点!
说到RabbitMQ消息中间件,这里就可以小小的扩展一下其他消息中间件。比如有RocketMQ、RabbitMQ、ActiveMQ、Kafka。但更多中小型公司都会去使用到RabbitMQ。

什么是消息中间件?

中间件被描述为为应用程序提供操作系统所提供的服务之外的服务,简化应用程序的通信、输入输出的开发,使他们专注于自己的业务逻辑。

什么是RabbitMQ?

RabbitMQ 它是一个由 Erlang 语言开发的 AMQP 的开源实现。何为AMQP呢?
AMQP :Advanced Message Queue Protocol,高级消息队列协议。它是应用层协议的一个开放标准,为面向消息的中间件设计,基于此协议的客户端与消息中间件可传递消息,并不受产品、开发语言等条件的限制。主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。

RabbitMQ由哪些组成?

Producer:消息的生产者
Consumer:消息的接收者
Broker:集群中的一个服务节点或RabbitMQ服务实例。我把它称为服务器
Queue:队列。很重要的,所有的消息最终都会扔到队列里面,再由消费者去消费
RoutingKey:路由key,需要通过key与队列进行绑定起来才会有作用
Exchange:交换器。生产者将消息发送到broker再到交换器,然后通过key去路由到指定队列,没有路由件的话直接发送到队列。
Exchange交换器类型:
fanout:它会把所有发送到该交换器的消息路由到所有与该交换器绑定的队列中。
direct:他会通过路由key去做发送,通过key找队列
topic:topic是模糊匹配,可以说成direct的功能加强:

RabbitMQ 6 种工作模式

  1. simple简单模式
    在这里插入图片描述
    消息产生者将消息放入队列
    消息的消费者(consumer) 监听(while) 消息队列,如果队列中有消息,就消费掉,消息被拿走后,自动从队列中删除(隐患 消息可能没有被消费者正确处理,已经从队列中消失了,造成消息的丢失)应用场景:聊天(中间有一个过度的服务器;p端,c端)

  2. work工作模式(资源的竞争)
    在这里插入图片描述

消息产生者将消息放入队列消费者可以有多个,消费者1,消费者2,同时监听同一个队列,消息被消费者C1 C2共同争抢当前的消息队列内容,谁先拿到谁负责消费消息(隐患,高并发情况下,默认会产生某一个消息被多个消费者共同使用,可以设置一个开关(syncronize,与同步锁的性能不一样) 保证一条消息只能被一个消费者使用)
应用场景:红包;大项目中的资源调度(任务分配系统不需知道哪一个任务执行系统在空闲,直接将任务扔到消息队列中,空闲的系统自动争抢)

  1. publish/subscribe发布订阅(共享资源)
    在这里插入图片描述
    X代表交换机rabbitMQ内部组件,erlang 消息产生者是代码完成,代码的执行效率不高,消息产生者将消息放入交换机,交换机发布订阅把消息发送到所有消息队列中,对应消息队列的消费者拿到消息进行消费
    相关场景:邮件群发,群聊天,广播(广告)

  2. routing路由模式
    在这里插入图片描述
    消息生产者将消息发送给交换机按照路由判断,路由是字符串(info) 当前产生的消息携带路由字符(对象的方法),交换机根据路由的key,只能匹配上路由key对应的消息队列,对应的消费者才能消费消息;
    根据业务功能定义路由字符串
    从系统的代码逻辑中获取对应的功能字符串,将消息任务扔到对应的队列中业务场景:error 通知;EXCEPTION;错误通知的功能;传统意义的错误通知;客户通知;利用key路由,可以将程序中的错误封装成消息传入到消息队列中,开发者可以自定义消费者,实时接收错误;

5.topic 主题模式(路由模式的一种)
在这里插入图片描述
星号井号代表通配符
星号代表多个单词,井号代表一个单词
路由功能添加模糊匹配
消息产生者产生消息,把消息交给交换机
交换机根据key的规则模糊匹配到对应的队列,由队列的监听消费者接收消息消费

6.RPC :这个你们可以另外去了解

使用RabbitMQ的优点有哪些呢?

解耦:举个简单的例子,用户注册成功,要做新增积分、发送emil、发送短信、微信推送等操作。如果将上述代码全部放在注册逻辑代码中,就会显得十分臃肿,不仅仅不利于维护,也会拖慢响应速度,使用到mq就不会出现这种情况,各司其职。
消息异步:加快业务的响应时间。
流量消峰:这也是很重要的一个核心功能,它可堆积大量的消息,当上游系统的吞吐能力远高于下游系统,在流量洪峰时可能会冲垮下游系统,消息中间件可以在峰值时堆积消息,而在峰值过去后下游系统慢慢消费消息解决流量洪峰的问题,典型的场景就是秒杀系统的设计。
消息的顺序性:消息中间件采用的是队列技术,消息队列可以保证消息的先进先出,能保证消息顺序执行。
消息的可靠性:消息中间件有消费确认机制(ACK),在收到成功被消费的确认消息之后才会把消息从队列中删除,并且消息中间件有本地刷盘存储功能。
解决分布式事物复杂性

RocketMQ、RabbitMQ、ActiveMQ的吞吐量相比!

阿里的rocketmq:吞吐量在11.6w/s
使用Erlang语言开发的rabbitmq:吞吐量5.95w/s
Apache出品的activemq:吞吐相对低

SpringBoot2.1.7.RELEASE整合到RabbitMQ!

因为公司项目大概是19年开始开发的,所以SpringBoot版本稍微低了一点!!!
Rabbitmq是基于Erlang语言开发的,所以我们要保证环境必须要安装Erlang,这里就不说安装这个语言了,因为之前自己安装Erlang还是请同事帮忙的,注意一下版本对应就好啦!

1.导入RabbitMQ的依赖

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

2.配置RabbitMQ

1.在yml配置文件中配置一下

  rabbitmq:
    host: ip地址
    port: 5672 (默认的5672)
    username: 用户名
    password: 密码
    #开启消息发送确认机制,默认为false
    #如果没有本条配置信息,当消费者收到生产者发送的消息后,生产者无法收到确认成功的回调信息
    publisher-confirms: true     # 开启确认是否到达exchange
    #支持消息发送失败返回队列,默认为false
    publisher-returns:  true     # 开启发送到队列中的认证
    template:  # 消息发送失败返回给生产者
      mandatory: false
      #消息确认机制
    listener:
      simple:
        #自动签收auto  手动 manual
        acknowledge-mode: MANUAL
        retry:
          enabled: true #是否开启消费者重试(为false时关闭消费者重试,这时消费端代码异常会一直重复收到消息)
          max-attempts: 3 #最大重试次数
          initial-interval: 5000 #重试间隔时间(单位毫秒)
          max-interval: 2000 #重试最大时间间隔(单位毫秒)
          multiplier: 2 #应用于前一重试间隔的乘法器。

2.需要自己写个配置类,去定义一些队列、交换机、路由键的一些绑定关系,直接贴的我项目中使用的。减少了大部分代码

我这边使用到value注解的原因因为多服务上面跑,用到了nginx、websocket。websocket每建立一个连接,是有唯一的一个链接通道,它不支持序列化,就算存进去了,取出来也是不能发送消息的。因此你不知道它反向代理到哪台服务器上面。所以是通过在yml中配置的。看你们的业务需求,这边你们可以使用一些类变量或实例变量去定义

@Slf4j
@Configuration
public class RabbitConfig {
    public static final String EXCHANGE_NAME = "serviceExchange"; //交换器名称
    public static final String DLX_EXCHANGE_NAME = "serviceDlxExchange";   // 死信交换机

    /*   正常消息队列   */
    @Value("${mq.serviceQuery}")
    public String QUEUE_NAME;                //  队列名称
    @Value("${mq.serviceKey}")
    public String ROUTING_KEY;               //  路由键

    // ---------------------------------------- 2分钟缓存队列、死信队列
    @Value("${mq.bufferTwoQuery}")
    public String BUFFER_TWO_MINUTES_QUERY;                //  两分钟缓存队列
    @Value("${mq.bufferTwoKey}")
    public String BUFFER_TWO_MINUTES_KEY;                  //  两分钟缓存队列的路由key

    @Value("${mq.twoMinutesDlxQuery}")
    public String TWO_MINUTES_DLX_QUERY;                   //  两分钟死信队列
    @Value("${mq.twoMinutesDlxKey}")
    public String TWO_MINUTES_DLX_KEY;                     //  两分钟死信队列的路由key

    // 正常聊天消息队列
    @Bean
    public Queue normalQueue() {
        // Map<String,Object> map = new HashMap<>();
        // map.put("x-message-ttl",5 * 1000); // 统一为整个队列的所有消息设置生命周期(单位为毫秒)
        // Map<String, Object> args = new HashMap<>(2);
        //x-dead-letter-exchange    这里声明当前队列绑定的死信交换机
        /*args.put("x-dead-letter-exchange", DEAD_LETTER_EXCHANGE);
        //x-dead-letter-routing-key  这里声明当前队列的死信路由key
        args.put("x-dead-letter-routing-key", DEAD_LETTER_QUEUE_A_ROUTING_KEY);*/
        Queue queue = new Queue(QUEUE_NAME,true,false,false);
        return queue;
    }

    //------------------------------------- 交换机、死信交换机 ----------------------------
    @Bean
    DirectExchange hydExchange() {
        return new DirectExchange(EXCHANGE_NAME);
    }
    @Bean
    DirectExchange hydDlxExchange() {
        return new DirectExchange(DLX_EXCHANGE_NAME);
    }

    // 正常消息队列跟交换机绑定
    @Bean
    Binding bindingExchangeMessage1s() {
        return BindingBuilder.bind(normalQueue()).to(hydExchange()).with(ROUTING_KEY);
    }


    /**
     * 配置消息确认
     * @param connectionFactory
     * @return
     */
    @Bean
    public RabbitTemplate createRabbitTemplate(ConnectionFactory connectionFactory){
        log.info("=================监控消息状态执行了=======================");
        RabbitTemplate rabbitTemplate = new RabbitTemplate();
        rabbitTemplate.setConnectionFactory(connectionFactory);
        //设置开启Mandatory,才能触发回调函数,无论消息推送结果怎么样都强制调用回调函数
        rabbitTemplate.setMandatory(true);
        // 消息是否到达转换机
        rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
            log.info("ConfirmCallback:     "+"相关数据:"+correlationData);
            log.info("ConfirmCallback:     "+"确认情况:"+ack);
            log.info("ConfirmCallback:     "+"原因:"+cause);
        });
        // 消息是否到达队列,未到达队列执行
        rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
            log.info("ReturnCallback:     "+"消息:"+message);
            log.info("ReturnCallback:     "+"回应码:"+replyCode);
            log.info("ReturnCallback:     "+"回应信息:"+replyText);
            log.info("ReturnCallback:     "+"交换机:"+exchange);
            log.info("ReturnCallback:     "+"路由键:"+routingKey);
        });
        return rabbitTemplate;
    }

    // ===========***************************************************************************************** 缓存队列 -正常队列 ===============================


    /**
     * 两分钟缓存队列
     * @return
     */
    @Bean
    public Queue bufferTwoQueue() {
        //关联2分钟消息队列
        Map<String,Object> map = new HashMap<>();
        map.put("x-dead-letter-exchange",DLX_EXCHANGE_NAME);  // 死信交换机
        map.put("x-dead-letter-routing-key",TWO_MINUTES_DLX_KEY); // 死信路由键
        map.put("x-message-ttl",120 * 1000); // 设置两分钟过期时间
        Queue queue = new Queue(BUFFER_TWO_MINUTES_QUERY,true,false,false,map);
        return  queue;
    }
  
    // *****************************************************************************************==========================      死信队列  --- 直接做操作处理
    /**
     * 处理两分钟消息队列
     * @return
     */
    @Bean
    public Queue twoDlxQueue(){
        // 关联死信队列
        //Map<String,Object> map = new HashMap<>();
        //map.put("x-dead-letter-exchange",DLX_EXCHANGE_NAME);  // 死信交换机
        //map.put("x-dead-letter-routing-key",DLX_ROUTING_KEY1); // 死信路由键
        //map.put("x-message-ttl",10 * 1000); // 2s 未被确认则进入死信队列(单位是毫秒)
        // map.put("x-max-length", 10);//生命队列的最大长度,超过长度的消息进入死信队列
        Queue queue = new Queue(TWO_MINUTES_DLX_QUERY,true,false,false);
        return queue;
    }




    // ==================== ***************************************             队列、交换机、路由键绑定 (只绑定正常的,绑定缓存队列)
    /**
     * 两分钟缓存消息队列跟交换机绑定
     * @return
     */
    @Bean
    Binding bindingExchangeMessage2() { return BindingBuilder.bind(bufferTwoQueue()).to(hydExchange()).with(BUFFER_TWO_MINUTES_KEY); }



    // ============================  ***************************************            死信队列做绑定
    /**
     * 两分钟消息队列跟交换机绑定
     * @return
     */
    @Bean
    Binding bindingExchangeMessage6() { return BindingBuilder.bind(twoDlxQueue()).to(hydDlxExchange()).with(TWO_MINUTES_DLX_KEY); }

3.创建生产者、消费者

// 生产者
  	// 获取到RabbitTemplate 
    private static RabbitTemplate rabbitTemplate = SpringUtils.getBean(RabbitTemplate.class);
    // 发送方法,个人建议不用工作模式中的直连模式。这就是生产者
    rabbitTemplate.convertAndSend(交换机名, 路由key, 消息体);
// 消费者
@Slf4j
@Component
public class BufferListener {
    /**
     * 消费者队列
     */
    @RabbitListener(queues = "${mq.twoMinutesDlxQuery}")
    public void twoListener(Message message, Channel channel) throws Exception {
        channel.basicAck(deliveryTag, false);
    }
}

使用注意事项与提醒

----------------------------------------channel参数解释--------------------------------------------------
1、channel.basicAck
basicAck:表示成功确认,使用此回执方法后,消息会被rabbitmq broker 删除。

void basicAck(long deliveryTag, boolean multiple)
deliveryTag:表示消息投递序号,每次消费消息或者消息重新投递后,deliveryTag都会增加。手动消息确认模式下,我们可以对指定deliveryTag的消息进行ack、nack、reject等操作。

multiple:是否批量确认,值为 true 则会一次性 ack所有小于当前消息 deliveryTag 的消息。

举个栗子: 假设我先发送三条消息deliveryTag分别是5、6、7,可它们都没有被确认,当我发第四条消息此时deliveryTag为8,multiple设置为 true,会将5、6、7、8的消息全部进行确认。

2、channel.basicNack
basicNack :表示失败确认,一般在消费消息业务异常时用到此方法,可以将消息重新投递入队列。

void basicNack(long deliveryTag, boolean multiple, boolean requeue)
deliveryTag:表示消息投递序号。

multiple:是否批量确认。

requeue:值为 true 消息将重新入队列。

3、channel.basicReject
basicReject:拒绝消息,与basicNack区别在于不能进行批量操作,其他用法很相似。

void basicReject(long deliveryTag, boolean requeue)
deliveryTag:表示消息投递序号。
requeue:值为 true 消息将重新入队列。
----------------------------------------Queue 参数解释--------------------------------------------------
Queue queue = new Queue(BUFFER_FOUR_MINUTES_QUERY,true,false,false,map);
map就是一个hashmap,里面配置消息过期、死信队列等等,可以看配置文件里面的代码,有说明
!!!一个队列是一个消费者去处理的,如果有多个消费者消费同一个队列的话,一条消息只会被一个消费者所消费
这边说说 Queue里面需要的参数:
queue :队列名称
durable :队列是否持久化,false:队列在内存中,服务器挂掉后,队列就没了;true:服务器重启后,队列将会重新生成.注意:只是队列持久化,不代表队列中的消息持久化
exclusive :队列是否专属,专属的范围针对的是连接,也就是说,一个连接下面的多个信道是可见的.对于其他连接是不可见的.连接断开后,该队列会被删除.注意,不是信道断开,是连接断开.并且,就算设置成了持久化,也会删除
autoDelete: 如果所有消费者都断开连接了,是否自动删除.如果还没有消费者从该队列获取过消息或者监听该队列,那么该队列不会删除.只有在有消费者从该队列获取过消息后,该队列才有可能自动删除(当所有消费者都断开连接,不管消息是否获取完)
在这里插入图片描述
----------------------------------------其他解释--------------------------------------------------
说说死信队列:他其实也是普通队列,只是我们把它成为死信队列,它可以处理一些不重要忽略的信息。这边可以用到的下单没有支付,15分钟取消订单的场景或者其他操作。
消息确认机制:这个其实也是队列中很重要的一个功能,消费者的服务器在处理消息的时候出现异常,那么可能这条正在处理的消息就没有完成消息消费,数据就会丢失。它能确保数据不会丢失
消息重复消费:这边可以通过redis 去做,将消息的唯一id存入reids。再做判断即可
还有一点就是如果不去配置重发次数限制的话,在生产者没有接收到消费者确认的消息后,会导致mq一直发送消息,会造成服务器卡死,这个还是要配置的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值