RabbitMQ相关图形,dljd

一、需求调研、框架的设计思想

1、什么是消息队列

        消息(Message)是指在应用间传送的数据。消息可以非常简单,比如只包含文本字符串,也可以更复杂,可能包含嵌入对象。

        ds:消息其实指的就是数据,什么“张三”、“李四”、“王五”、“赵六”等这些都是数据,都是消息。而它说的数据,并不是我们传统中所说的数据,而用在应用程序之间传递的数据,即消息。也就是说A系统和B系统之间传送的数据,我们通常叫做消息。消息可以是简单的字符串,如“张三”、“李四”、“王五”、“赵六”等;也可以是非常复杂的数据,如一个User.java对象;甚至可以是,一个集合,如ArrayList<User>对象。

        总的来说,什么东西都有可能是消息,因为它就是数据。

        消息队列(Message Queue)是一种应用间的通信方式,消息发送后可以立即返回,由消息系统来确保消息的可靠传递。消息发布者只管把消息发布到 MQ 中而不用管谁来取,消息使用者只管从 MQ 中取消息而不管是谁发布的。这样发布者和使用者都不用知道对方的存在。

        ds:也就是说,消息发布者只管发布消息到mq中,而不用管到底谁来取。而消息的使用者,只要去队列中取数据就可以了,它不用管这个消息是谁发布的。

        而“消息队列(Message Queue)是一种应用间的通信方式”,这句话表明了:我们的消息要遵守队列的概念。队列的核心思想是:先进先出,后进后出,因此先进队列的消息肯定先出队列,而后进的消息肯定后出队列,消息也是不能插队的。

        总的来说,也可以把消息队列想象成一个容器。

2、为什么要使用消息队列

        ds:消息是应用程序之间传送的消息(数据),那我们dubbo、redis、httpclient都能做到,即A应用把数据传递到B应用上而已,转子,为什么要使用消息队列来做呢?    

        从上面的描述中可以看出消息队列是一种应用间的异步协作机制,那什么时候需要使用 MQ 呢?ds:怎么就是“异步协作机制”了?即消息发布者只管把消息发布到 MQ 中而不用管谁来取,消息使用者只管从 MQ 中取消息而不管是谁发布的。就像平时的快递小哥只管把快递放到菜鸟驿站就行了,你不需要是哪个快递小哥、叫什么名字;反之,快递小哥也不需要知道哪个人过来领取快递、叫什么名字。这个快递的放置和领取,就是一种典型的异步协作机制。而“菜鸟驿站(容器)”,就相当于一个容器,就相当于我们的消息(快递)队列。

        一旦实现“异步协作机制”以后,就会有一个特点叫做解耦合。

        以常见的订单系统为例,用户点击【下单】按钮之后的业务逻辑可能包括:扣减库存、生成相应单据、发红包、发短信通知。在业务发展初期这些逻辑可能放在一起同步执行,随着业务的发展订单量增长,需要提升系统服务的性能,这时可以将一些不需要立即生效的操作拆分出来异步执行,比如发放红包、发短信通知等。这种场景下就可以用 MQ ,在下单的主流程(比如扣减库存、生成相应单据)完成之后发送一条消息到 MQ 让主流程快速完结,而由另外的单独线程拉取MQ的消息(或者由 MQ 推送消息),当发现 MQ 中有发红包或发短信之类的消息时,执行相应的业务逻辑。

        以上是用于业务解耦的情况,其它常见场景包括最终一致性、广播、错峰流控等等。

        ds:

        首先,一个业务逻辑可能包含:扣减库存、生成相应单据、发红包、发短信通知、发邮件等。而如,发短信要去调用第三方的短信接口,而这些接口可能都会很慢。再如,发邮件业务。发短信要去调用第三方的邮件服务器接口,这也都可能非常慢的。如果说用户点击一个按钮,应用就要把上面所说的所有业务都做一个遍,那么这个应用程序的效率就会非常非常的低。

        其次,有些业务并不是非常重要的:比如,发邮件业务,邮件发不发都可以。只要有短信到手机就可以了吧,大家手机都会在身边的。相反,这些业务中最最重要的两个业务(主要业务逻辑)是什么?那就是扣减库存、生成相应单据。什么“发红包、发短信通知、发邮件”等,都是其次吧。也就是说,用户点击按钮的时候,我们先把主要的业务逻辑,即扣减库存、生成相应单据处理完成。而次要的业务逻辑,即发红包、发短信通知、发邮件,你爱什么时候完成就什么时候完成,异步去做就行了。

3、解耦(异步协作机制)、场景包括最终一致性、广播、错峰流控等等

图1:消息队列运行流程 


 

图2、图3:异步协作机制  

ds:异步协作机制的场景2:解耦

        如上图所示,其实这个时候(在第2步)早就完成对用户的响应了。 如上图所示,这个时候就是异步去做的,这时候我们发红包、发短信、发邮件这3步(需要3秒)相当于“省掉”了。如果业务逻辑中有更多的次要业务,我们都用队列的方式去做,那么就会“省掉”更多时间,达到更快速地响应用户,提高用户体验。

        如在生活中,快递员只要把快递放置的位置(如菜鸟驿站)告诉你(如短信),然后他就走了,至于你什么取都可以。快递员只管把快递往那里放,他不关心谁来取,什么时候取。我们也是,只管取快递,不用管快递员是男的、女的,是高、是矮,是胖、是瘦。以上模式,就是异步协作机制。

        反过来想,如果是同步机制的话:这就要求快递员把快递送到你楼下,然后你亲自下楼拿,做到一手交钱一手交货。这效率,就慢很多。

ds:异步协作机制的场景2:最终一致性

            如上图所示,以前我们使用同步完成,我们可以使用数据库的事务去实现,可这5个业务(扣减库存、生成相应单据、发红包、发短信通知、发邮件)要么全部完成,要么全部回滚到原来的状态。而现在,主要业务逻辑(扣减库存、生成相应单据)完成以后,我们就会入库了。但将要业务逻辑(发红包、发短信通知、发邮件),不一定能够正常完成。

        “最终一致性”的意思正是如此,即主要业务逻辑完成就行了,次要的业务逻辑能不能完成不管。反之,那就是“强一致性”,即以前的写法:在一段代码中使用事务保证5个业务一起完成或一起回滚。

 ds:异步协作机制的场景3:广播

        广播是一种机制,比如服务器发布一条消息,所以的客户端都能获取到这条消息,这就是广播。

 ds:异步协作机制的场景4:错峰控制

        错峰控制,是做流量削峰用的。即当你的(访问)流量太高的时候,可以使用队列降低峰值,以避免服务器由于压力过大而宕机。

二、体系的组织结构设计、工作原理、运行流程、实现方法

1、RabbitMQ的消息发送和接收机制

        所有 MQ 产品从模型抽象上来说都是一样的过程:消费者(consumer)订阅(ds:监听)某个队列。生产者(producer)创建消息,然后发布到队列(queue)中,最后将消息发送到监听的消费者。

上面是MQ的基本抽象模型,但是不同的MQ产品有有者不同的机制,RabbitMQ实际基于AMQP协议的一个开源实现,因此RabbitMQ内部也是AMQP的基本概念。

        RabbitMQ的内部接收如下(ds:AMQP的基本概念长这个样子)

各个角色关系说明: 

  1. ds:一个Broker中,可以有若干个Virtual Host。
  2. ds:一个Virtual Host中,可以有若干个Exchange、若干个队列。
  3. ds:Exchange与队列的Binding(绑定)规则:一个Exchange,会绑定到一个队列去。

ds:生产者provider,工作流程

  1. 首先,生产者将消息写入到Broker中。
  2. 其次,进入到Broker中的某个Virtual Host中,即指定好命名空间。
  3. 接着,进入到指定的Virtual Host中,并指定将消息存入到某个Exchange中。
  4. 最后,Exchange根据绑定规则将消息写入到某个队列中,即消息最终会在队列中进行存放。在这里队列相当于一个容器,是专门用来存放消息的。

        经过以上4步以后,生产者的那部分操作就算完成了。只要生产者完成操作以后,那么这个时候它就可以立即访问。

        注:其实provide发送消息,也是需要通过通道的,也是需要通过通道才能把消息写入到交换机里面去。

ds:消费者consumer,工作流程

  1. 首先,consumer需要通过连接connection。
  2. 其次,一个connection中可以有若干个channel。channel称为通道,这个通道呢是一个双向通道,即我们通过通道进行读、也可以写。
  3. 接着,consumer通过connection里边的某个channel,channel最终会连接到某一个队列去。
  4. 然后,consumer通过channel到队列中去获取消息。此时,provider写入消息,consumer获取消息,并进行处理,整个流程就完成了。

1、Message

        消息,消息是不具体的,它由消息头和消息体组成。消息体(ds:我们的具体数据)是不透明的,而消息头则由一系列的可选属性组成,这些属性包括routing-key(路由键)、priority(相对于其他消息的优先权)、delivery-mode(指出该消息可能需要持久性存储)等。 

        ds:消息是我们传递的数据。由provider发送消息 ;消息进入exchange;exchange绑定之后把消息存入队列中;

        ds:消息头中对我们最重要的是routing-key(路由键)。那么provider发送的消息怎么就通过某个exchange存入到某个队列中去了呢,我们知道会有很多个exchange、队列是吧?其实是需要通过我们的routing-key进行绑定的,根据我们的 “routing-key + exchange、队列的绑定规则” ,才能把消息存入到指定队列中去。

2、Publisher
        消息的生产者,也是一个向交换器发布消息的客户端应用程序

        ds:Publisher是发送消息的,Publisher把消息发送给某一个exchange,再由exchange转发给我们的某一个队列。

3、Exchange
        交换器,用来接收生产者发送的消息并将这些消息路由给服务器中的队列。

        ds:一个Virtual Host中可以有若干个Exchange。

        ds:交换机是什么?也就是类似于我们的路由器,差不多。

        ds:消息会先进入exchange,exchange根据消息中的routing-key,然后转存到不同的队列里面。

4、Binding
        绑定,用于消息队列和交换器之间的关联。一个绑定就是基于路由键将交换器和消息队列连接起来的路由规则,所以可以将交换器理解成一个由绑定构成的路由表。

        ds:绑定(binding),这是一种规则。这个规则是用来做什么的呢?它是将某个exchange和某个队列绑定在一起,这个规则我们叫绑定(binding)。绑定(binding)完成之后 ,我们的消息就会有一个routing-key,此时这个routing-key和我们的绑定(binding)就会有一个对应关系。只要这个关系匹配成功,那么消息就会进入某个具体队列。

5、Queue
        消息队列,用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直在队列里面,等待消费者连接到这个队列将其取走。

        ds:队列是用于存放具体消息的,使用的数据结构是我们java中的队列数据结构(先进先出,后进后出,不能插队)。 

6、Connection
        网络连接,比如一个TCP连接。

        ds:Connection即网络连接,比如可以使用tcp连接实现。Connection要维持我们的应用程序和Broker的数据通信。

7、Channel
        信道,多路复用连接中的一条独立的双向数据流通道。信道是建立在真实的TCP连接内地虚拟连接,AMQP 命令都是通过信道发出去的,不管是发布消息、订阅队列还是接收消息,这些动作都是通过信道完成。因为对于操作系统来说建立和销毁 TCP 都是非常昂贵的开销,所以引入了信道的概念,以复用一条 TCP连接。

        ds:Connection中会有若干个channel,这个channel是双向的,可读可写,主要是用于传输我们数据流的。而具体的消息的读、写操作,也是通过通道来完成的,所以说后期我们的大多数操作都是通过channel来完成的。

        可以把channel想象成城市中的公路,Connection相当于公路的集合。比如你从家到学校,有很多条路可以走。其中的每一条路,就是一个channel。而这么条路,就组成了一个connection。

8、Consumer
        消息的消费者,表示一个从消息队列中取得消息的客户端应用程序

        ds:Consumer是客户端的应用程序,和我们的privoder都需要我们使用java代码去实现。privoder需要我们去编写发送消息的代码,在consumer中需要去编写接收和处理消息的代码。

9、Virtual Host(上图蓝色区域)
        虚拟主机,表示一批交换器、消息队列和相关对象。虚拟主机是共享相同的身份认证和加密环境的独立服务器域。每个 vhost 本质上就是一个 mini 版的RabbitMQ 服务器,拥有自己的队列、交换器、绑定和权限机制。vhost 是 AMQP概念的基础,必须在连接时指定,RabbitMQ 默认的vhost 是 / 。

        ds:Virtual Host,即命名空间,说白了就是一个文件夹呗。

        ds:在一个Broker中,可以有若干个Virtual Host。

        ds:Virtual Host即命名空间,相当于一个文件夹。在工作中,项目如果比较大,那可能会创建很多个Virtual Host;反之,如果项目比较小,甚至都不需要创建Virtual Host。所以说Virtual Host默认来讲的话,可有可无,即没有Virtual Host也无所谓,所以这个内容不是我们重点掌握的内容。

10、Broker(上图的黄色区域)
        表示消息队列服务器实体。

        ds:Broker,即你启动的那个RabbitMQ的主进程。

2、AMQP 中的消息路由

        AMQP 中消息的路由过程和 Java 开发者熟悉的 JMS 存在一些差别,AMQP 中增加了 Exchange 和 Binding 的角色。生产者把消息发布到 Exchange 上,消息最终到达队列并被消费者接收,而 Binding 决定交换器的消息应该发送到那个队列 

         ds:其实我们的MQ中呢有很多个产品。

         ds:我们java中有一个jms的概念(思想),即java消息服务。jms的作用和AMQP是一样的,都是一个协议规范。jms是java自己去定义的,自从java推出了这个jms概念(思想)后,就涌现出很多很多基于jms实现的消息中间件,比如activityMQ。

        jms和AMQP的区别在哪里呢?jms中没有exchange和绑定的概念,只有消息发送和接受这两个概念。反之,在AMQP中增加了exchange和绑定这两个角色。那么在AMQP中:首先,provider要把消息发布到exchange上去。然后,exchnage拿到消息之后,根据消息中的一个信息决定将消息最终把消息发送到哪个队列中去。那么在AMQP中就需要两个东西,一个是绑定,一个是routing-key,这两个东西一旦匹配上之后,就能够去转发。

        amqp的exchange有4种类型,分别是direct、fanout、topic、headers。其中headers 匹配 AMQP 消息的 header 而不是路由键,此外 headers 交换器和 direct 交换器完全一致,但性能差很多,目前几乎用不到了,所以直接看另外三种类型

3、Exchange 类型(绑定规则)

        Exchange分发消息时根据类型的不同分发策略有区别,目前共四种类型:direct、fanout、topic、headers 。headers 匹配 AMQP 消息的 header 而不是路由键,此外 headers 交换器和 direct 交换器完全一致,但性能差很多,目前几乎用不到了,所以直接看另外三种类型

