热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

RabbitMQ消息中间件的用法

1.什么是RabbitMQRabbitMQ是一个由erlang开发的AMQP(AdvancedMessageQueue)的开源实现。AMQP的出现其实也是应了广大人民群众的需求,虽然在同步

1.什么是RabbitMQ

RabbitMQ是一个由erlang开发的AMQP(Advanced Message Queue )的开源实现。AMQP 的出现其实也是应了广大人民群众的需求,虽然在同步消息通讯的世界里有很多公开标准(如 COBAR的 IIOP ,或者是 SOAP 等),但是在异步消息处理中却不是这样,只有大企业有一些商业实现(如微软的 MSMQ ,IBM 的 Websphere MQ 等),因此,在 2006 年的 6 月,Cisco 、Redhat、iMatix 等联合制定了 AMQP 的公开标准。 RabbitMQ是由RabbitMQ Technologies Ltd开发并且提供商业支持的。该公司在2010年4月被SpringSource(VMWare的一个部门)收购。在2013年5月被并入Pivotal。其实VMWare,Pivotal和EMC本质上是一家的。不同的是VMWare是独立上市子公司,而Pivotal是整合了EMC的某些资源,现在并没有上市。 RabbitMQ的官网是http://www.rabbitmq.com 百度百科amqp协议介绍https://baike.baidu.com/item/AMQP/8354716?fr=aladdin 注意:RabbitMQ是采用erlang语言开发的,所以必须有erlang环境才可以运行

2.为什么要使用MQ

 

3.常用消息中间件的对比

4.消息队列RabbitMq的五种形式队列

4.1.点对点(简单)的队列

点对点模式:一对一消费,一个生产者投递消息给队列,只能允许有一个消费者进行消费。

注意:如果消费集群的话,会进行均摊消费。前提是服务器的配置相同。

均摊消费的弊端:假如有2台服务器分别为A、B。如果每个消费处理消息的业务时间不相同的情况下,可能对消费者处理慢的服务器不公平(服务器压力大),A处理比B处理时间快,应该A处理的消息多一些,B处理的消息少一些才合理。

队列以先进先出原则进行存放消息集合。生产者投递消息到队列中。

当消费者启动的时候,会与队列服务器建立长连接,当生产者有消息投递到队列的时候,队列会立刻将消息通知给消费者进行消费。

长连接的好处:如果是短链接的话,每次访问都需要建立连接,比较占内存。建立长连接会减少三次握手,提高传输速度。

取消息队列与推送消息队列的区别:

取消息:生产者投递消息到队列中,队列服务器缓存消息。这时候当消费者启动的时候,消费者会去向队列服务器中获取消息。

推消息:当生产者和消费者都启动的时候,生产者向队列投递消息,这时候队列会将消息推送给消费者。

4.2.工作(公平性)队列模式

公平队列的原理:队列服务器向消费者发送消息的时候,消费者采用手动应答模式,队列服务器必须要收到消费者发送ack结果通知之后,才会继续发送下一个消息。

4.3.发布订阅模式

Direct exchange(直连交换机)是根据消息携带的路由键(routing key)将消息投递给对应队列的。

发布订阅实现流程:生产者投递消息给交换机,交换机根据路由策略(routignKey)转发到不同的队列服务器中缓存,然后队列服务器在推送消息给消费者进行消费或者消费者从队列服务器中拉取消息进行消费。

发布订阅实现原理:一对多。

这个队列模式是消息队列中最重要的队列了,其他的都是在它的基础上进行了扩展。 功能实现:一个生产者发送消息,多个消费者获取消息(同样的消息),包括一个生产者,一个交换机,多个队列,多个消费者。

思路解读(重点理解): 

1. 一个生产者,多个消费者

2. 每一个消费者都有自己的一个队列

3. 生产者没有直接发消息到队列中,而是发送到交换机 

4. 每个消费者的队列都绑定到交换机上

5. 消息通过交换机到达每个消费者的队列 该模式就是Fanout Exchange(扇型交换机)将消息路由给绑定到它身上的所有队列 以用户发邮件案例讲解

注意:交换机没有存储消息功能,如果消息发送没有绑定消费队列的交换机,消息则丢失。在消费者没有启动的情况下,生产者投递消息到交换机,这时候交换机不知道把消息转发给哪个消费者,所以消息会消失。因为交换机没有缓存功能,只做转发的功能。

