RabbitMq入门基础案例

一、消息队列

什么是消息队列呢?

我们之前如果需要进行远程调用,那么一般可以通过发送HTTP请求来完成,而现在,我们可以使用第二种方式,就是消息队列,它能够将发送方发送的信息放入队列中,当新的消息入队时,会通知接收方进行处理,一般消息发送方称为生产者,接收方称为消费者。

BabbitMq,Kafka,RocketMq
  • RabbitMQ - 性能很强,吞吐量很高,支持多种协议,集群化,消息的可靠执行特性等优势,很适合企业的开发。

  • Kafka - 提供了超高的吞吐量,ms级别的延迟,极高的可用性以及可靠性,而且分布式可以任意扩展。

  • RocketMQ - 阿里巴巴推出的消息队列,经历过双十一的考验,单机吞吐量高,消息的高可靠性,扩展性强,支持事务等,但是功能不够完整,语言支持性较差。

RabbitMq工作原理介绍

Broker:接收和分发消息的应用,RabbitMQ Server就是 Message Broker

Virtual host:出于多租户和安全因素设计的,把 AMQP 的基本组件划分到一个虚拟的分组中,类似于网 络中的 namespace 概念。当多个不同的用户使用同一个 RabbitMQ server 提供的服务时,可以划分出多个vhost,每个用户在自己的 vhost 创建 exchange/queue 等

Connection:publisher/consumer 和 broker 之间的 TCP 连接

Channel:如果每一次访问 RabbitMQ 都建立一个 Connection,在消息量大的时候建立 TCP Connection 的开销将是巨大的,效率也较低。Channel 是在 connection 内部建立的逻辑连接,如果应用程序支持多线 程,通常每个thread创建单独的 channel 进行通讯,AMQP method 包含了channel id 帮助客户端和 message broker 识别 channel,所以 channel 之间是完全隔离的。Channel 作为轻量级的 Connection 极大减少了操作系统建立 TCP connection 的开销

Exchange:message 到达 broker 的第一站,根据分发规则,匹配查询表中的 routing key,分发消息到 queue 中去。常用的类型有:direct (point-to-point), topic (publish-subscribe) and fanout (multicast)

Queue:消息最终被送到这里等待 consumer 取走

Binding:exchange 和 queue 之间的虚拟连接,binding 中可以包含 routing key。Binding 信息被保存 到 exchange 中的查询表中,用于 message 的分发依据

二、java实现六种工作模式

1、简单模式

简单的模型:生产者——>消息队列——>消费者

环境依赖:
 <dependency>
     <groupId>com.rabbitmq</groupId>
     <artifactId>amqp-client</artifactId>
     <version>5.14.2</version>
 </dependency>
 ​

创建生产者:
 package org.example.hellodayone;
 ​
 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 producer {
     public static void main(String[] args) throws IOException, TimeoutException {
         //1、创建连接工厂
         ConnectionFactory connectionFactory = new ConnectionFactory();
         //2、设置访问ip
         connectionFactory.setHost("localhost");
         connectionFactory.setPort(5672);
         connectionFactory.setUsername("guest");
         connectionFactory.setPassword("guest");
         connectionFactory.setVirtualHost("/");
         //3、创建connection连接对象
         Connection connection = connectionFactory.newConnection();
         //4、创建通道
         Channel channel = connection.createChannel();
         //5、设置对列属性
         /**
          * 第一个参数:队列名称
          * 第二个参数:队列是否要持久化
          * 第三个参数:是否排他性
          * 第四个参数:是否自动删除
          * 第五个参数:是否要设置些额外参数
          */
         channel.queueDeclare("01-hello",false,false,false,null);
         //6、设置交换机属性
         /**
          * 第一个参数:交换机名称
          * 第二个参数:交换机类型:direct(直连) fanout(扇形) topic(主体) header(头)
          * 第三个参数:是否需要设置额外参数
          */
         //channel.exchangeDeclare("01-hello-exchange","direct",false);
         //7、将队列绑定到交换机
         /**
          * - queue:需要绑定的队列名称。
          * - exchange:需要绑定的交换机名称。
          * - routingKey:不用多说了吧。
          */
         //channel.queueBind("01-hello","01-hello-exchange","01-hello-channel");
         //8、发送消息
         /**
          * 第一个参数:交换机名称
          * 第二个参数:路由key
          * 第三个参数:消息属性
          * 第四个参数:消息内容
          */
         String message = "hello rabbitmq,this is my first";
         channel.basicPublish("","01-hello",null,message.getBytes());
         //9、关闭资源
         channel.close();
         connection.close();
     }
 ​
 }
 ​