(1)direct(绑定规则:单播模式、精准匹配:RoutingKey = BindingKey)

        消息中的路由键(routing key)如果和 Binding 中的 binding key 一致, 交换器就将消息发到对应的队列中。路由键与队列名完全匹配,如果一个队列绑定到交换机要求路由键为“dog”,则只转发 routing key 标记为“dog”的消息,不会转发“dog.puppy”,也不会转发“dog.guard”等等。它是完全匹配、单播的模式。

         ds:direct是一种消息一对一的绑定,是一种精准匹配。如下图所示:

 

  1. provider,它会有一个具体的消息数据,比如上图的:姓名:张三、routingKey:123。
  2. provider需要把消息发送给Broker,即我们的消息服务器RabbitMQ进程。
  3. Broker的内部有一个交换机,provider的消息(即姓名:张三、routingKey:123)会经过/会写入Broker中的Exchange。Exchange会根据消息中的数据,即路由键routingKey:123,决定把消息路由(转发)给哪个给谁。
  4. Exchange下面会有Bindings,即绑定规则。我们的Exchange会通过我们的Bindings,将消息转发给不同的队列。
  5. 因此Bindings下面会有很多个队列。这些队列会通过BindindKey(如下图中,BindindKey=123或BindingKey=234),进行一个绑定。
  6. 如上图,Exchange会根据routingKey:123和BindindKey=123,routingKey = BindindKey,此时会把消息路由(转发)给Queue1这个队列。
  7. 最后,是消息的consumer。如上图所示,consumer1最终会拿到"张三"这个消息(数据)。

注意1:bindingkey需要事先写好吗?有什么命名规则吗? 是的,bindingkey是要事先定义好的。我们事先会定义(指定)好队列的bindingkey,然后队列通过bindingkey与我们的Exchange进行绑定。从图上也可以看出,bindings的作用就是帮助我们将Exchange和队列进行连接的规则,是吧。你的Exchange会把消息路由(转发)到,routingKey = BindindKey的队列中去。

注意2:bindingkey没有命名规则,爱怎么写怎么写。

(2)fanout(绑定规则)

        每个发到 fanout 类型交换器的消息都会分到所有绑定的队列上去。fanout 交换器不处理路由键(ds:fanout 不需要指定RoutingKey),fanout 只是简单的将队列绑定到交换器上,每个发送到交换器的消息都会被转发到与该交换器绑定的所有队列上(ds:如果说direct是一对一的,那么对应的fanout就是一对多)。很像子网广播,每台子网内的主机都获得了一份复制的消息。fanout 类型转发消息是最快的(ds:和电视、广播差不多,不管你是什么人,只要订阅了此电视节目或广播节目,那么就能收看收听)。

  

  1. provider生产消息,消息(数据)的内容是字符串”张三“。注意,这里没有routingKey,即它不需要绑定routingKey。
  2. Broker。
  3. Exchange。
  4. bindings,但没有bindingKeys。
  5. 队列。
  6. 准备好以上这些组件后,我们需要将队列与Exchange进行绑定。
  7. 此时消息一旦进入Exchange后,由于没有routingKey和bindingKeys,所以消息会被路由(转发)给所有的队列。
  8. 此时,多个consumer只要它监听其中一个队列,都能拿到消息(数据)。

注意1:fanout不能叫多播模式。

注意2:fanout类型的交换机下会丢失消息,不能保证消息一定能接收到,安全性不好。如果provider先发送消息,但长时间没有consumer去接收,那么这条消息可能会丢失。比如,你在追剧,但由于各种各样的原因,你错过了那个时间段,最终你没能看到某一集电视剧。

注意3:fanout类型的交换机,不需要任何的Bindings,因此优点是速度快。

注意4:应用场景,比如你订阅腾讯的新闻,今天在无网络状态下你肯定接收不到消息。到了明天,你又有信号了,即使昨天的腾讯新闻你没有接收到,但可能对你已经不重要了。

(3)topic(绑定规则)

        topic 交换器通过模式匹配分配消息的路由键属性,将路由键和某个模式进行匹配,此时队列需要绑定到一个模式上。它将路由键和绑定键的字符串切分成单词,这些单词之间用点隔开。它同样也会识别两个通配符:符号“#”和符号“*”。#匹配0个或多个单词,“*”匹配不多不少一个单词。

        ds:topic交换机(Exchange)的也是一种一对多,也是一种广播模式,但是其中会增加了一些限制信息。比如说,你是腾讯会员吗?如果,你是一个超级会员,能看到什么数据;如果你是一个普通会员,当腾讯发消息时你只会看到什么信息。

  

  1. provider,中有消息(数据),如”李四“ +  RoutingKey = 123
  2. Broker。
  3. Exchange(Broker中)。provider把消息发送到Exchange,进入到Exchange中。
  4. Bindings。
  5. Queues(队列)。Exchange把消息路由(转发)到某个队列中。现在我们有3个队列,分别指定不同的BindingKey,其中Queue1的Bindingkey=aa,Queue2的Bindingkey=aa.*,Queue3的Bindingkey=aa.#。topic交换机给这3个队列使用了不同的bindingKey,把这3个队列绑定一了Exchange中去。
  6. consumer(若干个/多个)。consumer分别绑定到不同的队列中。
    这时,provider把消息发送到Exchange,Exchange拿到消息,并开始路由(转发)消息。此时,由于消息的RoutingKey=aa,所以消息(”李四“)都会进入这2个匹配的队列中去。 

        注意1:topic也是一种一对多的消息,但与fanout不同的是,topic需要指定routingKey和BindingKey。

        注意2:topic交换机(Exchange)中单词和单词之间使用.做为分隔符。

        注意3:topic的BindingKey和direct不一样。topic的BindingKey是一种批配置的绑定规则,即topic交换机(Exchange)中我们可以使用*号和#号这些通配符。*号表示只能匹配不多不少一个单词。#号表示匹配0个或多个单词,这个单词有多少个字母不做限制。

        

三、具体1:环境搭建 

序言      

        一般来说安装 RabbitMQ 之前要安装 Erlang ,可以去Erlang官网下载。接着去RabbitMQ官网下载安装包,之后解压缩即可。

        Erlang官方下载地址:Downloads - Erlang/OTP

        RabbitMQ官方下载地址:Downloading and Installing RabbitMQ — RabbitMQ 

1、安装前的准备:安装Erlang语言环境 

第一步:依赖包安装

        ds:要想安装RabbitMQ,首先得安装Erlang。要安装Erlang,首先得先安装Erlang库(依赖包)。

        安装RabbitMQ之前必须要先安装所需要的依赖包可以使用下面的一次性安装命令

        yum install gcc glibc-devel make ncurses-devel openssl-devel xmlto -y

第二步:安装Erlang

1、将Erlang源代码包otp_src_19.3.tar.gz上传到Linux的/home目录下

2、解压erlang 源码包

                tar -zxvf otp_src_19.3.tar.gz

3、手动创建erlang 的安装目录

                mkdir /usr/local/erlang

4、进入erlang的解压目录

                cd otp_src_19.3

5、配置erlang的安装信息

                  ./configure --prefix=/usr/local/erlang --without-javac

6、编译并安装

                make && make install

7、配置环境变量

                vim /etc/profile

8、将这些配置填写到profile文件的最后

                ERL_HOME=/usr/local/erlang

                PATH=$ERL_HOME/bin:$PATH

                export ERL_HOME PATH

9、启动环境变量配置文件

                source /etc/profile

10、查看当前Erlang的版本号

                erl -version

2、安装RabbitMQ

1、将RabbitMQ安装包rabbitmq-server-3.7.2-1.el7.noarch.rpm上传到/home目录

2、安装RabbitMQ

                rpm -ivh --nodeps rabbitmq-server-3.7.2-1.el7.noarch.rpm

3、访问RabbitMQ管控台

                http://192.168.32.130:15672/ root root

四、具体2:RabbitMQ管控台界面详解 

1、Overview

2、Admin(不用关注)

3、Queue

(1)添加队列: 

五、具体3:案例1:不涉及交换机

0、启动linux中的RabbitMQ消息服务器

1、Provider

(1)pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.bjpowernode.rabbitmq</groupId>
    <artifactId>01-rabbitmq-send-java-m</artifactId>
    <version>1.0.0</version>

    <dependencies>
        <!--rabbitmq客户端依赖包-->
        <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>5.1.1</version>
        </dependency>
    </dependencies>
</project>

(2)消息发送类:Send类

package com.bjpowernode.rabbitmq;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class Send {
    public static void main(String[] args) throws IOException, TimeoutException {
        /**
         * 1、创建链接工厂对象
         */
        ConnectionFactory factory=new ConnectionFactory();

        /**
         * 2、在连接工厂中配置RabbitMq的相关连接信息
         *      说明:
         *          这里使用的是root用户,其它的用户是不能在外网访问的。
         *          之前我们已经对root用户配置了权限,即配置了某个命名空间下的资源进行读、写、配置等权限操作,因此root用户可以用于发送消息。
         */
        factory.setHost("192.168.222.128"); //设置RabbitMQ的主机IP
        factory.setPort(5672);              //设置RabbitMQ的端口号
        factory.setUsername("root");        //设置访问用户名
        factory.setPassword("root");        //设置访问密码

        /**
         * 3、准备连接和通道对象。
         */
        Connection connection=null;         //定义链接对象
        Channel channel=null;               //定义通道对象
        connection=factory.newConnection(); //实例化链接对象(通过连接工厂创建)
        channel=connection.createChannel(); //实例化通道对象(通过连接创建)

        /**
         * 4、准备具体的消息。
         */
        String message ="Hello World!3";

        /**
         * 4、准备一个队列。
         *      (1)参数1:myQueue:队列名称,取值任意。
         *      (2)参数2:true:是否为持久化的队列,即消息是不是持久化;
         *      (3)参数3:false:是不是排外的。如果排外则这个队列只允许一个消费者监听。
         *      (4)参数4:false:是不是会自动删除队列。如果为true则表示当队列中没有消息,也没有消费者连接时就会自动删除这个队列。
         *      (5)参数5:null:为队列的一些属性设置通常为null即可。;
         *  声明队列时:
	     *     1、如果这个队列已经存在则放弃声明,不会重复声明,也不会报错。如果这个队列不存在,则会声明一个新的队列。
		 *     2、队列名可以取值任意,但是要与消息接收时的队列名完全一致。
	     *     3、这行代码是可有可无的,但是一定要在发送消息前确认队列名称已经存在RabbitMQ中,否则就会出现问题:消息去哪了,我也不知道。值得注意的是,它也不会抛出异常。
         *     4、队列可以使得下面的代码创建,也可以在RabbitMQ管控台页面中创建,建议在代码中创建。
         */
        channel.queueDeclare("myQueue", true, false, false, null);

        /**
         * 5、发送消息到指定队列。
         *      (1)参数1:"":为交换机名称,这里为空字符串表示不使用交换机。
         *      (2)参数2:myQueue:为队列名或RoutingKey,当指定了交换机名称以后,这个值就是RoutingKey。如果没有交换机,那就写队列名。
         *      (3)参数3:null:为消息的属性信息,通常为空即可。
         *      (4)参数4:message.getBytes("UTF-8"):为具体的消息数据的字节数组。
         *    注意:不管怎么样,消息发送这里的队列名一定要和消息接收主的队列名保持一致。
         */
        channel.basicPublish("","myQueue",null,message.getBytes("UTF-8"));
        System.out.println("消息发送成功: "+message);

        /**
         * 6、关闭通道。
         */
        channel.close();

        /**
         * 7、关闭连接。
         */
        connection.close();
    }
}

(3)测试(测试provider能不能发送消息)

1)第一步:运行Send.main();

2)第二步:控制台打印

3)第三步:查看RabbitMq控制台页面

        ds:看哪?看队列就行了:

点击队列”myQueeu" 》 点击“Get messages"的”Get messages“自动读取消息:

返回队列,由于消息出队了,所以数据都变成了0:

(4)其它现象(重)

  1.  多发送几次,后面来的消息不会覆盖前面的消息,消息会累加。
  2. 当有多条消息时,点击一次“Get Message”只会获取到一条消息,即一条一条出队的。

(5)因为我们没有自定义交换机,所以交换机列表中都是它默认自带的: 

2、consumer

序言

        ds:不光是RabbitMQ,所有的XxxMQ都是跨系统之间进行消息传递的。

(1)pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.bjpowernode.rabbitmq</groupId>
    <artifactId>02-rabbitmq-receive-java-m</artifactId>
    <version>1.0.0</version>

    <dependencies>
        <!--rabbitmq客户端依赖包-->
        <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>5.1.1</version>
        </dependency>
    </dependencies>
</project>

(2)Recieve类接收消息

package rabbitmq;

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class Receive {
    public static void main(String[] args) throws IOException, TimeoutException {
        /**
         * 1、创建链接工厂对象
         */
        ConnectionFactory factory=new ConnectionFactory();

        /**
         * 2、在连接工厂中配置RabbitMq的相关连接信息
         *      说明:
         *          这里使用的是root用户,其它的用户是不能在外网访问的。
         *          之前我们已经对root用户配置了权限,即配置了某个命名空间下的资源进行读、写、配置等权限操作,因此root用户可以用于发送消息。
         */
        factory.setHost("192.168.32.130"); //设置RabbitMQ的主机IP
        factory.setPort(5672);              //设置RabbitMQ的端口号
        factory.setUsername("root");        //设置访问用户名
        factory.setPassword("root");        //设置访问密码

        /**
         * 3、准备连接和通道对象。
         */
        Connection connection=null;         //定义链接对象
        Channel channel=null;               //定义通道对象
        connection=factory.newConnection(); //实例化链接对象(通过连接工厂创建)
        channel=connection.createChannel(); //实例化通道对象(通过连接创建)

        /**
         * 4、准备具体的消息。
         */
        String message ="Hello World!3";

        /**
         * 4、准备一个队列。
         *      (1)参数1:myQueue:队列名称,取值任意。
         *      (2)参数2:true:是否为持久化的队列,即消息是不是持久化;
         *      (3)参数3:false:是不是排外的。如果排外则这个队列只允许一个消费者监听。
         *      (4)参数4:false:是不是会自动删除队列。如果为true则表示当队列中没有消息,也没有消费者连接时就会自动删除这个队列。
         *      (5)参数5:null:为队列的一些属性设置通常为null即可。;
         */
        channel.queueDeclare("myQueue", true, false, false, null);



        //--------------------------------------前面这段和消息发送都是一样的,包括声明队列都是一样的---------------------------------------------



        //消息消费
        boolean autoAck = true;     //  表示:我们的消息是不是要自动确认的。
        String consumerTag = "";
        //接收消息
        //参数1:队列名称。即要监听的队列名称。队列名必须要与发送时的队列名完全一致否则接收不到消息。
        //参数2:是否自动确认消息,true表示自动确认,false表示手动确认。
        //      所谓自动确认,指的是收到消息后,消息会不会自动从队列中移除。如果是true,表明收到消息后,消息会自动从队列中移除掉。反之,不会。
        //参数3:为消息标签,用来区分不同的消费者这里暂时为""。
        //      当有多个消费者去监听队列时,这个参数才会起作用。
        //      大多数情况下,我们不会配置这个参数。
        //      如果我们配置了这个参数,那么provider这边发送消息时需要配置标签才可以。
        //参数4:消费者回调方法用于编写处理消息的具体代码(例如打印或将消息写入数据库)。
        //      即在回调函数中接收消息。
        //注意:使用了basicConsumer方法以后,会启动一个线程异步地、持续地去监听队列,如果有新的消息进入队列,则消费者会自动接收消息,因此不能关闭连接和通道对象。
        channel.basicConsume("myQueue", autoAck, consumerTag, new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag,
                                       Envelope envelope,
                                       AMQP.BasicProperties properties,
                                       byte[] body) throws IOException {
                //获取消息数据
                String bodyStr = new String(body, "UTF-8");
                System.out.println("消费者---"+bodyStr);
            }
        });
        //不能关闭通道和连接,如果一旦关闭可能会造成接收时抛出异常或无法接收到消息。
        //注意:这里不能关闭连接和通道。如果关闭了,那么消费都就不能再继续监听队列。如果一直不关闭,说明消费都在持续性地监听着队列,也就可以持续性地接收消息了。
        //channel.close();
        //conn.close();
    }
}