使用场景:用户注册→发送邮件→发送短信。

 

4.4.路由模式RoutingKey

Direct exchange(直连交换机)是根据消息携带的路由键(routing key)将消息投递给对应队列的。

生产者发送消息到交换机并指定一个路由key,消费者队列绑定到交换机时要制定路由key(key匹配就能接受消息,key不匹配就不能接受消息)。

例如:我们可以把路由key设置为insert ,那么消费者队列key指定包含insert才可以接收消息,消费者队列key定义为update或者delete就不能接收消息。很好的控制了更新,插入和删除的操作。 采用交换机direct模式

流程说明:如果生产者投递消息到交换机(exchange),邮件队列和短信队列也都绑定了交换机(exchange)。但是当交换机的类型(type=direct)的时候,交换机的转发(路由)由routingKey决定转发给谁。如下如图所示,当交换机的rOntingKey=email的时候,消息将转发到邮件队列服务然后由邮件消费者进行消费。而短信队列是都收不到消息的,因为短信的路由routingKey=msg。如果短信队列也想收到消息就需要修改routingKey=email才可以收到消息。

这就是交换机类型type=direct的用法及特性。

 

4.5.通配符模式Topics

说明:此模式实在路由key模式的基础上,使用了通配符来管理消费者接收消息。生产者P发送消息到交换机X,type=topic,交换机根据绑定队列的routing key的值进行通配符匹配;

符号#匹配一个或者多个词lazy.# 可以匹配lazy.irs或者lazy.irs.cor

符号*只能匹配一个词lazy.* 可以匹配lazy.irs或者lazy.cor

 

 

消息队列RabbitMQ应答模式

为了确保消息不会丢失,RabbitMQ支持消息应答。消费者发送一个消息应答,告诉RabbitMQ这个消息已经接收并且处理完毕了。RabbitMQ就可以删除它了。 如果一个消费者挂掉却没有发送应答,RabbitMQ会理解为这个消息没有处理完全,然后交给另一个消费者去重新处理。这样,你就可以确认即使消费者偶尔挂掉也不会丢失任何消息了。 没有任何消息超时限制;只有当消费者挂掉时,RabbitMQ才会重新投递。即使处理一条消息会花费很长的时间。 消息应答是默认打开的。我们通过显示的设置autoAsk=true关闭这种机制。现即自动应答开,一旦我们完成任务,消费者会自动发送应答。通知RabbitMQ消息已被处理,可以从内存删除。如果消费者因宕机或链接失败等原因没有发送ACK(不同于ActiveMQ,在RabbitMQ里,消息没有过期的概念),则RabbitMQ会将消息重新发送给其他监听在队列的下一个消费者。

RabbitMQ的公平转发

目前消息转发机制是平均分配,这样就会出现俩个消费者,奇数的任务很耗时,偶数的任何工作量很小,造成的原因就是近当消息到达队列进行转发消息。并不在乎有多少任务消费者并未传递一个应答给RabbitMQ。仅仅盲目转发所有的奇数给一个消费者,偶数给另一个消费者。 为了解决这样的问题,我们可以使用basicQos方法,传递参数为prefetchCount= 1。这样告诉RabbitMQ不要在同一时间给一个消费者超过一条消息。 换句话说,只有在消费者空闲的时候会发送下一条信息。调度分发消息的方式,也就是告诉RabbitMQ每次只给消费者处理一条消息,也就是等待消费者处理完毕并自己对刚刚处理的消息进行确认之后,才发送下一条消息,防止消费者太过于忙碌,也防止它太过去清闲。 通过 设置channel.basicQos(1);

消息队列RabbitMQ应答模式

案例: 生产者端代码不变,消费者端代码这部分就是用于开启手动应答模式的。 channel.basicConsume(QUEUE_NAME, false, defaultConsumer); 注:第二个参数值为false代表关闭RabbitMQ的自动应答机制,改为手动应答。 在处理完消息时,返回应答状态,true表示为自动应答模式。 channel.basicAck(envelope.getDeliveryTag(), false);

传统简单队列是如何实现的?

生产者生产消息直接投递给队列服务器,队列服务器在以推送消息到消费者或者消费者从队列服务器拉取消息进行消费。消费者启动的时候会与队列服务器建立长连接。

RabbitMQ关键名词