其中queueDeclare方法的参数如下:

  • queue:队列的名称(默认创建后routingKey和队列名称一致)

  • durable:是否持久化。

  • exclusive:是否排他,如果一个队列被声明为排他队列,该队列仅对首次声明它的连接可见,并在连接断开时自动删除。排他队列是基于Connection可见,同一个Connection的不同Channel是可以同时访问同一个连接创建的排他队列,并且,如果一个Connection已经声明了一个排他队列,其他的Connection是不允许建立同名的排他队列的,即使该队列是持久化的,一旦Connection关闭或者客户端退出,该排他队列都会自动被删除。

  • autoDelete:是否自动删除。

  • arguments:设置队列的其他一些参数,这里我们暂时不需要什么其他参数。

其中queueBind方法参数如下:

  • queue:需要绑定的队列名称。

  • exchange:需要绑定的交换机名称。

  • routingKey:不用多说了吧。

其中basicPublish方法的参数如下:

  • exchange: 对应的Exchange名称,我们这里就使用第二个直连交换机。

  • routingKey:这里我们填写绑定时指定的routingKey,其实和之前在管理页面操作一样。

  • props:其他的配置。

  • body:消息本体

消费者:
 package org.example.hellodayone;
 ​
 import com.rabbitmq.client.*;
 ​
 import java.io.IOException;
 import java.util.concurrent.TimeoutException;
 ​
 public class Consumer {
     public static void main(String[] args) throws IOException, TimeoutException {
         //1、创建连接工厂
         ConnectionFactory connectionFactory = new ConnectionFactory();
         //2、设置访问ip
         connectionFactory.setHost("localhost");
         connectionFactory.setPort(5672);
         connectionFactory.setUsername("guest");
         connectionFactory.setPassword("guest");
         connectionFactory.setVirtualHost("/");
         //3、创建connection连接对象
         Connection connection = connectionFactory.newConnection();
         //4、创建通道
         Channel channel = connection.createChannel();
         //5、设置对列属性
         /**
          * 第一个参数:队列名称
          * 第二个参数:队列是否要持久化
          * 第三个参数:是否排他性
          * 第四个参数:是否自动删除
          * 第五个参数:是否要设置些额外参数
          */
         channel.queueDeclare("01-hello",false,false,false,null);
         //6、使用channel去rabbitmq中进行消费
         /**
          * 第一个参数:队列名称
          * 第二个参数:是否自动签收
          */
         channel.basicConsume("01-hello", true, new DeliverCallback() {
             /**
              * 当消息从mq中取出来后回调这个函数,消费者消费消息就在handler中进行处理
              * @param s
              * @param delivery
              * @throws IOException
              */
             @Override
             public void handle(String s, Delivery delivery) throws IOException {
                 System.out.println("从生产者获取到的消息是:" + new String(delivery.getBody()));
 ​
                 /**basicAck是确认应答
                  * 第一个参数:当前的消息标签
                  * 第二个参数:是否批量处理队列中所有信息,为false,则只处理当前消息
                  */
                 channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
                 /**basicNack是拒绝应答
                  * 最后一个参数表示是否将当前消息放回队列,如果为false,那么消息就会被丢弃
                  */
                 //channel.basicNack(delivery.getEnvelope().getDeliveryTag(), false, false);
 ​
             }
         }, new CancelCallback() {
             /**
              * 当消息取消会调用这个handler方法
              * @param s
              * @throws IOException
              */
             @Override
             public void handle(String s) throws IOException {
                 System.out.println("消息已经取消");
             }
         });
 ​
     }
 }
 ​

访问的路径:http://127.0.0.1:15672/

2、work工作队列模式