(3)测试(是否成功接收到消息)

1)第一步:运行Receive.main()方法,持续监听。

2)第二步:运行Send.main()方法,发送消息。

3)第三步:控制台情况:

        

多次发送消息,多次接收消息:

六、具体4:案例2:direct交换机类型

序言

        ds:之前的案例,是利用RabbitMQ直接将消息发送给队列。这种方式,是所有XxxMQ的统一的一种发送机制。而RabbitMQ是基于AMQP协议的,要求有交换机,首先要讲的是direct类型的交换机。

        direct交换机类型,要求RoutingKey和BindingKey完全一致的一种规则。在这种模式下:

  1. 首先,创建Exchange。
  2. 其次,创建队列。
  3. 然后,使用bindingKey把Exchange和队列绑定在一起。
  4. 接着,发送消息到Exchange中,注意:消息中必须携带routingKey。
  5. 最后,Exchange会根据你的routingKey找到routingKey = bindingKey队列,把消息路由(转发)给此队列。

1、provider

(1)pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.bjpowernode.rabbitmq</groupId>
    <artifactId>03-rabbitmq-send-direct-m</artifactId>
    <version>1.0.0</version>

    <dependencies>
        <!--rabbitmq客户端依赖包-->
        <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>5.1.1</version>
        </dependency>
    </dependencies>
</project>

(2)Send.main()

package com.bjpowernode.rabbitmq;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class Send {
    public static void main(String[] args) throws IOException, TimeoutException {
        //  第1步:创建链接工厂对象
        ConnectionFactory factory=new ConnectionFactory();

        //  第2步:在连接工厂中配置RabbitMq的相关连接信息
        factory.setHost("192.168.32.130");  //设置RabbitMQ的主机IP
        factory.setPort(5672);              //设置RabbitMQ的端口号
        factory.setUsername("root");        //设置访问用户名
        factory.setPassword("root");        //设置访问密码

        //  第3步:创建连接和通道对象。
        Connection connection=null;         //定义链接对象
        Channel channel=null;               //定义通道对象
        connection=factory.newConnection(); //实例化链接对象
        channel=connection.createChannel(); //实例化通道对象

        //  第4步:创建消息
        String message ="Hello World!Direct!provider!";

        //  第5步:创建队列
        channel.queueDeclare("directQueue", true, false, false, null);

        //  第6步:创建direct交换机,需指定Exchange的类型为direct
        //      参数1:交换机名称,取值任意
        //      参数2:交换机类型取值为:direct、queue、topic、headers
        //      参数3:为是否为持久化消息,true表示持久化消息;false表示非持久化。
        //  注意:
        //      1、声明交换机时:如果这个交换机已经存在则会放弃声明。如果交换机不存在在则声明交换机。
        //      2、这行代码是可有可无的,但是在使用前必须要确保这个交换机已经被声明(存在)
        channel.exchangeDeclare("directExchange", "direct", true);

        //  第7步:将队列绑定到交换机
        //      参数1:队列名称。
        //      参数2:交换机名称。
        //      参数3:消息的routingKey(就是bindingKey)。其实这是一个绑定key,即bindingKey,但这里参数名叫routingKey而已。
        //  注意:
        //      1、在进行队列和交换机的绑定时,必须要确保交换机和队列已经成功的声明(存在)
        channel.queueBind("directQueue","directExchange","directRoutingKey");

        //  第7步:把消息发送到指定队列
        //      参数1:交换机名称。
        //      参数2:为消息的RoutingKey。如果这个消息的RoutingKey和“ 某个队列与交换机绑定的RoutingKey ”一致,那么这个消息就会被发送到指定的队列中。
        //      参数3:设置消息的属性,可以通过消息属性设置消息是否是持久化的
        //      参数4:具体要发送的消息信息
        //  注意:发送消息时必须确保交换机已经创建,并且确保交换机已经正确地绑定到某个队列上。
        channel.basicPublish("directExchange","directRoutingKey",null,message.getBytes("UTF-8"));
        System.out.println("providere消息发送成功: "+message);
//      channel.close();
//      connection.close();
    }
}

(3)测试

1)第一步:启动linux中的RabbitMQ消息服务器

                rabbitmq-server start &

                systemctl stop firewalld

                ifconifg

2)第二步:启动Send.main();

3)第三步:控制台:           

 4)第四步:RabbitMQ管控台:Exchanges:http://192.168.32.130:15672/ root root

  5)第五步:RabbitMQ管控台:Queue:http://192.168.32.130:15672/ root root

2、consumer

(1)pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.bjpowernode.rabbitmq</groupId>
    <artifactId>04-rabbitmq-receive-direct-m</artifactId>
    <version>1.0.0</version>

    <dependencies>
        <!--rabbitmq客户端依赖包-->
        <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>5.1.1</version>
        </dependency>
    </dependencies>
</project>

(2)Receive.Main()

package com.bjpowernode.rabbitmq;

import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class Receive {
    public static void main(String[] args) throws IOException, TimeoutException {
        //  第1步:创建链接工厂对象
        ConnectionFactory factory=new ConnectionFactory();

        //  第2步:在连接工厂中配置RabbitMq的相关连接信息
        factory.setHost("192.168.32.130");  //设置RabbitMQ的主机IP
        factory.setPort(5672);              //设置RabbitMQ的端口号
        factory.setUsername("root");        //设置访问用户名
        factory.setPassword("root");        //设置访问密码

        //  第3步:创建连接和通道对象。
        Connection connection=null;         //定义链接对象
        Channel channel=null;               //定义通道对象
        connection=factory.newConnection(); //实例化链接对象
        channel=connection.createChannel(); //实例化通道对象

        //  第4步:创建消息
        String message ="Hello World!Direct Consumer!!!";

        //  第5步:创建队列
        channel.queueDeclare("directQueue", true, false, false, null);


        //  第6步:创建direct交换机,需指定Exchange的类型为direct
        //      参数1:交换机名称,取值任意
        //      参数2:交换机类型取值为:direct、queue、topic、headers
        //      参数3:为是否为持久化消息,true表示持久化消息;false表示非持久化。
        //  注意:
        //      1、声明交换机时:如果这个交换机已经存在则会放弃声明。如果交换机不存在在则声明交换机。
        //      2、这行代码是可有可无的,但是在使用前必须要确保这个交换机已经被声明(存在)
        channel.exchangeDeclare("directExchange", "direct", true);

        //  第7步:将队列绑定到交换机
        //      参数1:队列名称。
        //      参数2:交换机名称。
        //      参数3:消息的routingKey(就是bindingKey)。其实这是一个绑定key,即bindingKey,但这里参数名叫routingKey而已。
        //  注意:
        //      1、在进行队列和交换机的绑定时,必须要确保交换机和队列已经成功的声明(存在)
        channel.queueBind("directQueue","directExchange","directRoutingKey");

        //  第8步:监听某个队列,并获取队列中的消息(数据)
        //      参数1:队列名称
        //      参数2:true自动删除
        //      参数3:""标签
        //      参数4:回调函数
        //  注意:
        //      1、当前被监听(绑定)的队列必须已经存在,并且这个队列已经正确地绑定到了某个交换机中。
        channel.basicConsume("directQueue", true, "", new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag,
                                       Envelope envelope,
                                       AMQP.BasicProperties properties,
                                       byte[] body) throws IOException {
                //获取消息数据
                String bodyStr = new String(body, "UTF-8");
                System.out.println("---consumer中获取的消息----"+bodyStr);
            }
        });
        //channel.close();
        //conn.close();
    }
}

(3)测试

1)第一步:启动linux中的RabbitMQ消息服务器

                rabbitmq-server start &

                systemctl stop firewalld

                ifconifg

2)第二步:启动Receive.main()

3)第三步:控制台:

3、注意:可有可无

        ds:在consumer中,创建队列:,创建交换机:,绑定交换机和队列:,这3句代码是可有可无的,但是在使用前必须要确保这个队列、交换机已经被声明(存在),而且确保交换机和队列已经成功绑定。

4、注意:routingKey就是bindingKey

        ds:在direct类型的交换机中,provider、consumer中的channel.queueBind()方法的routingKey参数值就是bindingKey。这是因为在direct类型的交换机中,要求routingKey = bindingKey。

七、具体5:案例3:fanout交换机类型

序言

特点:

  1. fanout交换机类型是一种广播模式。

工作流程:

  1. 首先,有消息
  2. 其次,消息进入交换机。
  3. 接着,有交换机。
  4. 然后,可以同时有多个队列去监听/绑定交换机。交换机会用简单的方式和队列进行绑定,fanout交换机和队列进行绑定时不需要routingKey。
  5. 再其次,消息一旦进入交换机之后,交换机就会把消息转发到所有绑定到这个交换机的所有队列中去。
  6. 再接着,因为fanout交换机类型是一种广播模式,所以就像我们收听广播、看卫星电视一样,要想不错过某一套广播、或电视剧集的播放,就得先启动消息监听(即消息的接收者)才行(同先打开广播调频等着、先打开电视机等着)。反之,如果先发送消息,再进行监听你就会错过一些消息(如后打开电视机的会错过这直播)。

1、consumer(这回先写)

(1)pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.bjpowernode.rabbitmq</groupId>
    <artifactId>06-rabbitmq-receive-direct-m</artifactId>
    <version>1.0.0</version>

    <dependencies>
        <!--rabbitmq客户端依赖包-->
        <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>5.1.1</version>
        </dependency>
    </dependencies>
</project>

(2)Receive01.Main()

package fanout;

import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class Receive {
    public static void main(String[] args) throws IOException, TimeoutException {
        //  第1步:创建链接工厂对象
        ConnectionFactory factory=new ConnectionFactory();

        //  第2步:在连接工厂中配置RabbitMq的相关连接信息
        factory.setHost("192.168.32.130");  //设置RabbitMQ的主机IP
        factory.setPort(5672);              //设置RabbitMQ的端口号
        factory.setUsername("root");        //设置访问用户名
        factory.setPassword("root");        //设置访问密码

        //  第3步:创建连接和通道对象。
        Connection connection=null;         //定义链接对象
        Channel channel=null;               //定义通道对象
        connection=factory.newConnection(); //实例化链接对象
        channel=connection.createChannel(); //实例化通道对象

        //  第4步:创建消息
        String message ="Hello World!Direct Consumer!!!";

        //  第5步:创建队列
        /*
            队列通常是排外的,即一个队列通常只允许一个接收者去监听。因此如果想要实现一个队列被多个接收者去监听的话,就得为这个队列取多个名称:
            (1)由于fanout类型的交换机的消息是类似于广播的模式,它不需要Routingkey。
            (2)而且又可能会有很多个消息者来监听并接收这个交换机中的数据(消息),因此我们创建队列时要创建一个随机的队列名称。

            queueDeclare():
                没有参数的queueDeclare()方法会创建一个名字为随机的一个队列,
                这个队列的数据是非持久的、是排外的(同时最多只允许有一个消费者监听当前队列)、自动删除的(当没有任何消费者监听队列时,这个队列会自动删除掉)

            getQueue()方法用于获取这个随机的队列名称。
         */
        String queueName = channel.queueDeclare().getQueue();


        //  第6步:创建direct交换机,需指定Exchange的类型为direct
        //      参数1:交换机名称,取值任意
        //      参数2:交换机类型取值为:direct、queue、topic、headers
        //      参数3:为是否为持久化消息,true表示持久化消息;false表示非持久化。
        //  注意:
        //      1、声明交换机时:如果这个交换机已经存在则会放弃声明。如果交换机不存在在则声明交换机。
        //      2、这行代码是可有可无的,但是在使用前必须要确保这个交换机已经被声明(存在)
        channel.exchangeDeclare("fanoutExchange", "fanout", true);

        //  第7步:将这个随机的队列绑定到交换机中
        //      参数1:队列名称。
        //      参数2:交换机名称。
        //      参数3:由于是fanout类型的交换机因此不需要指定Routingkey进行绑定
        //  注意:
        //      1、fanout类型的交换机不需要绑定routingkey
        channel.queueBind(queueName,"fanoutExchange","");

        //  第8步:监听这个随机队列,并获取队列中的消息(数据)
        //      参数1:队列名称
        //      参数2:true自动删除
        //      参数3:""标签
        //      参数4:回调函数
        //  注意:
        //      1、当前被监听(绑定)的队列必须已经存在,并且这个队列已经正确地绑定到了某个交换机中。
        channel.basicConsume(queueName, true, "", new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag,
                                       Envelope envelope,
                                       AMQP.BasicProperties properties,
                                       byte[] body) throws IOException {
                //获取消息数据
                String bodyStr = new String(body, "UTF-8");
                System.out.println("---consumer01中获取的消息----"+bodyStr);
            }
        });
        //channel.close();
        //conn.close();
    }
}

(3)Receive02.Main():只是打印的信息不一样,仅此而已

package fanout;

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class Receive02 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //  第1步:创建链接工厂对象
        ConnectionFactory factory=new ConnectionFactory();

        //  第2步:在连接工厂中配置RabbitMq的相关连接信息
        factory.setHost("192.168.32.130");  //设置RabbitMQ的主机IP
        factory.setPort(5672);              //设置RabbitMQ的端口号
        factory.setUsername("root");        //设置访问用户名
        factory.setPassword("root");        //设置访问密码

        //  第3步:创建连接和通道对象。
        Connection connection=null;         //定义链接对象
        Channel channel=null;               //定义通道对象
        connection=factory.newConnection(); //实例化链接对象
        channel=connection.createChannel(); //实例化通道对象

        //  第4步:创建消息
        String message ="Hello World!Direct Consumer!!!";

        //  第5步:创建队列
        /*
            队列通常是排外的,即一个队列通常只允许一个接收者去监听。因此如果想要实现一个队列被多个接收者去监听的话,就得为这个队列取多个名称:
            (1)由于fanout类型的交换机的消息是类似于广播的模式,它不需要Routingkey。
            (2)而且又可能会有很多个消息者来监听并接收这个交换机中的数据(消息),因此我们创建队列时要创建一个随机的队列名称。

            queueDeclare():
                没有参数的queueDeclare()方法会创建一个名字为随机的一个队列,
                这个队列的数据是非持久的、是排外的(同时最多只允许有一个消费者监听当前队列)、自动删除的(当没有任何消费者监听队列时,这个队列会自动删除掉)

            getQueue()方法用于获取这个随机的队列名称。
         */
        String queueName = channel.queueDeclare().getQueue();


        //  第6步:创建direct交换机,需指定Exchange的类型为direct
        //      参数1:交换机名称,取值任意
        //      参数2:交换机类型取值为:direct、queue、topic、headers
        //      参数3:为是否为持久化消息,true表示持久化消息;false表示非持久化。
        //  注意:
        //      1、声明交换机时:如果这个交换机已经存在则会放弃声明。如果交换机不存在在则声明交换机。
        //      2、这行代码是可有可无的,但是在使用前必须要确保这个交换机已经被声明(存在)
        channel.exchangeDeclare("fanoutExchange", "fanout", true);

        //  第7步:将这个随机的队列绑定到交换机中
        //      参数1:队列名称。
        //      参数2:交换机名称。
        //      参数3:由于是fanout类型的交换机因此不需要指定Routingkey进行绑定
        //  注意:
        //      1、fanout类型的交换机不需要绑定routingkey
        channel.queueBind(queueName,"fanoutExchange","");

        //  第8步:监听这个随机队列,并获取队列中的消息(数据)
        //      参数1:队列名称
        //      参数2:true自动删除
        //      参数3:""标签
        //      参数4:回调函数
        //  注意:
        //      1、当前被监听(绑定)的队列必须已经存在,并且这个队列已经正确地绑定到了某个交换机中。
        channel.basicConsume(queueName, true, "", new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag,
                                       Envelope envelope,
                                       AMQP.BasicProperties properties,
                                       byte[] body) throws IOException {
                //获取消息数据
                String bodyStr = new String(body, "UTF-8");
                System.out.println("---consumer02中获取的消息----"+bodyStr);
            }
        });
        //channel.close();
        //conn.close();
    }
}