AMQP(高级消息队列协议)是一个异步消息传递所使用应用层协议规范,为面向消息中间件设计,基于此协议的客户端与消息中间件可以无视消息来源传递消息,不受客户端、消息中间件、不同的开发语言环境等条件的限制;

涉及概念解释: 

 Server(Broker):接收客户端连接,实现AMQP协议的消息队列和路由功能的进程;

 Virtual Host:虚拟主机的概念,类似权限控制组,一个Virtual Host里可以有多个Exchange和Queue。     

Exchange:交换机,接收生产者发送的消息,并根据Routing Key将消息路由到服务器中的队列Queue。

 ExchangeType:交换机类型决定了路由消息行为,RabbitMQ中有三种类型Exchange,分别是fanout、direct、topic;  Message Queue:消息队列,用于存储还未被消费者消费的消息;

 Message:由Header和body组成,Header是由生产者添加的各种属性的集合,包括Message是否被持久化、优先级是多少、由哪个Message Queue接收等;

body是真正需要发送的数据内容;

BindingKey:绑定关键字,将一个特定的Exchange和一个特定的Queue绑定起来。

 

RabbitMQ交换机的作用

生产者发送消息不会像传统方式直接将消息投递到队列中,而是先将消息投递到交换机中,在由交换机转发到具体的队列,队列在将消息以推送或者拉取方式给消费者进行消费,这和我们之前学习Nginx有点类似。 交换机的作用根据具体的路由策略分发到不同的队列中。

交换机有四种类型:

Direct exchange(直连交换机):是根据消息携带的路由键;

routing key:将消息投递给对应队列的 Fanout exchange(扇型交换机)将消息路由给绑定到它身上的所有队列 ;

Topic exchange(主题交换机):队列通过路由键绑定到交换机上,然后,交换机根据消息里的路由值,将消息路由给一个或多个绑定队列;

Headers exchange(头交换机):类似主题交换机,但是头交换机使用多个消息属性来代替路由键建立路由规则。通过判断消息头的值能否与指定的绑定相匹配来确立路由规则。

 

RabbitMQ消息确认机制

问题产生背景: 生产者发送消息出去之后,不知道到底有没有发送到RabbitMQ服务器, 默认是不知道的。而且有的时候我们在发送消息之后,后面的逻辑出问题了,我们不想要发送之前的消息了,需要撤回该怎么做。

如果RabbitMQ服务器宕机了,消息会丢失吗?

  答案:RabbitMQ服务器支持消息持久化机制,会把消息持久化在硬盘上保存。代码设置  channel.queueDeclare(EMAIL_QUEUE, true, false, false, null); 方法第二个参数,默认情况下我们应该设置为true。

解决方案:

1.AMQP 事务机制

2.Confirm 模式

事务模式::

  txSelect:将当前channel设置为transaction模式

  txCommit :提交当前事务

  txRollback:事务回滚

 

 生产者   消费者   队列服务器  

消费者如何确保消息一定能够消费成功?

通过应答模式,默认为应答模式,可以修改为手动应答。设置方法:channel.basicConsume(QUEUE_NAME, false, defaultConsumer); 第二个参数。

设置应答模式 :第一个参数 队列名称、第二个参数 应答模式 如果为true 自动应答,false 为手动应答、第三个参数 监听器
自动应答(true):不在乎消费者对这个消息处理是否成功,都会告诉队列删除该消息。如果处理消息失败的情况下,应该实现自动补偿。
手动应答(false):当队列把消息推送给消费者,消费者处理完业务逻辑之后,手动返回ack(通知)告诉给队列服务器是否要删除该消息、如果失败,队列服务器做补偿,而不会直接删除该消息、

 

springboot整合rabbitmq项目

springboot整合rabbitmq分为2个项目,一个是生产者服务,一个是消息服务平台项目。消息服务平台项目中包括邮件消费者和短信消费者。没有必要每一个消费者都创建一个项目,那样会浪费资源。

在一个项目中,可以有多个生产者和消费者。

 

RabbitMQ消息重试机制

消费者在消费消息的时候,如果消费者业务逻辑出现程序异常,这时候应该如何处理?

答案:使用消息重试机制。

如何合适选择重试机制:

情况1: 消费者获取到消息后,调用第三方接口,但接口暂时无法访问,是否需要重试?

答案:需要重试机制。

情况2: 消费者获取到消息后,抛出数据转换异常,是否需要重试? 

答案:不需要重试机制,需要发布版本进行解决。