(1)当队列里消息较多时,我们通常会开启多个消费者处理消息;公平分发和轮询分发都是我们经常使用的模式。

(2)轮询分发的主要思想是“按均分配”,不考虑消费者的处理能力,所有消费者均分;这种情况下,处理能力弱的服务器,一直都在处理消息,而处理能力强的服务器,在处理完消息后,处于空闲状态;

(3)公平分发的主要思想是”能者多劳”,按需分配,能力强的干的多。

1、按均分配:

生产者:

 package org.example.work_02;
 ​
 import com.rabbitmq.client.*;
 ​
 import java.io.IOException;
 import java.util.concurrent.TimeoutException;
 ​
 public class Workproducer {
     public static void main(String[] args) throws IOException, TimeoutException {
         //1、创建工厂
         ConnectionFactory connectionFactory = new ConnectionFactory();
         connectionFactory.setHost("localhost");//设置访问ip
         //2、创建连接对象
         Connection connection = connectionFactory.newConnection();
         //3、创建channel连接通道
         Channel channel = connection.createChannel();
         //4、设置队列属性
         channel.queueDeclare("02-work-queue",false,false,false,null);
         /**
          * work队列模式是不涉及交换机的,exchange,所以是由队列直接通过channel通道传递给消费者
          */
         //5、发送消息
         for (int i = 0; i < 20; i++) {
             String message = "hello rabbitmq_" + i;
             channel.basicPublish("","02-work-queue",null,message.getBytes());
         }
 ​
     }
 ​
 }
 ​

消费者:

 package org.example.work_02;
 ​
 import com.rabbitmq.client.*;
 ​
 import java.io.IOException;
 import java.util.concurrent.TimeoutException;
 ​
 public class WorkConsumer01 {
     public static void main(String[] args) throws IOException, TimeoutException {
         //1、创建工厂
         ConnectionFactory connectionFactory = new ConnectionFactory();
         connectionFactory.setHost("localhost");//设置访问ip
         //2、创建连接对象
         Connection connection = connectionFactory.newConnection();
         //3、创建channel连接通道
         Channel channel = connection.createChannel();
         //4、设置队列属性
         channel.queueDeclare("02-work-queue",false,false,false,null);
 ​
         //5、消费者进行消费
         channel.basicConsume("02-work-queue", true, new DeliverCallback() {
             //消费者消费消息会回调这个handler函数
             @Override
             public void handle(String s, Delivery delivery) throws IOException {
                 try {
                     Thread.sleep(1000);
                     System.out.println("消费者1获得的信息为:" + new String(delivery.getBody()));
                     //channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
                 } catch (InterruptedException e) {
                     throw new RuntimeException(e);
                 }
             }
         }, new CancelCallback() {
             //消息取消了会回调这个函数
             @Override
             public void handle(String s) throws IOException {
                 System.out.println("消息已经取消了");
             }
         });
     }
 }
 ​

测试结果:

image-20230920105042686

2、按劳分配:
  • 设置通道channel.basicQos(1); //只限一条消息给同一个消费者,消息处理完后,才发送第二条消息

  • 需要将basicConsume的第二个参数,autoAck改为手动签收,也就是参数设置为false

  • 在DriverCallBack中的handler1方法中添加

    channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);

测试结果:

image-20230920105936439

3、pub/sub发布订阅模式

业务场景:发送消息,或者是发送邮件的时候(群发,保证接受的所有对象都能收倒消息)。

P:生产者,也就是要发送消息的程序,但是不再发送到队列中,而是发给X(交换机)

C:消费者,消息的接收者,会一直等待消息到来

Queue:消息队列,接收消息、缓存消息

Exchange:交换机(X)。一方面,接收生产者发送的消息。另一方面,知道如何处理消息,例如递交给某个特别队列、

递交给所有队列、或是将消息丢弃。到底如何操作,取决于Exchange的类型。Exchange有常见以下3种类型:

Fanout:广播,将消息交给所有绑定到交换机的队列

Direct:定向,把消息交给符合指定routing key 的队列

Topic:通配符,把消息交给符合routing pattern(路由模式) 的队列

Exchange(交换机)只负责转发消息,不具备存储消息的能力,因此如果没有任何队列与 Exchange 绑定,或者没有符合路由规则的队列,那么消息会丢失!