(4)先启动Receive01.Main()等着

(5)再启动Receive02.Main()等着

(6)查看RabbitMQ管控台:

1)查看交换机

2)fanout交换机详情

 

3)查看队列:生成随机的两个队列(名称):

4)队列详情:点击其中的个队列进去

 (7)停掉Receive01.Main()和Receive02.Main(),即没有消费者去监听队列以后这两个随机队列也会自动删除。效果是,管控台中怎么刷新也见不到随机队列了:

(8)先启动Receive01.Main()等着,再启动Receive02.Main()等着,因为是随机的队列名称就变了,即每次启动队列名称都不一样。

2、provider(这加后写,后启动)

(1)pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.bjpowernode.rabbitmq</groupId>
    <artifactId>05-rabbitmq-send-direct-m</artifactId>
    <version>1.0.0</version>

    <dependencies>
        <!--rabbitmq客户端依赖包-->
        <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>5.1.1</version>
        </dependency>
    </dependencies>

</project>

(2)Send.main()

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class Send {
    public static void main(String[] args) throws IOException, TimeoutException {
        //  第1步:创建链接工厂对象
        ConnectionFactory factory=new ConnectionFactory();

        //  第2步:在连接工厂中配置RabbitMq的相关连接信息
        factory.setHost("192.168.32.130");  //设置RabbitMQ的主机IP
        factory.setPort(5672);              //设置RabbitMQ的端口号
        factory.setUsername("root");        //设置访问用户名
        factory.setPassword("root");        //设置访问密码

        //  第3步:创建连接和通道对象。
        Connection connection=null;         //定义链接对象
        Channel channel=null;               //定义通道对象
        connection=factory.newConnection(); //实例化链接对象
        channel=connection.createChannel(); //实例化通道对象

        //  第4步:创建消息
        String message ="fanout类型交换机的测试消息";

        /**
         * 由于使用了fanout类型的交换机,
         * 因此消息的接收方可能会有多个,
         * 因此不建议在消息发送时来创建队列以及绑定交换机(因为我们不确定到底有多少个,所以我们不能在这里绑定),
         * 建议在消费者中创建队列并绑定交换机,但要注意的是:发送消息时至少应该确保交换机是存在的。
         *
         */
        //  第5步:创建队列
        //  channel.queueDeclare("directQueue", true, false, false, null);
        //  第6步:创建direct交换机,需指定Exchange的类型为direct
          channel.exchangeDeclare("fanoutExchange", "fanout", true);
        //  第7步:将队列绑定到交换机
        //  channel.queueBind("directQueue","directExchange","directRoutingKey");

        //  第7步:把消息发送到指定队列
        //      参数1:交换机名称。
        //      参数2:fanout类型的交换机不需要routingkey,因此这里置空
        //      参数3:设置消息的属性,可以通过消息属性设置消息是否是持久化的
        //      参数4:具体要发送的消息信息
        //  注意:发送消息时必须确保交换机已经创建,并且确保交换机已经正确地绑定到某个队列上。
        channel.basicPublish("fanoutExchange","",null,message.getBytes("UTF-8"));
        System.out.println("provider消息发送成功: "+message);
//      channel.close();
//      connection.close();
    }
}

(3)测试:启动Send.main()

provider一启动:

consumer1和consumer2都同时接收到消息:

(4)老师强调点: 

         ds:如上图红色框框所示:明确指定的队列名称并进行了与交换机的绑定,可以保证fanout类型的消息不会丢失,但是这么做没有任何的意义。这是因为后期消费者的数量可能会有很多,但我们却不能让所有的消费者全部监听同一个队列。

        如果想实现同一个消费者都监听同一个队列的话,我们直接使用direct交换机更好。

八、具体5:案例3:topic交换机类型

序言

        ds:topic交换机也是一种一对多的模式,它和fanout交换机很类似。topic和fanout交换机唯一的区别在哪里呢?答:topic交换机需要绑定routingkey,而它的routingkey是可以使用通配符(*、#)的。

        使用通配符(*、#)进行匹配以后,topic交换机类型也是一种一对多的消息接收模式。

        所以说topic交换机,可以明确声明队列,也可以写随机队列,写哪种都行。

1、consumer(建议先写接收)

(1)pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.bjpowernode.rabbitmq</groupId>
    <artifactId>08-rabbitmq-receive-topic</artifactId>
    <version>1.0.0</version>

    <dependencies>
        <!--rabbitmq客户端依赖包-->
        <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>5.1.1</version>
        </dependency>
    </dependencies>
</project>

(2)Receive01.Main()


import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class Receive01 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //  第1步:创建链接工厂对象
        ConnectionFactory factory=new ConnectionFactory();

        //  第2步:在连接工厂中配置RabbitMq的相关连接信息
        factory.setHost("192.168.32.130");  //设置RabbitMQ的主机IP
        factory.setPort(5672);              //设置RabbitMQ的端口号
        factory.setUsername("root");        //设置访问用户名
        factory.setPassword("root");        //设置访问密码

        //  第3步:创建连接和通道对象。
        Connection connection=null;         //定义链接对象
        Channel channel=null;               //定义通道对象
        connection=factory.newConnection(); //实例化链接对象
        channel=connection.createChannel(); //实例化通道对象

        //  第4步:创建消息
        String message ="Hello World!Direct Consumer!!!";

        //  第5步:案例1:topic交换机使用明确队列(但也可以创建随机队列)
        channel.queueDeclare("topicQueue01",true,false,false,null);

        //  第6步:创建direct交换机,需指定Exchange的类型为direct
        //      参数1:交换机名称,取值任意
        //      参数2:交换机类型取值为:direct、queue、topic、headers
        //      参数3:为是否为持久化消息,true表示持久化消息;false表示非持久化。
        //  注意:
        //      1、声明交换机时:如果这个交换机已经存在则会放弃声明。如果交换机不存在在则声明交换机。
        //      2、这行代码是可有可无的,但是在使用前必须要确保这个交换机已经被声明(存在)
        channel.exchangeDeclare("topicExchange", "topic", true);

        //  第7步:将这个随机的队列绑定到交换机中
        channel.queueBind("topicQueue01","topicExchange","aa");

        //  第8步:监听这个随机队列,并获取队列中的消息(数据)
        //      参数1:队列名称
        //      参数2:true自动删除
        //      参数3:""标签
        //      参数4:回调函数
        //  注意:
        //      1、当前被监听(绑定)的队列必须已经存在,并且这个队列已经正确地绑定到了某个交换机中。
        channel.basicConsume("topicQueue01", true, "", new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag,
                                       Envelope envelope,
                                       AMQP.BasicProperties properties,
                                       byte[] body) throws IOException {
                //获取消息数据
                String bodyStr = new String(body, "UTF-8");
                System.out.println("---consumer01中获取的消息(routingkey=aa)----"+bodyStr);
            }
        });
        //channel.close();
        //conn.close();
    }
}

(3)Receive02.Main()

         ds:两个队列都绑定到同一个交换机上,只不过Receive01.Main()的routingkey=aa,而Receive02.Main()的routingkey=aa.*。

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class Receive02 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //  第1步:创建链接工厂对象
        ConnectionFactory factory=new ConnectionFactory();

        //  第2步:在连接工厂中配置RabbitMq的相关连接信息
        factory.setHost("192.168.32.130");  //设置RabbitMQ的主机IP
        factory.setPort(5672);              //设置RabbitMQ的端口号
        factory.setUsername("root");        //设置访问用户名
        factory.setPassword("root");        //设置访问密码

        //  第3步:创建连接和通道对象。
        Connection connection=null;         //定义链接对象
        Channel channel=null;               //定义通道对象
        connection=factory.newConnection(); //实例化链接对象
        channel=connection.createChannel(); //实例化通道对象

        //  第4步:创建消息
        String message ="Hello World!Direct Consumer!!!";

        //  第5步:案例1:topic交换机使用明确队列(但也可以创建随机队列)
        channel.queueDeclare("topicQueue02",true,false,false,null);

        //  第6步:创建direct交换机,需指定Exchange的类型为direct
        //      参数1:交换机名称,取值任意
        //      参数2:交换机类型取值为:direct、queue、topic、headers
        //      参数3:为是否为持久化消息,true表示持久化消息;false表示非持久化。
        //  注意:
        //      1、声明交换机时:如果这个交换机已经存在则会放弃声明。如果交换机不存在在则声明交换机。
        //      2、这行代码是可有可无的,但是在使用前必须要确保这个交换机已经被声明(存在)
        channel.exchangeDeclare("topicExchange", "topic", true);

        //  第7步:将这个随机的队列绑定到交换机中
        channel.queueBind("topicQueue02","topicExchange","aa.*");

        //  第8步:监听这个随机队列,并获取队列中的消息(数据)
        //      参数1:队列名称
        //      参数2:true自动删除
        //      参数3:""标签
        //      参数4:回调函数
        //  注意:
        //      1、当前被监听(绑定)的队列必须已经存在,并且这个队列已经正确地绑定到了某个交换机中。
        channel.basicConsume("topicQueue02", true, "", new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag,
                                       Envelope envelope,
                                       AMQP.BasicProperties properties,
                                       byte[] body) throws IOException {
                //获取消息数据
                String bodyStr = new String(body, "UTF-8");
                System.out.println("---consumer02中获取的消息(routingkey=aa.*)----"+bodyStr);
            }
        });
        //channel.close();
        //conn.close();
    }
}

(4)Receive03.Main():routingkey=aa.#

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class Receive03 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //  第1步:创建链接工厂对象
        ConnectionFactory factory=new ConnectionFactory();

        //  第2步:在连接工厂中配置RabbitMq的相关连接信息
        factory.setHost("192.168.32.130");  //设置RabbitMQ的主机IP
        factory.setPort(5672);              //设置RabbitMQ的端口号
        factory.setUsername("root");        //设置访问用户名
        factory.setPassword("root");        //设置访问密码

        //  第3步:创建连接和通道对象。
        Connection connection=null;         //定义链接对象
        Channel channel=null;               //定义通道对象
        connection=factory.newConnection(); //实例化链接对象
        channel=connection.createChannel(); //实例化通道对象

        //  第4步:创建消息
        String message ="Hello World!Direct Consumer!!!";

        //  第5步:案例1:topic交换机使用明确队列(但也可以创建随机队列)
        channel.queueDeclare("topicQueue03",true,false,false,null);

        //  第6步:创建direct交换机,需指定Exchange的类型为direct
        //      参数1:交换机名称,取值任意
        //      参数2:交换机类型取值为:direct、queue、topic、headers
        //      参数3:为是否为持久化消息,true表示持久化消息;false表示非持久化。
        //  注意:
        //      1、声明交换机时:如果这个交换机已经存在则会放弃声明。如果交换机不存在在则声明交换机。
        //      2、这行代码是可有可无的,但是在使用前必须要确保这个交换机已经被声明(存在)
        channel.exchangeDeclare("topicExchange", "topic", true);

        //  第7步:将这个随机的队列绑定到交换机中
        channel.queueBind("topicQueue03","topicExchange","aa.#");

        //  第8步:监听这个随机队列,并获取队列中的消息(数据)
        //      参数1:队列名称
        //      参数2:true自动删除
        //      参数3:""标签
        //      参数4:回调函数
        //  注意:
        //      1、当前被监听(绑定)的队列必须已经存在,并且这个队列已经正确地绑定到了某个交换机中。
        channel.basicConsume("topicQueue03", true, "", new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag,
                                       Envelope envelope,
                                       AMQP.BasicProperties properties,
                                       byte[] body) throws IOException {
                //获取消息数据
                String bodyStr = new String(body, "UTF-8");
                System.out.println("---consumer03中获取的消息(routingkey=aa.#)----"+bodyStr);
            }
        });
        //channel.close();
        //conn.close();
    }
}

(5)分别启动Receive01.main()、Receive02.main()、Receive03.main()

管控台,查看队列列表:

 管控台,点击交换机,查看交换机与队列的绑定情况:

2、provider

(1)pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.bjpowernode.rabbitmq</groupId>
    <artifactId>07-rabbitmq-send-topic</artifactId>
    <version>1.0.0</version>

    <dependencies>
        <!--rabbitmq客户端依赖包-->
        <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>5.1.1</version>
        </dependency>
    </dependencies>
</project>

(2)Send.main()

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class Send {
    public static void main(String[] args) throws IOException, TimeoutException {
        //  第1步:创建链接工厂对象
        ConnectionFactory factory=new ConnectionFactory();

        //  第2步:在连接工厂中配置RabbitMq的相关连接信息
        factory.setHost("192.168.32.130");  //设置RabbitMQ的主机IP
        factory.setPort(5672);              //设置RabbitMQ的端口号
        factory.setUsername("root");        //设置访问用户名
        factory.setPassword("root");        //设置访问密码

        //  第3步:创建连接和通道对象。
        Connection connection=null;         //定义链接对象
        Channel channel=null;               //定义通道对象
        connection=factory.newConnection(); //实例化链接对象
        channel=connection.createChannel(); //实例化通道对象

        //  第4步:创建消息
        String message ="topic类型交换机的测试消息";

        //  第5步:创建队列
        //  channel.queueDeclare("directQueue", true, false, false, null);
        //  第6步:创建direct交换机,需指定Exchange的类型为direct
        channel.exchangeDeclare("topicExchange", "topic", true);
        //  第7步:将队列绑定到交换机
        //  channel.queueBind("directQueue","directExchange","directRoutingKey");

        //  第7步:把消息发送到指定队列
        //      参数1:交换机名称。
        //      参数2:fanout类型的交换机不需要routingkey,因此这里置空
        //      参数3:设置消息的属性,可以通过消息属性设置消息是否是持久化的
        //      参数4:具体要发送的消息信息
        //  注意:发送消息时必须确保交换机已经创建,并且确保交换机已经正确地绑定到某个队列上。
        channel.basicPublish("topicExchange","aa",null,message.getBytes("UTF-8"));
        System.out.println("provider消息发送成功: "+message);
//      channel.close();
//      connection.close();
    }
}