如何实现重试机制

总结:对于情况2,如果消费者代码抛出异常是需要发布新版本才能解决的问题,那么不需要重试,重试也无济于事。应该采用日志记录+定时任务job健康检查+人工进行补偿。

消费者如果保证消息幂等性,不被重复消费

产生原因:网络延迟传输中,消费出现异常或者是消费延迟消费,会造成MQ进行重试补偿,在重试过程中,可能会造成重复消费。

消费者如何保证消息幂等性,不被重复消费

解决办法:

①使用全局MessageID判断消费方是否是同一个,解决幂等性。

②或者使用业务逻辑id保证唯一(比如订单号码)

RabbitMQ信队列

死信队列 听上去像 消息“死”了     其实也有点这个意思,死信队列  是 当消息在一个队列 因为下列原因:

消息被拒绝(basic.reject/ basic.nack)并且不再重新投递 requeue=false

消息超期 (rabbitmq  Time-To-Live -> messageProperties.setExpiration())

队列超载

变成了 “死信” 后    被重新投递(publish)到另一个Exchange   该Exchange 就是DLX     然后该Exchange 根据绑定规则 转发到对应的 队列上  监听该队列  就可以重新消费     说白了 就是  没有被消费的消息  换个地方重新被消费

生产者   -->  消息 --> 交换机  --> 队列  --> 变成死信  --> DLX交换机 -->队列 --> 消费者

什么是死信呢?什么样的消息会变成死信呢?

消息被拒绝(basic.reject或basic.nack)并且requeue=false.

消息TTL过期

队列达到最大长度(队列满了,无法再添加数据到mq中)

应用场景分析

在定义业务队列的时候,可以考虑指定一个死信交换机,并绑定一个死信队列,当消息变成死信时,该消息就会被发送到该死信队列上,这样就方便我们查看消息失败的原因了

channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false); 丢弃消息

如何使用死信交换机呢?

定义业务(普通)队列的时候指定参数

x-dead-letter-exchange: 用来设置死信后发送的交换机

x-dead-letter-routing-key:用来设置死信的routingKey

/**
* 定义死信队列相关信息
*/
public final static String deadQueueName = "dead_queue";
public final static String deadRoutingKey = "dead_routing_key";
public final static String deadExchangeName = "dead_exchange";
/**
* 死信队列 交换机标识符
*/
public static final String DEAD_LETTER_QUEUE_KEY = "x-dead-letter-exchange";
/**
* 死信队列交换机绑定键标识符
*/
public static final String DEAD_LETTER_ROUTING_KEY = "x-dead-letter-routing-key";

/**
* 定义短信队列 包括死信队列
*
* @return
*/
@Bean
public Queue fanoutMsgQueue() {
//return new Queue(MSG_QUEUE_FANOUT);
// 将普通队列绑定到死信队列交换机上
Map args = new HashMap<>(2);
args.put(DEAD_LETTER_QUEUE_KEY, deadExchangeName);
args.put(DEAD_LETTER_ROUTING_KEY, deadRoutingKey);
Queue queue = new Queue(MSG_QUEUE_FANOUT, true, false, false, args);
return queue;
}
/**
* 配置死信队列
*
* @return
*/
@Bean
public Queue deadQueue() {
Queue queue = new Queue(deadQueueName, true);
return queue;
}

/**
* 创建死信交换机
*/
@Bean
public DirectExchange deadExchange() {
return new DirectExchange(deadExchangeName);
}

/**
* 死信交换机绑定私信队列
* @param deadQueue
* @param deadExchange
* @return
*/
@Bean
public Binding bindingDeadExchange(Queue deadQueue, DirectExchange deadExchange) {
return BindingBuilder.bind(deadQueue).to(deadExchange).with(deadRoutingKey);
}

RabbitMq 的配置文件

spring:
rabbitmq:
#### 连接地址
host: 127.0.0.1
####端口号
port: 5672
#### 用户名 自己在rabbitmq服务器上新建的 默认的用户名和密码为guest
username: ming
#### 密码
password: ming
### 虚拟主机
virtual-host: /member
listener:
simple:
retry:
####开启消费者(程序出现异常的情况下)进行重试机制
enabled: true
### 最大重试次数, 默认情况下 一直重试
max-attempts: 5
#### 重试间隔时间 单位:毫秒
initial-interval: 3000
##### 开启手动应答 ack
acknowledge-mode: manual