生产者
 ​
 package org.example._03_pubsub;
 ​
 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 PubSubProducer {
     public static void main(String[] args) throws IOException, TimeoutException {
         //1、创建连接工厂
         ConnectionFactory connectionFactory = new ConnectionFactory();
         //2、设置访问ip
         connectionFactory.setHost("localhost");
         connectionFactory.setPort(5672);
         connectionFactory.setUsername("guest");
         connectionFactory.setPassword("guest");
         connectionFactory.setVirtualHost("/");
         //3、创建connection连接对象
         Connection connection = connectionFactory.newConnection();
         //4、创建通道
         Channel channel = connection.createChannel();
         //在发布订阅模式当中,生产者是直接连接的交换机,然后,消费者通过channel与交换机连接,通过匿名队列进行消费
         //6、设置交换机属性
         /**
          * 第一个参数:交换机名称
          * 第二个参数:交换机类型:direct(直连) fanout(扇形) topic(主体) header(头)
          * 第三个参数:是否需要设置额外参数
          */
         channel.exchangeDeclare("03-pubsub-exchange","fanout",false);
 ​
         //8、发送消息
         /**
          * 第一个参数:交换机名称
          * 第二个参数:路由key
          * 第三个参数:消息属性
          * 第四个参数:消息内容
          */
         for (int i = 0; i < 10; i++) {
             String message = "hello rabbitmq,this is publish/subscribe model --" + i;
             channel.basicPublish("03-pubsub-exchange","",null,message.getBytes());//发布订阅模式是直接发送到交换机上的
         }
         //9、关闭资源
         channel.close();
         connection.close();
     }
 ​
 }
 ​
消费者:
 package org.example._03_pubsub;
 ​
 import com.rabbitmq.client.*;
 ​
 import java.io.IOException;
 import java.util.concurrent.TimeoutException;
 ​
 public class PubSubConsumer {
     public static void main(String[] args) throws IOException, TimeoutException {
         //1、创建连接工厂
         ConnectionFactory connectionFactory = new ConnectionFactory();
         //2、设置访问ip
         connectionFactory.setHost("localhost");
         connectionFactory.setPort(5672);
         connectionFactory.setUsername("guest");
         connectionFactory.setPassword("guest");
         connectionFactory.setVirtualHost("/");
         //3、创建connection连接对象
         Connection connection = connectionFactory.newConnection();
         //4、创建通道
         Channel channel = connection.createChannel();
         //5、设置交换机属性
         /**
          * 参数一:交换机命名
          * 参数二:交换机类型
          * 参数三:额外的参数设置
          */
         channel.exchangeDeclare("03-pubsub-exchange","fanout");
 ​
         //6、交换机绑定匿名队列
         /**
          * 参数一:队列名
          * 参数二:交换机名
          * 参数三:routing key
          */
         String queue = channel.queueDeclare().getQueue();
         channel.queueBind(queue,"03-pubsub-exchange","");
         //6、使用channel去rabbitmq中进行消费
         /**
          * 第一个参数:队列名称
          * 第二个参数:是否自动签收
          */
         channel.basicConsume(queue, true, new DeliverCallback() {
             /**
              * 当消息从mq中取出来后回调这个函数,消费者消费消息就在handler中进行处理
              * @param s
              * @param delivery
              * @throws IOException
              */
             @Override
             public void handle(String s, Delivery delivery) throws IOException {
                 System.out.println("消费者1接收到的消息:" + new String(delivery.getBody()));
 ​
                 /**basicAck是确认应答
                  * 第一个参数:当前的消息标签
                  * 第二个参数:是否批量处理队列中所有信息,为false,则只处理当前消息
                  */
                 channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
                 /**basicNack是拒绝应答
                  * 最后一个参数表示是否将当前消息放回队列,如果为false,那么消息就会被丢弃
                  */
                 //channel.basicNack(delivery.getEnvelope().getDeliveryTag(), false, false);
 ​
             }
         }, new CancelCallback() {
             /**
              * 当消息取消会调用这个handler方法
              * @param s
              * @throws IOException
              */
             @Override
             public void handle(String s) throws IOException {
                 System.out.println("消息已经取消");
             }
         });
 ​
     }
 }
 ​