(3)启动Send.main()测试,修改多种routingkey格式 

  1. 如果在Send.main()中指定了routingkey=aa,那么只有Receive01(routingkey=aa)和Receive03(routingkey=aa.#)会接收到消息。
  2. 如果在Send.main()中指定了routingkey=aa.bb,那么只有Receive02(routingkey=aa.*)和Receive03(routingkey=aa.#)会接收到消息。
  3. 如果在Send.main()中指定了routingkey=aa.bb.cc,那么只有Receive03(routingkey=aa.#)会接收到消息。

九、fanout交换机与topic交换机类型的区别

相同:

  1. fanout和topic都是一对多的消息匹配方式,即同一个消息可以同时发送到多个队列里面去。
    Topic类型的交换机也是消息一对多的一种交换机类型,它和fanout都能实现同一个消息同时发送给多个队列。

不同:

  1. fanout更适合于使用在同一个功能不同的进程来获取数据。例如,手机app中的消息推送:因为一个app中可能会有很多个用户来进行安装,然后他们都会启动一个随机的队列来接收着自己的数据。
  2. topic更适合于不同的功能模块来接收同一个消息。例如,商城下单成功以后需要发送消息到队列中:假如订单系统routingkey = order.success,假如物流系统监听订单routingkey = order.*,假如发票系统监听routingkey = order.*。
  3. topic可以指定一个明确的队列名称,也可以指定一个随机的队列名称。但是,如果应用在和订单有关的功能中,建议使用明确的队列名称,并且要求为持久化的队列。

十、事务消息

序言

        事务消息与数据库的事务类似,只是MQ中的消息是要保证消息是否会全部发送成功,防止丢失消息的一种策略。ds:即都是保证消息一定能够发送到队列中去。

RabbitMQ有两种方式来解决这个问题:

        通过AMQP提供的事务机制实现;

        使用发送者确认模式实现;

        注:其中发送者确认模式的效率要比事务机制高

1、事务使用

        事务的实现主要是对信道(Channel)的设置,主要的方法有三个:

                channel.txSelect()声明启动事务模式;

                channel.txCommint()提交事务;

                channel.txRollback()回滚事务;

        ds:工作流程:

  1. 第一步:channel.txSelect()启动一个事务。
  2. 第二步:使用channel发送消息,不管发送多少个消息都行。
  3. 第三步:channel.txCommint()提交事务 或  channel.txRollback()回滚事务

2、具体使用案例

(1)provider:演示事务对发送者的影响

pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.bjpowernode.rabbitmq</groupId>
    <artifactId>07-rabbitmq-send-topic</artifactId>
    <version>1.0.0</version>

    <dependencies>
        <!--rabbitmq客户端依赖包-->
        <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>5.1.1</version>
        </dependency>
    </dependencies>
</project>

Send.main():

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class Send {
    public static void main(String[] args) {
        Connection connection=null;         //定义链接对象
        Channel channel=null;               //定义通道对象
        try{
            //  第1步:创建链接工厂对象
            ConnectionFactory factory=new ConnectionFactory();

            //  第2步:在连接工厂中配置RabbitMq的相关连接信息
            factory.setHost("192.168.32.130");  //设置RabbitMQ的主机IP
            factory.setPort(5672);              //设置RabbitMQ的端口号
            factory.setUsername("root");        //设置访问用户名
            factory.setPassword("root");        //设置访问密码

            //  第3步:创建连接和通道对象。
            connection=factory.newConnection(); //实例化链接对象
            channel=connection.createChannel(); //实例化通道对象
            //  第4步:创建消息
            String message ="事务的测试消息";

            //  第5步:创建队列
            channel.queueDeclare("txQueue", true, false, false, null);
            //  第6步:创建direct交换机,需指定Exchange的类型为direct
            channel.exchangeDeclare("directTxExchange", "direct", true);
            //  第7步:将队列绑定到交换机
            channel.queueBind("txQueue","directTxExchange","txRoutingKey");

            //  第7步:把消息发送到指定队列
            /**
             * 如果我们有两条或两条以上的消息呢?如何保证这两条消息要么同时发送到交换机成功,要么同时不发送成功呢?
             * 答:要使用事务
             */
            channel.txSelect();//  启动一个事务。启动事务以后所有定稿到队列中的消息,必须显示地调用txCommit()提交事务  或  tx.Rollback()回滚事务。
            channel.basicPublish("directTxExchange","txRoutingKey",null,message.getBytes("UTF-8"));
            System.out.println(10/0);//  模拟报错
            channel.basicPublish("directTxExchange","txRoutingKey",null,message.getBytes("UTF-8"));
            channel.txCommit();//  提交事务。如果我们调用了txSelect()开启一个事务以后,必须显示地调用txCommit()进行事务的提交,否则消息不会真正地写入队列中。提交以后,此时就会将内存中的消息写入到队列中并释放内存。
            System.out.println("provider消息发送成功: "+message);
    //      channel.close();
    //      connection.close();
        }catch (Exception ex){
            ex.printStackTrace();
        }finally {
            try {
                channel.txRollback();//  回滚事务:放弃当前事务中所有没有提交的消息,释放内存资源。
                channel.close();
                connection.close();
            } catch (IOException e) {
                e.printStackTrace();
            }catch (TimeoutException e) {
                e.printStackTrace();
            }
        }
    }
}

启动Send.main():        

管控台中,交换机列表创建了交换机。队列列表中接收到了两条消息,并可以点击getMessge按钮进行消费。

(2)consumer:演示事务对接收者的影响

3、事务对接收的影响以及防重复处理

生活中遇到的问题

  1. 首先,privoder发送10000条消息。
  2. 其次,consumer中只开启事务,不提交事务。
  3. 最后,效果,consumer运行时手动确认失效。 

解决方案:

        提交事务

案例:

import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class Receive01 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //  第1步:创建链接工厂对象
        ConnectionFactory factory=new ConnectionFactory();

        //  第2步:在连接工厂中配置RabbitMq的相关连接信息
        factory.setHost("192.168.32.130");  //设置RabbitMQ的主机IP
        factory.setPort(5672);              //设置RabbitMQ的端口号
        factory.setUsername("root");        //设置访问用户名
        factory.setPassword("root");        //设置访问密码

        //  第3步:创建连接和通道对象。
        Connection connection=null;         //定义链接对象
        Channel channel=null;               //定义通道对象
        connection=factory.newConnection(); //实例化链接对象
        channel=connection.createChannel(); //实例化通道对象

        //  第4步:创建消息
        String message ="Hello World!Direct Consumer!!!";

        //  第5步:创建队列
        channel.queueDeclare("confirmQueue", true, false, false, null);


        //  第6步:创建direct交换机,需指定Exchange的类型为direct
        //      参数1:交换机名称,取值任意
        //      参数2:交换机类型取值为:direct、queue、topic、headers
        //      参数3:为是否为持久化消息,true表示持久化消息;false表示非持久化。
        //  注意:
        //      1、声明交换机时:如果这个交换机已经存在则会放弃声明。如果交换机不存在在则声明交换机。
        //      2、这行代码是可有可无的,但是在使用前必须要确保这个交换机已经被声明(存在)
        channel.exchangeDeclare("directConfirmExchange", "direct", true);

        //  第7步:将队列绑定到交换机
        //      参数1:队列名称。
        //      参数2:交换机名称。
        //      参数3:消息的routingKey(就是bindingKey)。其实这是一个绑定key,即bindingKey,但这里参数名叫routingKey而已。
        //  注意:
        //      1、在进行队列和交换机的绑定时,必须要确保交换机和队列已经成功的声明(存在)
        channel.queueBind("confirmQueue","directConfirmExchange","confirmRoutingKey");

        //  第8步:监听某个队列,并获取队列中的消息(数据)
        //      参数1:队列名称
        //      参数2:true自动删除
        //      参数3:""标签
        //      参数4:回调函数
        //  注意:
        //      1、当前被监听(绑定)的队列必须已经存在,并且这个队列已经正确地绑定到了某个交换机中。
        /**
         * 1、消费者自动消息确认模式:
         *    参数2:为消息的确认机制。
         *          如果为true,表示使用自动消息确认,确认以后消息会从队列中移除。
         *    注意:如果我们只是接收了消息,但是还没有来得及去处理,当前程序就崩溃。
         *         或者
         *         在进行处理的过程中,遇到如数据库不可用等。
         *         那么由于消息是自动确认的,此时这条消息就会在接收完成以后自动从队列中被删除掉。这种情况下,我们会认为是丢失消息。
         *         消息消费了,但我们的业务没有处理成功(完成)呀,这就是问题。
         *         解决方案:手动确认。
         * 2、消费者手动消息确认模式:
         *    参数2:为消息的确认机制。
         *          如果为false,表示使用手动消息确认。
         *    显示调用手动确认方法:
         *          手动确认消息,方式1:basicAck(): 用于肯定确认,multiple参数用于多个消息确认。可单条确认,也可以是批量确认。
         *          手动确认消息,方式2:basicRecover():是路由不成功的消息可以使用recovery重新发送到队列中(还给队列)。
         *          手动确认消息,方式3:basicReject():是接收端告诉服务器这个消息我拒绝接收,不处理,可以设置是否放回到队列中还是丢掉,而且只能一次拒绝一个消息,官网中有明确说明不能批量拒绝消息,为解决批量拒绝消息才有了basicNack。
         *          手动确认消息,方式4:basicNack():可以一次拒绝N条消息,客户端可以设置basicNack方法的multiple参数为true。
         *          注:同一条消息只能拒绝一次,不能重复拒绝。
         */
        channel.txSelect(); //  开启事务 //  演示:事务对接收的影响以及防重复处理
        channel.basicConsume("confirmQueue", false, "", new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag,
                                       Envelope envelope,
                                       AMQP.BasicProperties properties,
                                       byte[] body) throws IOException {
                //System.out.println(10/0); //  TO DO 模拟报错
                //获取消息数据
                String bodyStr = new String(body, "UTF-8");
                System.out.println("---消费者 处理了消息----"+bodyStr);

                /**
                 *   获取消息的编号,我们需要根据消息的编号来确认消息。
                 */
                long tag = envelope.getDeliveryTag();
                /**
                 *   获取当前内部类中的通道Channel
                 */
                Channel c = super.getChannel();
                /**
                 *   表示使用手动确认消息,确认以后表示当前消息已经成功地处理了,需要从队列中移除掉。
                 *   这个方法应该在当前消息上的处理程序全部完成以后执行。
                 *   参数1:为消息的编号
                 *   参数2:为是否确认多个。
                 *         如果为true表示需要确认小于等于当前编号的所有消息。
                 *         如果为false表示只确认当前消息。
                 */
                c.basicAck(tag,true);
                /**
                 * 注意:如果启动了事务,而消息的消费者的消息确认模式为手动确认,那么必须要提交事务。
                 *      如果不提交事务,那么即使调用了手动确认方法,那么消息也不会从队列中移除掉。
                 */
                c.txCommit();// 演示:事务对接收的影响以及防重复处理

            }
        });

        //channel.close();
        //conn.close();
    }
}

生活中遇到的问题:

        消息的重复处理

解决方案:

        使用boolean isRedeliver = envelope.isRedeliver();来判断消息处理的状态

案例:

import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class Receive01 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //  第1步:创建链接工厂对象
        ConnectionFactory factory=new ConnectionFactory();

        //  第2步:在连接工厂中配置RabbitMq的相关连接信息
        factory.setHost("192.168.32.130");  //设置RabbitMQ的主机IP
        factory.setPort(5672);              //设置RabbitMQ的端口号
        factory.setUsername("root");        //设置访问用户名
        factory.setPassword("root");        //设置访问密码

        //  第3步:创建连接和通道对象。
        Connection connection=null;         //定义链接对象
        Channel channel=null;               //定义通道对象
        connection=factory.newConnection(); //实例化链接对象
        channel=connection.createChannel(); //实例化通道对象

        //  第4步:创建消息
        String message ="Hello World!Direct Consumer!!!";

        //  第5步:创建队列
        channel.queueDeclare("confirmQueue", true, false, false, null);


        //  第6步:创建direct交换机,需指定Exchange的类型为direct
        //      参数1:交换机名称,取值任意
        //      参数2:交换机类型取值为:direct、queue、topic、headers
        //      参数3:为是否为持久化消息,true表示持久化消息;false表示非持久化。
        //  注意:
        //      1、声明交换机时:如果这个交换机已经存在则会放弃声明。如果交换机不存在在则声明交换机。
        //      2、这行代码是可有可无的,但是在使用前必须要确保这个交换机已经被声明(存在)
        channel.exchangeDeclare("directConfirmExchange", "direct", true);

        //  第7步:将队列绑定到交换机
        //      参数1:队列名称。
        //      参数2:交换机名称。
        //      参数3:消息的routingKey(就是bindingKey)。其实这是一个绑定key,即bindingKey,但这里参数名叫routingKey而已。
        //  注意:
        //      1、在进行队列和交换机的绑定时,必须要确保交换机和队列已经成功的声明(存在)
        channel.queueBind("confirmQueue","directConfirmExchange","confirmRoutingKey");

        //  第8步:监听某个队列,并获取队列中的消息(数据)
        //      参数1:队列名称
        //      参数2:true自动删除
        //      参数3:""标签
        //      参数4:回调函数
        //  注意:
        //      1、当前被监听(绑定)的队列必须已经存在,并且这个队列已经正确地绑定到了某个交换机中。
        /**
         * 1、消费者自动消息确认模式:
         *    参数2:为消息的确认机制。
         *          如果为true,表示使用自动消息确认,确认以后消息会从队列中移除。
         *    注意:如果我们只是接收了消息,但是还没有来得及去处理,当前程序就崩溃。
         *         或者
         *         在进行处理的过程中,遇到如数据库不可用等。
         *         那么由于消息是自动确认的,此时这条消息就会在接收完成以后自动从队列中被删除掉。这种情况下,我们会认为是丢失消息。
         *         消息消费了,但我们的业务没有处理成功(完成)呀,这就是问题。
         *         解决方案:手动确认。
         * 2、消费者手动消息确认模式:
         *    参数2:为消息的确认机制。
         *          如果为false,表示使用手动消息确认。
         *    显示调用手动确认方法:
         *          手动确认消息,方式1:basicAck(): 用于肯定确认,multiple参数用于多个消息确认。可单条确认,也可以是批量确认。
         *          手动确认消息,方式2:basicRecover():是路由不成功的消息可以使用recovery重新发送到队列中(还给队列)。
         *          手动确认消息,方式3:basicReject():是接收端告诉服务器这个消息我拒绝接收,不处理,可以设置是否放回到队列中还是丢掉,而且只能一次拒绝一个消息,官网中有明确说明不能批量拒绝消息,为解决批量拒绝消息才有了basicNack。
         *          手动确认消息,方式4:basicNack():可以一次拒绝N条消息,客户端可以设置basicNack方法的multiple参数为true。
         *          注:同一条消息只能拒绝一次,不能重复拒绝。
         */
        channel.txSelect(); //  开启事务 //  演示:事务对接收的影响以及防重复处理
        channel.basicConsume("confirmQueue", false, "", new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag,
                                       Envelope envelope,
                                       AMQP.BasicProperties properties,
                                       byte[] body) throws IOException {
                /**
                 * envelope.isRedeliver()获取当前消息是否被接收过。
                 * 如果返回值为false,表示消息之前没有被接收过。
                 * 如果返回值为true,表示消息之前被接收过,可能也处理完成。因此我们要进行消息的防重复处理。
                 */
                boolean isRedeliver = envelope.isRedeliver();
                Channel c = super.getChannel();
                long tag = envelope.getDeliveryTag();
                if(!isRedeliver){
                    //获取消息数据
                    String message = new String(body, "UTF-8");
                    System.out.println("---消费者 处理了消息----"+message);
                    c.basicAck(tag,true);
                    c.txCommit();
                }else {
                    /**
                     * 防重复处理:
                     *      方案:例如,查询数据库中的数据,看看是否成功添加了记录或已经修改过了记录。
                     *      如果经过判断这条消息没有被处理完成,则需要重新处理消息,然后确认掉这条消息。
                     *      如果已经处理过这么消息,则直接确认消息即可,不需要进行其他处理操作。
                     */
                    // ...... 例如,查询数据库中的数据,看看是否成功添加了记录或已经修改过了记录。
                    c.basicAck(tag,true);
                    c.txCommit();
                }
            }
        });

        //channel.close();
        //conn.close();
    }
}