### 服务端口号
server:
port: 8081

rabbitmq地址:http://www.rabbitmq.com/getstarted.html

 

 

 


推荐阅读
  • CISCO ASA防火墙Failover+multiple context详细部署By 年糕泰迪[操作系统入门]
    一.文章概述本文主要就CISCOASA防火墙的高可用和扩张性进行阐述和部署。再cisco防火墙系列中主要有3种技术来实现高可用和扩张性。分别是Failover,multiplese ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • 知识图谱——机器大脑中的知识库
    本文介绍了知识图谱在机器大脑中的应用,以及搜索引擎在知识图谱方面的发展。以谷歌知识图谱为例,说明了知识图谱的智能化特点。通过搜索引擎用户可以获取更加智能化的答案,如搜索关键词"Marie Curie",会得到居里夫人的详细信息以及与之相关的历史人物。知识图谱的出现引起了搜索引擎行业的变革,不仅美国的微软必应,中国的百度、搜狗等搜索引擎公司也纷纷推出了自己的知识图谱。 ... [详细]
  • Tomcat/Jetty为何选择扩展线程池而不是使用JDK原生线程池?
    本文探讨了Tomcat和Jetty选择扩展线程池而不是使用JDK原生线程池的原因。通过比较IO密集型任务和CPU密集型任务的特点,解释了为何Tomcat和Jetty需要扩展线程池来提高并发度和任务处理速度。同时,介绍了JDK原生线程池的工作流程。 ... [详细]
  • RabbitMq之发布确认高级部分1.为什么会需要发布确认高级部分?在生产环境中由于一些不明原因,导致rabbitmq重启,在RabbitMQ重启期间生产者消息投递失败,导致消息丢 ... [详细]
  • mysql自动打开文件_让docker中的mysql启动时自动执行sql文件
    本文提要本文目的不仅仅是创建一个MySQL的镜像,而是在其基础上再实现启动过程中自动导入数据及数据库用户的权限设置,并且在新创建出来的容器里自动启动My ... [详细]
  • 使用Flutternewintegration_test进行示例集成测试?回答首先在dev下的p ... [详细]
  • 移动传感器扫描覆盖摘要:关于传感器网络中的地址覆盖问题,已经做过很多尝试。他们通常归为两类,全覆盖和栅栏覆盖,统称为静态覆盖 ... [详细]
  • 浅谈EditText控件的inputType类型
    其中大多数是用不到的,这里总结一下常用的几种键盘效果1、numberDecimal(可以带小数点的浮点格式)只可以输入0-9数字和小数点,即只浮点数2、number(数字格式 )只 ... [详细]
  • Docker安装Rabbitmq(配合宝塔)
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Docker安装Rabbitmq(配合宝塔)相关的知识,希望对你有一定的参考价值。一、事前准备 ... [详细]
  • RabbitMQ的消息持久化处理
    1、RabbitMQ的消息持久化处理,消息的可靠性是RabbitMQ的一大特色,那么RabbitMQ是如何保证消息可靠性的呢——消息持久化。2、auto ... [详细]
  • 消息中间件RabbitMQ 高级特性之消费端ACK与重回队列
    什么是消费端的ACK和重回队列?消费端的手工ACK和NACK消费端进行消费的时候,如果由于业务异常我们可以进行日志的记录,然后进行补偿如果由于服务器宕机等严重问题 ... [详细]
  • 本文比较了eBPF和WebAssembly作为云原生VM的特点和应用领域。eBPF作为运行在Linux内核中的轻量级代码执行沙箱,适用于网络或安全相关的任务;而WebAssembly作为图灵完备的语言,在商业应用中具有优势。同时,介绍了WebAssembly在Linux内核中运行的尝试以及基于LLVM的云原生WebAssembly编译器WasmEdge Runtime的案例,展示了WebAssembly作为原生应用程序的潜力。 ... [详细]
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • Android日历提醒软件开源项目分享及使用教程
    本文介绍了一款名为Android日历提醒软件的开源项目,作者分享了该项目的代码和使用教程,并提供了GitHub项目地址。文章详细介绍了该软件的主界面风格、日程信息的分类查看功能,以及添加日程提醒和查看详情的界面。同时,作者还提醒了读者在使用过程中可能遇到的Android6.0权限问题,并提供了解决方法。 ... [详细]
author-avatar
RealMadrid
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有