4、routing路由模式

队列与交换机的绑定,不能是任意绑定了,而是要指定一个 RoutingKey(路由key)

消息的发送方在向 Exchange 发送消息时,也必须指定消息的 RoutingKey

Exchange 不再把消息交给每一个绑定的队列,而是根据消息的 Routing Key 进行判断,只有队列的 Routingkey 与消息的 Routing key 完全一致,才会接收到消息

生产者:
 //6、设置交换机属性
         /**
          * 第一个参数:交换机名称
          * 第二个参数:交换机类型:direct(直连) fanout(扇形) topic(主体) header(头)
          * 第三个参数:是否需要设置额外参数
          */
         //在routing模式下,这里的交换机类型是direct
         channel.exchangeDeclare("04-routing-exchange","direct",false);
 ​
         //8、发送消息
         /**
          * 第一个参数:交换机名称
          * 第二个参数:路由key
          * 第三个参数:消息属性
          * 第四个参数:消息内容
          */
        //这里设置routing key 为info
         String message = "hello rabbitmq,this is routing model" ;
         String routingKey = "info";
         channel.basicPublish("04-routing-exchange",routingKey,null,message.getBytes());//发布订阅模式是直接发送到交换机上的
 ​

消费者:
 //5、设置交换机属性
         /**
          * 参数一:交换机命名
          * 参数二:交换机类型
          * 参数三:额外的参数设置
          */
         channel.exchangeDeclare("04-routing-exchange","direct");
 ​
         //6、交换机绑定匿名队列
         /**
          * 参数一:队列名
          * 参数二:交换机名
          * 参数三:routing key
          */
         //这里设置了一个绑定队列,但是分别有三个不同的bind虚拟连接 error、info、warning 连接绑定队列
         String queue = channel.queueDeclare().getQueue();
         channel.queueBind(queue,"04-routing-exchange","error");
         channel.queueBind(queue,"04-routing-exchange","info");
         channel.queueBind(queue,"04-routing-exchange","warning");

5、topic模式

Topic 类型与 Direct 相比,都是可以根据 RoutingKey 把消息路由到不同的队列。只不过 Topic 类型

Exchange 可以让队列在绑定 Routing key 的时候使用通配符

Routingkey 一般都是有一个或多个单词组成,多个单词之间以”.”分割,例如: item.insert

通配符规则:# 匹配一个或多个词,* 匹配不多不少恰好1个词,例如:item.# 能够匹配 item.insert.abc 或者 item.insert,item.* 只能匹配 item.insert

图解:

红色 Queue:绑定的是 usa.# ,因此凡是以 usa. 开头的 routing key 都会被匹配到

黄色 Queue:绑定的是 #.news ,因此凡是以 .news 结尾的 routing key 都会被匹配

生产者:
 //6、设置交换机属性
         /**
          * 第一个参数:交换机名称
          * 第二个参数:交换机类型:direct(直连) fanout(扇形) topic(主题) header(头)
          * 第三个参数:是否需要设置额外参数
          */
         channel.exchangeDeclare("05-topic-exchange","topic");
 ​
         //8、发送消息
         /**
          * 第一个参数:交换机名称
          * 第二个参数:路由key
          * 第三个参数:消息属性
          * 第四个参数:消息内容
          */
        //这里设置routing key 为info
         String message = "hello rabbitmq,this is topic model" ;
         channel.basicPublish("05-topic-exchange","user.delete",null,message.getBytes());//发布订阅模式是直接发送到交换机上的
 ​
消费者:
 //5、设置交换机属性
         /**
          * 参数一:交换机命名
          * 参数二:交换机类型
          * 参数三:额外的参数设置
          */
         channel.exchangeDeclare("05-topic-exchange","topic");
 ​
         //6、交换机绑定匿名队列
         /**
          * 参数一:队列名
          * 参数二:交换机名
          * 参数三:routing key
          */
         //设置routing key 为employee
         String queue = channel.queueDeclare().getQueue();
         channel.queueBind(queue,"05-topic-exchange","employee.*");

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值