十一、消息的确认模式

序言

(1)生活中遇到的问题:事务的性能比较低

        ds:事务可以保证事务的原子性、一致性、隔离性,但使用事务的性能比较低。假如100次消息发送中,99次都是正常的消息推送,只有一次是异常的消息推送(需要回滚)。那么其中99次的正常的消息推送,在频繁的开始和提交事务中显得性能低下。

(2)解决方案

        ds:使用消息的确认模式,同事务一样,都可以保证消息一定能够发送成功。                  

1、消息的发送者确认模式

序言

  思想、流程、原理

ds:  事务如果遇到异常,消息拒绝发送,即发送不成功。

      在发送者边进行确认。

       保证消息一定能够发送到服务器的机制,服务器返回消息发送的状态(即是否发送成功功)。

       发送者消息的确认模式下,遇到异常,会补发消息,直到成功发送。

分类

  1. 普通发送方确认模式
  2. 批量确认模式
  3. 异步监听发送方确认模式   

1.普通发送方确认模式

  1. channel.confirmSelect():开启发送方确认模式

  2. channel.waitForConfirms()

    1. 可以同时确认一个或多个消息。

    2.       阻塞线程等待服务返回响应,用于确认消息是否发送成功。如果服务确认消息已经发送完成则返回true,否则返回false。
            超时时间:可以为这个方法指定一个超时时间(毫秒)。如果超过了指定的时间则会招聘异常InterruptedException(),表示服务器出现问题,需要补发消息。
            消息补发概念:所谓消息补发就是重新发送一次消息。
            消息补发方法:第一种:服务器返回false时立即补发。第二种:使用递归进行补发。第三种:将消息缓存到redis中稍后利用定时任务补发。
            需要注意的是:现实中即使返回的是false或者招聘异常,消息也是有可能发送成功的。因此如果我们要求这条消息一定要发送到队列中(例如订单数据),那么我们可以采用消息补发。

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class SendwaitForConfirms {
    public static void main(String[] args) {
        Connection connection=null;         //定义链接对象
        Channel channel=null;               //定义通道对象
        try{
            //  第1步:创建链接工厂对象
            ConnectionFactory factory=new ConnectionFactory();

            //  第2步:在连接工厂中配置RabbitMq的相关连接信息
            factory.setHost("192.168.32.130");  //设置RabbitMQ的主机IP
            factory.setPort(5672);              //设置RabbitMQ的端口号
            factory.setUsername("root");        //设置访问用户名
            factory.setPassword("root");        //设置访问密码

            //  第3步:创建连接和通道对象。
            connection=factory.newConnection(); //实例化链接对象
            channel=connection.createChannel(); //实例化通道对象
            //  第4步:创建消息
            String message ="发送者确认模式Confirm的测试消息";

            //  第5步:创建队列
            channel.queueDeclare("confirmQueue", true, false, false, null);
            //  第6步:创建direct交换机,需指定Exchange的类型为direct
            channel.exchangeDeclare("directConfirmExchange", "direct", true);
            //  第7步:将队列绑定到交换机
            channel.queueBind("confirmQueue","directConfirmExchange","confirmRoutingKey");
            //  第8步:启动发送者confirm消息确认模式
            channel.confirmSelect();
            //  第9步:把消息发送到指定队列
            channel.basicPublish("directConfirmExchange","confirmRoutingKey",null,message.getBytes("UTF-8"));
            //  第10步:
            /**
             * 阻塞线程等待服务返回响应,用于确认消息是否发送成功。如果服务确认消息已经发送完成则返回true,否则返回false。
             * 超时时间:可以为这个方法指定一个超时时间(毫秒)。如果超过了指定的时间则会招聘异常InterruptedException(),表示服务器出现问题,需要补发消息。
             * 消息补发概念:所谓消息补发就是重新发送一次消息。
             * 消息补发方法:第一种:服务器返回false时立即补发。第二种:使用递归进行补发。第三种:将消息缓存到redis中稍后利用定时任务补发。
             * 需要注意的是:现实中即使返回的是false或者招聘异常,消息也是有可能发送成功的。因此如果我们要求这条消息一定要发送到队列中(例如订单数据),那么我们可以采用消息补发。
             */
            boolean b = channel.waitForConfirms();
            System.out.println("provider消息发送成功: "+message);
        }catch (Exception ex){
            ex.printStackTrace();
        }finally {
            try {
                channel.close();
                connection.close();
            } catch (IOException e) {
                e.printStackTrace();
            }catch (TimeoutException e) {
                e.printStackTrace();
            }
        }
    }
}

2.批量确认模式

(1)生活中的需求

        ds:批量,即一次性确认多条消息。

(2)解决方案:看注释

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class SendwaitForConfirmsOrDie {
    public static void main(String[] args) {
        Connection connection=null;         //定义链接对象
        Channel channel=null;               //定义通道对象
        try{
            //  第1步:创建链接工厂对象
            ConnectionFactory factory=new ConnectionFactory();

            //  第2步:在连接工厂中配置RabbitMq的相关连接信息
            factory.setHost("192.168.32.130");  //设置RabbitMQ的主机IP
            factory.setPort(5672);              //设置RabbitMQ的端口号
            factory.setUsername("root");        //设置访问用户名
            factory.setPassword("root");        //设置访问密码

            //  第3步:创建连接和通道对象。
            connection=factory.newConnection(); //实例化链接对象
            channel=connection.createChannel(); //实例化通道对象
            //  第4步:创建消息
            String message ="发送者确认模式Confirm的测试消息";

            //  第5步:创建队列
            channel.queueDeclare("confirmQueue", true, false, false, null);
            //  第6步:创建direct交换机,需指定Exchange的类型为direct
            channel.exchangeDeclare("directConfirmExchange", "direct", true);
            //  第7步:将队列绑定到交换机
            channel.queueBind("confirmQueue","directConfirmExchange","confirmRoutingKey");
            //  第8步:启动发送者confirm消息确认模式
            channel.confirmSelect();
            //  第9步:把消息发送到指定队列
            channel.basicPublish("directConfirmExchange","confirmRoutingKey",null,message.getBytes("UTF-8"));
            //  第10步:批量确认
            /**
             * 思想:
             *      批量消息确认是指:它会同时向服务中确认之前(当前)通道中发送的所有消息是否已经全部成功写入。
             *      waitForConfirmsOrDie()这个方法没有任何的返回值。
             *      如果服务器中有一条消息没有能够成功  或  向服务器发送确认时服务器不可访问,都被认定为消息确认失败。此时可能有消息没见有发送成功,因此我们需要进行消息的补发,补发消息到队列。
             *      注1:如果无法向服务器获取确认信息,那么waitForConfirmsOrDie()方法就会抛出interruptedException异常,这时就需要补发消息到队列。
             *      注2:waitForConfirmsOrDie()方法可以指定一个参数timeout,用于等待服务器的确认时间,如果超时也会抛出异常,这时也需要补发消息到队列。
             *      注3:批量消息确认的速度比普通的消息确认要快。
             *      注4:如果批量确认招聘异常,那么就要进行消息的补发。但因为我们不能确定具体是哪条消息没有完成发送,所以需要将本次发送的所有消息全部都进行补发。
             */
            channel.waitForConfirmsOrDie();
            System.out.println("provider消息发送成功: "+message);
        }catch (Exception ex){
            ex.printStackTrace();
        }finally {
            try {
                channel.close();
                connection.close();
            } catch (IOException e) {
                e.printStackTrace();
            }catch (TimeoutException e) {
                e.printStackTrace();
            }
        }
    }
}

3.异步监听发送方确认模式

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConfirmListener;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class SendaddConfirmListener {
    public static void main(String[] args) {
        Connection connection=null;         //定义链接对象
        Channel channel=null;               //定义通道对象
        try{
            //  第1步:创建链接工厂对象
            ConnectionFactory factory=new ConnectionFactory();

            //  第2步:在连接工厂中配置RabbitMq的相关连接信息
            factory.setHost("192.168.32.130");  //设置RabbitMQ的主机IP
            factory.setPort(5672);              //设置RabbitMQ的端口号
            factory.setUsername("root");        //设置访问用户名
            factory.setPassword("root");        //设置访问密码

            //  第3步:创建连接和通道对象。
            connection=factory.newConnection(); //实例化链接对象
            channel=connection.createChannel(); //实例化通道对象
            //  第4步:创建消息
            String message ="发送者确认模式Confirm的测试消息";

            //  第5步:创建队列
            channel.queueDeclare("confirmQueue", true, false, false, null);
            //  第6步:创建direct交换机,需指定Exchange的类型为direct
            channel.exchangeDeclare("directConfirmExchange", "direct", true);
            //  第7步:将队列绑定到交换机
            channel.queueBind("confirmQueue","directConfirmExchange","confirmRoutingKey");
            //  第8步:启动发送者confirm消息确认模式
            channel.confirmSelect();
            //  第9步:异步监听发送方确认模式
            /**先启动监听,再发消息*/
            //  ------第一步:创建一个监听器----
            channel.addConfirmListener(new ConfirmListener() {
                //  ------第二步:实现消息确认以后的回调方法。----
                /**
                 * @param l  为被确认的消息的编号,从1开始自动递增,用于标记当前是第条消息。
                 * @param b  为当前消息是否同时确认了多个
                 *  注意:如果参数2为true,则表明本次确认同时确认了多条消息,消息编号小于等于当前参数1(消息编号)的所有消息全部被确认。
                 *       如果参数2为false,则表明只确认了当前编号的消息。
                 */
                public void handleAck(long l, boolean b) throws IOException {
                    System.out.println("消息被确认了 --- 消息编号:"+l+"   是否确认了多条"+b);
                }
                //  ------第三步:实现消息没有确认以后的回调方法。----

                /**
                 *  如果这个方法被执行,表示当前的消息没有被确认,需要进行补发。
                 *  @param l  为没有被确认的消息的编号,从1开始自动递增,用于标记当前是第条消息。
                 *  @param b  为当前消息是否同时没有确认了多个
                 *  注意:如果参数2为true,则表示小于当前编号的所有消息可能都没有发送成功,需要进行消息的补发。
                 *       如果参数2为false,则表示当前编号的消息没有发送成功,只需要进行补发当前消息即可。
                 */
                public void handleNack(long l, boolean b) throws IOException {
                    System.out.println("消息没有被确认 --- 消息编号:"+l+"   是否没有被确认了多条"+b);
                }
            });
            //  第10步:把消息发送到指定队列
            /**先启动监听,再发消息*/
            /**批量发送消息*/
            for(int i=0;i<10000;i++){
                channel.basicPublish("directConfirmExchange","confirmRoutingKey",null,message.getBytes("UTF-8"));
            }
            System.out.println("provider消息发送成功: "+message);
        }catch (Exception ex){
            ex.printStackTrace();
        }finally {
            try {
                channel.close();
                connection.close();
            } catch (IOException e) {
                e.printStackTrace();
            }catch (TimeoutException e) {
                e.printStackTrace();
            }
        }
    }
}

2、消息的消费者确认模式

序言

       ds:在实际工作中,消费者消息确认模式才是常用的,对我们来说最最重要的消息确认方式。

       生活中遇到的问题:

  1. 首先,provider发送10000条消息。
  2. 其次,consumer在接收消息时使用System.out.println(10/0); // TO DO 模拟报错。
  3. 最后,效果:在运行消费者消费消息时,由于报错导致一个消息都没有成功消费,但是管控台中显示消息队列中少于10000条消息,即消息会丢失。

       解决方案:

  1.  消费都使用手动消息确认模式           

       案例:    

import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class Receive01 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //  第1步:创建链接工厂对象
        ConnectionFactory factory=new ConnectionFactory();

        //  第2步:在连接工厂中配置RabbitMq的相关连接信息
        factory.setHost("192.168.32.130");  //设置RabbitMQ的主机IP
        factory.setPort(5672);              //设置RabbitMQ的端口号
        factory.setUsername("root");        //设置访问用户名
        factory.setPassword("root");        //设置访问密码

        //  第3步:创建连接和通道对象。
        Connection connection=null;         //定义链接对象
        Channel channel=null;               //定义通道对象
        connection=factory.newConnection(); //实例化链接对象
        channel=connection.createChannel(); //实例化通道对象

        //  第4步:创建消息
        String message ="Hello World!Direct Consumer!!!";

        //  第5步:创建队列
        channel.queueDeclare("confirmQueue", true, false, false, null);


        //  第6步:创建direct交换机,需指定Exchange的类型为direct
        //      参数1:交换机名称,取值任意
        //      参数2:交换机类型取值为:direct、queue、topic、headers
        //      参数3:为是否为持久化消息,true表示持久化消息;false表示非持久化。
        //  注意:
        //      1、声明交换机时:如果这个交换机已经存在则会放弃声明。如果交换机不存在在则声明交换机。
        //      2、这行代码是可有可无的,但是在使用前必须要确保这个交换机已经被声明(存在)
        channel.exchangeDeclare("directConfirmExchange", "direct", true);

        //  第7步:将队列绑定到交换机
        //      参数1:队列名称。
        //      参数2:交换机名称。
        //      参数3:消息的routingKey(就是bindingKey)。其实这是一个绑定key,即bindingKey,但这里参数名叫routingKey而已。
        //  注意:
        //      1、在进行队列和交换机的绑定时,必须要确保交换机和队列已经成功的声明(存在)
        channel.queueBind("confirmQueue","directConfirmExchange","confirmRoutingKey");

        //  第8步:监听某个队列,并获取队列中的消息(数据)
        //      参数1:队列名称
        //      参数2:true自动删除
        //      参数3:""标签
        //      参数4:回调函数
        //  注意:
        //      1、当前被监听(绑定)的队列必须已经存在,并且这个队列已经正确地绑定到了某个交换机中。
        /**
         * 1、消费者自动消息确认模式:
         *    参数2:为消息的确认机制。
         *          如果为true,表示使用自动消息确认,确认以后消息会从队列中移除。
         *    注意:如果我们只是接收了消息,但是还没有来得及去处理,当前程序就崩溃。
         *         或者
         *         在进行处理的过程中,遇到如数据库不可用等。
         *         那么由于消息是自动确认的,此时这条消息就会在接收完成以后自动从队列中被删除掉。这种情况下,我们会认为是丢失消息。
         *         消息消费了,但我们的业务没有处理成功(完成)呀,这就是问题。
         *         解决方案:手动确认。
         * 2、消费者手动消息确认模式:
         *    参数2:为消息的确认机制。
         *          如果为false,表示使用手动消息确认。
         *    显示调用手动确认方法:
         *          手动确认消息,方式1:basicAck(): 用于肯定确认,multiple参数用于多个消息确认。可单条确认,也可以是批量确认。
         *          手动确认消息,方式2:basicRecover():是路由不成功的消息可以使用recovery重新发送到队列中(还给队列)。
         *          手动确认消息,方式3:basicReject():是接收端告诉服务器这个消息我拒绝接收,不处理,可以设置是否放回到队列中还是丢掉,而且只能一次拒绝一个消息,官网中有明确说明不能批量拒绝消息,为解决批量拒绝消息才有了basicNack。
         *          手动确认消息,方式4:basicNack():可以一次拒绝N条消息,客户端可以设置basicNack方法的multiple参数为true。
         *          注:同一条消息只能拒绝一次,不能重复拒绝。
         */
        channel.basicConsume("confirmQueue", false, "", new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag,
                                       Envelope envelope,
                                       AMQP.BasicProperties properties,
                                       byte[] body) throws IOException {
                //System.out.println(10/0); //  TO DO 模拟报错
                //获取消息数据
                String bodyStr = new String(body, "UTF-8");
                System.out.println("---消费者 处理了消息----"+bodyStr);

                /**
                 *   获取消息的编号,我们需要根据消息的编号来确认消息。
                 */
                long tag = envelope.getDeliveryTag();
                /**
                 *   获取当前内部类中的通道Channel
                 */
                Channel c = super.getChannel();
                /**
                 *   表示使用手动确认消息,确认以后表示当前消息已经成功地处理了,需要从队列中移除掉。
                 *   这个方法应该在当前消息上的处理程序全部完成以后执行。
                 *   参数1:为消息的编号
                 *   参数2:为是否确认多个。
                 *         如果为true表示需要确认小于等于当前编号的所有消息。
                 *         如果为false表示只确认当前消息。
                 */
                c.basicAck(tag,true);


            }
        });
        //channel.close();
        //conn.close();
    }
}

十二、SpringBoot集成RabbitMQ

1、生活中的想法、需求、问题

        ds:使用springboot让RabbitMQ的开发更加快捷方便。

2、想法:Direct模式消息发送和接收 

(1)坑:no exchange 'bootDirectExchange' in vhost '/', class-id=60, method-id=40)

生活中的问题:

         报错详情:2022-04-24 01:52:31.516 ERROR 2696 --- [168.32.131:5672] o.s.a.r.c.CachingConnectionFactory       : Channel shutdown: channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no exchange 'bootDirectExchange' in vhost '/', class-id=60, method-id=40)

        ds:即没有创建交换机。

解决方案: 

        ds:使用springboot的配置类,创建交换机和队列,并把交换机与队列绑定。

(2)案例

1)provider:pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.bjpowernode.rabbitmq</groupId>
    <artifactId>09-rabbitmq-send-springboot</artifactId>
    <version>1.0.0</version>

    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <!--springboot起步依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <!--添加AMQP的起步依赖,添加成功后就会自动引入RabbitMQ的依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

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

    </build>
</project>

2)provider:application.properties

#配置RabbitMQ的相关连接信息(单机版)
spring.rabbitmq.host=192.168.32.131
spring.rabbitmq.port=5672
spring.rabbitmq.username=root
spring.rabbitmq.password=root

3)provider:SendService.java

package com.bjpowernode.rabbitmq.service;

public interface SendService {
    void sendMessage(String message);
    void sendFanoutMessage(String message);
    void sendTopicMessage(String message);
}

4)provider:SendServiceImpl.java

package com.bjpowernode.rabbitmq.service.impl;

import com.bjpowernode.rabbitmq.service.SendService;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

//  给这个XxxServiceImpl取个名称
@Service("sendService")
public class SendServiceImpl implements SendService {
    //  注入Amqp的模板类,利用这个对象来发送和接收消息
    @Resource
    private AmqpTemplate amqpTemplate;

    public void sendMessage(String message) {
        /**
         * 发送消息
         * 参数 1 为交换机名
         * 参数 2 为RoutingKey
         * 参数 3 为具体发送的消息数据
         */
        amqpTemplate.convertAndSend("bootDirectExchange","bootDirectRoutingKey",message);
    }




    public void sendFanoutMessage(String message) {
        amqpTemplate.convertAndSend("fanoutExchange","",message);
    }

    public void sendTopicMessage(String message) {
        amqpTemplate.convertAndSend("topicExchange","aa.bb.cc",message);
    }
}

5)provider:Application.java

package com.bjpowernode.rabbitmq;


import com.bjpowernode.rabbitmq.service.SendService;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;


@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        ApplicationContext ac=SpringApplication.run(Application.class, args);
        SendService service= (SendService) ac.getBean("sendService");
        //  向服务器(RabbitMq)发送消息
        service.sendMessage("Boot的测试数据");

//        service.sendFanoutMessage("Boot的Fanout测试数据");
//        service.sendTopicMessage("Boot的Topic测数据数据key 为 aa.bb.cc");
    }
}

6)provider:RabbitMQConfig .java

package com.bjpowernode.rabbitmq.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitMQConfig {
    //  配置一个Direct类型的交换机
    @Bean
    public DirectExchange directExchangeCreate(){
        return new DirectExchange("bootDirectExchange");
    }

    //  配置一个队列
    @Bean
    public Queue directQueueCreate(){
        return new Queue("bootDirectQueue");
    }

    /**
     * 配置交换机与队列的绑定
     * @param bootDirectQueue  需要绑定的队列的对象,参数名必须要与某个@Bean下的方法的方法名完成相同,这样就会自动注入
     * @param directExchangeCreate  需要绑定的交换机的对象,参数名必须要与某个@Bean下的方法的方法名完成相同,这样就会自动注入
     * @return
     */
    @Bean
    public Binding directExchangeAndQueueBinding(Queue directQueueCreate,DirectExchange directExchangeCreate){
        //  完成绑定
        //      参数1:为需要绑定的队列
        //      参数2:为需要绑定的交换机
        //      参数3:routingkey
        return BindingBuilder.bind(directQueueCreate).to(directExchangeCreate).with("bootDirectRoutingKey");
    }
}

1)consumer:pom.xml 同上

2)consumer:application.properties 同上

3)consumer:ReceiveService.java

package com.bjpowernode.rabbitmq.service;

public interface ReceiveService {
    void receive();
}

4)consumer:ReceiveServiceImpl.java

package com.bjpowernode.rabbitmq.service.impl;

import com.bjpowernode.rabbitmq.service.ReceiveService;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

//  给这个XxxServiceImpl取个名称
@Service("receiveService")
public class ReceiveServiceImpl implements ReceiveService {
    //  注入Amqp的模板类,利用这个对象来发送和接收消息
    @Resource
    private AmqpTemplate amqpTemplate;

    @Override
    public void receive() {
        //  监听队列,队列名称为bootDirectQueue
        String receiveStr = (String)amqpTemplate.receiveAndConvert("bootDirectQueue");
        System.out.println("receive的Message = "+receiveStr);
    }
}

5)consumer:Application.java

package com.bjpowernode.rabbitmq;


import com.bjpowernode.rabbitmq.service.ReceiveService;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;


@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        ApplicationContext ac=SpringApplication.run(Application.class, args);
        ReceiveService service= (ReceiveService) ac.getBean("receiveService");
        //  从服务器(RabbitMq)接收消息
        service.receive();

//        service.sendFanoutMessage("Boot的Fanout测试数据");
//        service.sendTopicMessage("Boot的Topic测数据数据key 为 aa.bb.cc");
    }
}

6)consumer:RabbitMQConfig.java 同上

(3)坑:以上代码的消息消费者不能不间断地持续的接收消息(provider多次发送消息,consumer不会主动消费消息)

生活中的问题:

/**
     * 不建议使用:
     *      这个接收不是不间断地接收消息,
     *      每执行一次这个方法只能接收一条消息,
     *      如果有新消息进入队列(如多次运行provider),消费者不会不间断地自动地接收消息。
     */
    @Override
    public void receive() {
        //  监听队列,队列名称为bootDirectQueue
        String receiveStr = (String)amqpTemplate.receiveAndConvert("bootDirectQueue");
        System.out.println("receive的Message = "+receiveStr);
    }

解决方案:

package com.bjpowernode.rabbitmq.service.impl;

import com.bjpowernode.rabbitmq.service.ReceiveService;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

//  给这个XxxServiceImpl取个名称
@Service("receiveService")
public class ReceiveServiceImpl implements ReceiveService {
    //  注入Amqp的模板类,利用这个对象来发送和接收消息
    @Resource
    private AmqpTemplate amqpTemplate;

    /**
     * 不建议使用:
     *      这个接收不是不间断地接收消息,
     *      每执行一次这个方法只能接收一条消息,
     *      如果有新消息进入队列(如多次运行provider),消费者不会不间断地自动地接收消息。
     */
    @Override
    public void receive() {
        //  监听队列,队列名称为bootDirectQueue
//        String receiveStr = (String)amqpTemplate.receiveAndConvert("bootDirectQueue");
//        System.out.println("receive的Message = "+receiveStr);
    }

    /**
     * @RabbitListener 注解用于标记当前方法是一个RabbitMQ的消息监听方法,作用是用于持续性地监听队列,从而实现消费者持续性地自动接收消息。
     *                 这个方法不需要手动调用,spring会自动运行这个监听。
     *        queues参数,用于指定一个已经存在的队列名称,用于进行队列的监听。
     * @param message  接收到的具体的消息数据
     */
    @RabbitListener(queues = {"bootDirectQueue"})
    public void directReceive(String message){
        System.out.println("消费者      监听器接收的消息 = "+message);
    }
}
package com.bjpowernode.rabbitmq;


import com.bjpowernode.rabbitmq.service.ReceiveService;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;


@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        ApplicationContext ac=SpringApplication.run(Application.class, args);
        ReceiveService service= (ReceiveService) ac.getBean("receiveService");
        //  从服务器(RabbitMq)接收消息
        //  如果使用了消息监听器接收消息,那么就不需要(不能)调用接收消息方法来接收消息了
        //  service.receive();

//        service.sendFanoutMessage("Boot的Fanout测试数据");
//        service.sendTopicMessage("Boot的Topic测数据数据key 为 aa.bb.cc");
    }
}

(4)坑:如果在消费者接收消息后10/0,效果:消息能接收到;死循环报错;管控台中消费还是1,没有被确认消费掉。

生活中遇到的问题代码:

/**
     * @RabbitListener 注解用于标记当前方法是一个RabbitMQ的消息监听方法,作用是用于持续性地监听队列,从而实现消费者持续性地自动接收消息。
     *                 这个方法不需要手动调用,spring会自动运行这个监听。
     *        queues参数,用于指定一个已经存在的队列名称,用于进行队列的监听。
     * @param message  接收到的具体的消息数据
     */
    @RabbitListener(queues = {"bootDirectQueue"})
    public void directReceive(String message){
        System.out.println("消费者      监听器接收的消息 = "+message);
        System.out.println(10/0); // TO DO 模拟报错
    }

解决方案:

package com.bjpowernode.rabbitmq.service.impl;

import com.bjpowernode.rabbitmq.service.ReceiveService;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

//  给这个XxxServiceImpl取个名称
@Service("receiveService")
public class ReceiveServiceImpl implements ReceiveService {
    //  注入Amqp的模板类,利用这个对象来发送和接收消息
    @Resource
    private AmqpTemplate amqpTemplate;

    /**
     * 不建议使用:
     *      这个接收不是不间断地接收消息,
     *      每执行一次这个方法只能接收一条消息,
     *      如果有新消息进入队列(如多次运行provider),消费者不会不间断地自动地接收消息。
     */
    @Override
    public void receive() {
        //  监听队列,队列名称为bootDirectQueue
//        String receiveStr = (String)amqpTemplate.receiveAndConvert("bootDirectQueue");
//        System.out.println("receive的Message = "+receiveStr);
    }

    /**
     * @RabbitListener 注解用于标记当前方法是一个RabbitMQ的消息监听方法,作用是用于持续性地监听队列,从而实现消费者持续性地自动接收消息。
     *                 这个方法不需要手动调用,spring会自动运行这个监听。
     *        queues参数,用于指定一个已经存在的队列名称,用于进行队列的监听。
     * @param message  接收到的具体的消息数据
     *  注意:
     *     如果当前监听方法正常结束,spring就会自动确认掉消息。
     *     如果当前监听方法出现异常,则spring不会自动确认掉消息(即使接收到消息了)。
     *     因此在处理消息时,我们需要做好消息的防重复处理工作,不然就会有重复数据被处理。
     */
    @RabbitListener(queues = {"bootDirectQueue"})
    public void directReceive(String message){
        System.out.println("消费者      监听器接收的消息 = "+message);
        //System.out.println(10/0); // TO DO 模拟报错
    }
}

3、想法:fanout模式消息发送和接收 

(1)坑:fanout需要随机生成队列名称,而在springboot配置类中没办法随机生成队列名称。

解决方案:

  1. 第一种方案(也有坑):在springboot配置类中在配置队列时,使用如UUID之类地工具每次自动生成新的队列名称。但坑又出现了,在consumer这边指定接收队列时,使用的是字符串常量,问题还是在。
  2. 第二种方案:使用springboot + rabbitmq的全注解开发方式。

(2)案例

comsumer:其它同上上

comsumer:ReceiveServiceImpl.java

package com.bjpowernode.rabbitmq.service.impl;

import com.bjpowernode.rabbitmq.service.ReceiveService;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

//  给这个XxxServiceImpl取个名称
@Service("receiveService")
public class ReceiveServiceImpl implements ReceiveService {
    //  注入Amqp的模板类,利用这个对象来发送和接收消息
    @Resource
    private AmqpTemplate amqpTemplate;


    /**
     * @QueueBinding注解,要完成队列和交换机的绑定。
     * @Queue()注解,创建一个队列,这里没有指定参数表示创建了一个随机队列
     * @Exchange注解,创建一个交换机,name为交换机的名称,type为交换机的类型
     */
    @RabbitListener(
                bindings = {
                            @QueueBinding(
                                    value = @Queue(),
                                    exchange = @Exchange(name="fanoutExchange",type = "fanout")
                            )
                }
            )
    public void fanoutReceive01(String message){
        System.out.println("消费者   fanoutReceive01   监听器接收的消息 = "+message);
    }

    @RabbitListener(
            bindings = {
                    @QueueBinding(
                            value = @Queue(),
                            exchange = @Exchange(name="fanoutExchange",type = "fanout")
                    )
            }
    )
    public void fanoutReceive02(String message){
        System.out.println("消费者   fanoutReceive02   监听器接收的消息 = "+message);
    }
}

consumer:先运行,效果:

    管理台显示两个随机队列创建成功:

 provider:其它同上上

 provider:SendService.java

package com.bjpowernode.rabbitmq.service;

public interface SendService {
    void sendMessage(String message);
    void sendFanoutMessage(String message);
    void sendTopicMessage(String message);
}

provider:SendServiceImpl.java

package com.bjpowernode.rabbitmq.service.impl;

import com.bjpowernode.rabbitmq.service.SendService;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

//  给这个XxxServiceImpl取个名称
@Service("sendService")
public class SendServiceImpl implements SendService {
    //  注入Amqp的模板类,利用这个对象来发送和接收消息
    @Resource
    private AmqpTemplate amqpTemplate;

    public void sendMessage(String message) {
        /**
         * 发送消息
         * 参数 1 为交换机名
         * 参数 2 为RoutingKey
         * 参数 3 为具体发送的消息数据
         */
        amqpTemplate.convertAndSend("bootDirectExchange","bootDirectRoutingKey",message);
    }

    public void sendFanoutMessage(String message) {
        amqpTemplate.convertAndSend("fanoutExchange","",message);
    }

    public void sendTopicMessage(String message) {
        amqpTemplate.convertAndSend("topicExchange","aa.bb.cc",message);
    }
}

 provider:Application.java

package com.bjpowernode.rabbitmq;


import com.bjpowernode.rabbitmq.service.SendService;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;


@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        ApplicationContext ac=SpringApplication.run(Application.class, args);
        SendService service= (SendService) ac.getBean("sendService");
        //  向服务器(RabbitMq)发送消息
        //service.sendMessage("Boot的测试数据");

        service.sendFanoutMessage("Boot的Fanout测试数据");
//        service.sendTopicMessage("Boot的Topic测数据数据key 为 aa.bb.cc");
    }
}

provider:Application.java

package com.bjpowernode.rabbitmq.config;

import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitMQConfig {
    //  配置一个Direct类型的交换机
    @Bean
    public DirectExchange directExchangeCreate(){
        return new DirectExchange("bootDirectExchange");
    }

    //  配置一个队列
    @Bean
    public Queue directQueueCreate(){
        return new Queue("bootDirectQueue");
    }

    /**
     * 配置交换机与队列的绑定
     * @param bootDirectQueue  需要绑定的队列的对象,参数名必须要与某个@Bean下的方法的方法名完成相同,这样就会自动注入
     * @param directExchangeCreate  需要绑定的交换机的对象,参数名必须要与某个@Bean下的方法的方法名完成相同,这样就会自动注入
     * @return
     */
    @Bean
    public Binding directExchangeAndQueueBinding(Queue directQueueCreate,DirectExchange directExchangeCreate){
        //  完成绑定
        //      参数1:为需要绑定的队列
        //      参数2:为需要绑定的交换机
        //      参数3:routingkey
        return BindingBuilder.bind(directQueueCreate).to(directExchangeCreate).with("bootDirectRoutingKey");
    }

    //  配置一个fanout类型的交换机
    @Bean
    public FanoutExchange fanoutExchangeCreate(){
        return new FanoutExchange("bootFanoutExchange");
    }
}

provider:后运行,效果:

4、想法:topic模式消息发送和接收 

(1)consumer先写,先运行

consumer:其它同上上上

consumer:ReceiveServiceImpl.java

    @RabbitListener(bindings = {@QueueBinding(value=@Queue("topic01"),key = {"aa"},exchange =@Exchange(name = "bootTopicExchange",type = "topic"))})
    public void  topicReceive01(String message){
        System.out.println("topic01消费者 ---aa---"+message );
    }
    @RabbitListener(bindings = {@QueueBinding(value=@Queue("topic02"),key = {"aa.*"},exchange =@Exchange(name = "bootTopicExchange",type = "topic"))})
    public void  topicReceive02(String message){
        System.out.println("topic02消费者 ---aa.*---"+message );
    }
    @RabbitListener(bindings = {@QueueBinding(value=@Queue("topic03"),key = {"aa.#"},exchange =@Exchange(name = "bootTopicExchange",type = "topic"))})
    public void  topicReceive03(String message){
        System.out.println("topic03消费者 ---aa。#---"+message );
    }

consumer:先运行,效果:

(2)provider后写,后运行

provider:其它同上上上

provider:SendService.java 添加接口方法

provider:SendServiceImpl.java添加接口实现方法

public void sendTopicMessage(String message) {
        //    将消息发送到指定的交换机上
        amqpTemplate.convertAndSend("bootTopicExchange","aa.bb.cc",message);
    }

provider:RabbitMQConfig.java

//  配置一个topic类型的交换机
    @Bean
    public TopicExchange topicExchangeCreate(){
        return new TopicExchange("bootTopicExchange");
    }

provider:Application.java

package com.bjpowernode.rabbitmq;


import com.bjpowernode.rabbitmq.service.SendService;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;


@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        ApplicationContext ac=SpringApplication.run(Application.class, args);
        SendService service= (SendService) ac.getBean("sendService");
        //  向服务器(RabbitMq)发送消息
        //service.sendMessage("Boot的测试数据");

        //service.sendFanoutMessage("Boot的Fanout测试数据");
        service.sendTopicMessage("Boot的Topic测数据数据key 为 aa.bb.cc");
    }
}

provider:后运行,效果:能正常通配符完成消息的接收。

5、springboot+RabbitMQ的重要结论:

  1. 在使用springboot+RabbitMQ时,正常运行时会自动确认。但如果抛出异常,那么自动确认就不起作用。
  2. 因此在使用springboot+RabbitMQ时,如果抛出异常,那么就需要手动去确认。

十三、集群配置

1、生活中的问题:集群:一台,单点故障问题

2、解决方案:两种集群方式

(1)普通集群模式

  1. 首先,两台RabbitMq(r1和r2),两个客户端(a,b)。
  2. 其次,r1和r2之间建立一条临时通道。
  3. 接着,a向r1写入消息数据“张三”。
  4. 然后,b从r2获取消息数据“张三”。
  5. 缺点,r1宕机时,b就不能从r2中获取到消息数据了 

(2)高可用:镜像模式

1)思想、结构、流程、原理

  1. 首先,两台RabbitMq(r1和r2),两个客户端(a,b)。
  2. 其次,r1和r2之间是镜像模式。双向。同步消息,此时两边都有,即r1中都r2的消息数据,反之r2也有r1的消息数据。
  3. 接着,a向r1写入消息数据“张三”。
  4. 然后,b从r1或r2获取消息数据“张三”。
  5. 优点,即使r1宕机了,b也能从r2中获取消息数据。

2)配置高可用:镜像模式

  1. 环境搭建
    1. 两台linux(l1和l2)。
    2. 修改host文件:vi /etc/hosts,添加两个域名镜像(l1和l2)。
    3. 关闭防火墙确保2台机器相互ping 同可以执行ping l1和ping l2命令进行测试。
    4. 在2台Linux服务中分别安装RabbitMQ
      1. 依赖包安装:yum install gcc glibc-devel make ncurses-devel openssl-devel xmlto -y

      2. 上传rabbitmq安装包到指定目录

      3. 安装两个Erlang

      4. 配置配置文件中的环境变量

      5. 安装两个rabbitmq

      6. 安装两个rabbitmq的管控台插件

      7. 后台启动两个rabbitmq

        1. 抗:启动问题:chmod 777 /var/lib/rabbitmq/.erlang.cookie

      8. ​​​​​​测试:​访问两个管控台

  2. 配置集群

    1. 两边,添加账号root root。

    2. 两边,给root账号添加角色。

    3. 两边,给root账号授权读写能力。

    4. 两边,root账号登录。

    5. 效果:两个管控台分别显示有两个不同的节点,RabbitMqA和RabbitMqB。

    6. RabbitMqA和RabbitMqB链接配置

      1. 两边,cookie文件必须一样。使用跨机器拷贝命令:[root@A ~]# scp /var/lib/rabbitmq/.erlang.cookie 192.168.222.130:/var/lib/rabbitmq

      2. 两边,关机,再后台重启

      3. 一边中加入另一边的节点配置

      4. 查看集群状态:两边的管控台都显示两个节点(自己的和对方的)

  3. 编写代码:springboot版

    1. 编写springboot版的provider

    2. 修改provider的配置文件aapplication.propertiest,配置rabbitmq集群地址、用户名、密码

    3. 编写springboot版的cosumer

    4. 修改cosumer的配置文件aapplication.propertiest,配置rabbitmq集群地址、用户名、密码

  4. 测试

    1. 运行provider发送消息,正常

    2. 运行consumer接收消息,正常

    3. 坑:此时,如果把b节点关机,conusmer还是不能正常获取消息

  5. 升级配置:高可用配置

    1. Admin配置镜像规则:所有节点的交换机和队列都相互镜像

    2. 此时,如果把b节点关机,conusmer就能正常获取(a)中的消息

  6. 注意:在高可用下,也必须有一个节点正常运行才行

十四、其它具体

1、RabbitMQ特点

        RabbitMQ 是一个由 Erlang 语言开发的 AMQP 的开源实现。ds: AMQP 的开源实现有10几20个都不止。

        AMQP :Advanced Message Queue,高级消息队列协议。它是应用层协议的一个开放标准,为面向消息的中间件设计,基于此协议的客户端与消息中间件可传递消息,并不受产品、开发语言等条件的限制。

        RabbitMQ 最初起源于金融系统,用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。具体特点包括:

   1、可靠性(Reliability)

                RabbitMQ 使用一些机制来保证可靠性,如持久化、传输确认、发布确认。

    2、灵活的路由(Flexible Routing)

                在消息进入队列之前,通过 Exchange 来路由消息的。对于典型的路由功能,RabbitMQ 已经提供了一些内置的 Exchange 来实现。针对更复杂的路由功能,可以将多个 Exchange 绑定在一起,也通过插件机制实现自己的 Exchange 。

   3、 消息集群(Clustering)

                多个 RabbitMQ 服务器可以组成一个集群,形成一个逻辑 Broker 。

   3、 高可用(Highly Available Queues)

                队列可以在集群中的机器上进行镜像,使得在部分节点出问题的情况下队列仍然可用。

    4、多种协议(Multi-protocol)

                RabbitMQ 支持多种消息队列协议,比如 STOMP、MQTT 等等。

    5、多语言客户端(Many Clients)

                RabbitMQ 几乎支持所有常用语言,比如 Java、.NET、Ruby 等等。

   6、 管理界面(Management UI)

          RabbitMQ 提供了一个易用的用户界面,使得用户可以监控和管理消息 Broker 的许多方面。

   7、 跟踪机制(Tracing)

                如果消息异常,RabbitMQ 提供了消息跟踪机制,使用者可以找出发生了什么。

2、什么是路由、路由器?

        ds:路由就是转发的意思。像我们使用的路由器,什么是路由器呀?路由器就是将我们的信号转发到我们的计算机中去,或者反方向将我们的计算机转发到互联网去。这就是所谓的路由器的一个作用,有了路由器就可以将网络信息转发到不同的计算机中去。

3、路由器和交换器

        ds:交换机是什么?也就是类似于我们的路由器,差不多。

4、RabbitMQ与kafuka的区别

        ds:RabbitMQ和kafuka有着天壤之别,kafuka的效率比RabbitMQ要高,但是数据不安全。RabbitMQ的效率虽然没有kafuka效率高,但是数据是安全的。所以,我们在不同的领域中,要使用不同的消息队列。   

5、Erlang与java的区别与联系

        ds:因为Erlang是RabbitMQ的开发语言,所以要安装RabbitMQ,首先得先去安装Erlang语言环境。Erlang语言有点类似于我们的java语言,也需要安装它自己的“jdk”,即像jdk一样去安装Erlang语言环境才可以。

6、.rpm(linux)与.exe(windows)的区别

        ds:在linux中的.rpm可执行文件(可执行安装包文件),相当于windows中的.exe可执行文件。我们在linux中,直接运行.rpm,就可以安装当前的软件了。

7、命名空间是什么?

        ds:命名空间相当于我们windows中的一个文件夹。

8、AMQP与jms的区别与联系  

        我们java中有一个jms的概念(思想),即java消息服务。jms的作用和AMQP是一样的,都是一个协议规范。jms是java自己去定义的,自从java推出了这个jms概念(思想)后,就涌现出很多很多基于jms实现的消息中间件,比如activityMQ。

        jms和AMQP的区别在哪里呢?jms中没有exchange和绑定的概念,只有消息发送和接受这两个概念。反之,在AMQP中增加了exchange和绑定这两个角色。那么在AMQP中:首先,provider要把消息发布到exchange上去。然后,exchnage拿到消息之后,根据消息中的一个信息决定将消息最终把消息发送到哪个队列中去。那么在AMQP中就需要两个东西,一个是绑定,一个是routing-key,这两个东西一旦匹配上之后,就能够去转发。

9、RabbitMQ常用命令

(1)启动和关闭

1、启动RabbitMQ

rabbitmq-server start &

            注意:这里可能会出现错误,错误原因是/var/lib/rabbitmq/.erlang.cookie文件权限不够。

解决方案对这个文件授权:

            chown rabbitmq:rabbitmq /var/lib/rabbitmq/.erlang.cookie

            ds:第一个问题:.erlang.cookie的所属用户和用户组不是rabbitmq,启动不了。

            chmod 400 /var/lib/rabbitmq/.erlang.cookie

            ds:第二个问题,.erlang.cookie没有只读权限,启动不了。

2、停止服务

rabbitmqctl stop

3、查看RabbitMQ服务

           ps -ef|grep rabbitmq

(2)插件管理

1、添加插件

        rabbitmq-plugins enable {插件名}

2、删除插件

        rabbitmq-plugins disable {插件名}

        注意:RabbitMQ启动以后可以使用浏览器进入管控台但是默认情况RabbitMQ不允许直接使用浏览器浏览器进行访问因此必须添加插件

        rabbitmq-plugins enable rabbitmq_management

3、使用浏览器访问管控台http://RabbitMQ服务器IP:15672

http://192.168.71.128:15672  用户名:guest,密码:guest

(3)用户管理

RabbitMQ安装成功后使用默认用户名guest登录

账号:guest

密码:guest

注意:这里guest只允许本机登录访问需要创建用户并授权远程访问命令如下

添加用户:rabbitmqctl add_user {username} {password}

                  rabbitmqctl add_user root root

删除用户:rabbitmqctl delete_user {username}

修改密码:rabbitmqctl change_password {username} {newpassword}

                  rabbitmqctl change_password root 123456

设置用户角色:rabbitmqctl set_user_tags {username} {tag}

                  rabbitmqctl set_user_tags root administrator

tag参数表示用户角色取值为:management monitoring policymaker  administrator

各角色详解:

        management

                用户可以通过AMQP做的任何事外加:

                列出自己可以通过AMQP登入的virtual hosts  

                查看自己的virtual hosts中的queues, exchanges 和 bindings

                查看和关闭自己的channels 和 connections

                查看有关自己的virtual hosts的“全局”的统计信息,包含其他用户在这些virtual hosts中的活动。

        policymaker

                management可以做的任何事外加:

                查看、创建和删除自己的virtual hosts所属的policies和parameters

        monitoring  

                management可以做的任何事外加:

                列出所有virtual hosts,包括他们不能登录的virtual hosts

                查看其他用户的connections和channels

                查看节点级别的数据如clustering和memory使用情况

                查看真正的关于所有virtual hosts的全局的统计信息

        administrator   

                policymaker和monitoring可以做的任何事外加:

                创建和删除virtual hosts

                查看、创建和删除users

                查看创建和删除permissions

                关闭其他用户的connections

(4)权限管理(对队列的操作权限,如访问权限)

1、授权命令:rabbitmqctl set_permissions [-p vhostpath] {user} {conf} {write} {read}

                -p vhostpath :用于指定一个资源的命名空间,例如 –p / 表示根路径命名空间

                user:用于指定要为哪个用户授权填写用户名

                conf:一个正则表达式match哪些配置资源能够被该用户配置。
                write:一个正则表达式match哪些配置资源能够被该用户读。
                read:一个正则表达式match哪些配置资源能够被该用户访问。

     例如:

                rabbitmqctl set_permissions -p / root '.*' '.*' '.*'

                用于设置root用户拥有对所有资源的 读写配置权限

2、查看用户权限 rabbitmqctl  list_permissions [vhostpath]

        例如

                查看根径经下的所有用户权限

                rabbitmqctl  list_permissions

                查看指定命名空间下的所有用户权限

                rabbitmqctl  list_permissions /abc

3、查看指定用户下的权限rabbitmqctl  list_user_permissions {username}

        例如

                查看root用户下的权限

                rabbitmqctl  list_user_permissions root

4、清除用户权限rabbitmqctl  clear_permissions {username}

        例如:

                清除root用户的权限

                rabbitmqctl  clear_permissions root

(5)vhost管理

        vhost是RabbitMQ中的一个命名空间,可以限制消息的存放位置利用这个命名空间可以进行权限的控制有点类似Windows中的文件夹一样,在不同的文件夹中存放不同的文件。

1、添加vhost: rabbitmqctl add vhost {name}

        例如

                rabbitmqctl add vhost bjpowernode

2、删除vhost:rabbitmqctl delete vhost {name}

        例如

                rabbitmqctl delete vhost bjpowernode

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值