当前位置: 首页 > news >正文

【RabbitMQ】常用消息模型详解

文章目录

  • AMQP协议的回顾
  • RabbitMQ支持的消息模型
  • 第一种模型(直连)
    • 开发生产者
    • 开发消费者
    • 生产者、消费者开发优化
    • API参数细节
  • 第二种模型(work quene)
    • 开发生产者
    • 开发消费者
    • 消息自动确认机制
  • 第三种模型(fanout)
    • 开发生产者
    • 开发消费者
  • 第四种模型(Routing)
    • 开发生产者
    • 开发消费者
  • 第五种模型(Topic)
    • 开发生产者
    • 开发消费者

AMQP协议的回顾

在这里插入图片描述
在RabbitMQ中有生产者、消费者的概念,生产者先与我们的RabbitMQServer建立连接,建立完连接之后,它会把消息通过连接中通道的形式去传递我们的消息。每一个生产者会对应一个专门的虚拟主机。

在我们做项目的时候,RabbitMQ希望我们每一个项目具有单独的虚拟主机,这样我们多个应用在操作同一个RabbitMQServer的时候互不影响,所以这里的虚拟机有点像关系型数据库中的库概念。

我们在访问虚拟主机的时候是需要权限的,如果需要访问到某一个具体的虚拟主机,我们需要将虚拟主机与用户进行绑定。

比如RabbitMQ默认为我们提供的guest账户,他是可以访问所有的虚拟主机的,具有至高无上的权限。在我们实际的生产环境中我们一般是一个项目访问一个虚拟主机,或者说是一个业务访问一个虚拟主机,在访问的时候我们一般为一个虚拟主机绑定特定的用户。

当我们的生产者通过通道将消息放入到虚拟机之中,因为RabbitMQ存在许多的消息模型,所以这里不一定会把消息放入到交换机之中。也就是说当生产者将消息传递给交换机或者队列之后,他的任务就告一段落了。

这个时候我们的生产者和消费者是完全解耦的,我们不需要关心生产者到底有没有运行,我只关心消费者监听的队列里面有没有对应的消息即可。

消费者在消费消息的时候也需要去连接到我们RabbitMQServer以及虚拟主机,我们才能消费到对应主机中的消息队列里面的数据。

RabbitMQ支持的消息模型

在这里插入图片描述
在这里插入图片描述

最新的版本有第七种消息模型:消息确认模型

第一种模型(直连)

在这里插入图片描述

在上图的模型中,有以下概念:

  • P:生产者,也就是要发送消息的程序
  • C:消费者:消息的接受者,会一直等待消息到来。
  • queue:消息队列,图中红色部分。类似一个邮箱,可以缓存消息;生产者向其中投递消息,消费者从其中取出消息。

首先我们先创建一个新用户/ems,然后将一个虚拟主机与其绑定,然后给他添加超级用户权限:
在这里插入图片描述

注意:
用户名必须以/开头

开发生产者

public class Provider {//生产消息@Testpublic void testSendMessage() throws IOException, TimeoutException {//创建连接mq的连接工厂对象ConnectionFactory connectionFactory = new ConnectionFactory();//设置连接rabbitmqserver主机connectionFactory.setHost("10.15.0.9");//设置端口号connectionFactory.setPort(5672);//设置连接那个虚拟主机connectionFactory.setVirtualHost("/ems");//设置访问虚拟主机的用户名和密码connectionFactory.setUsername("ems");connectionFactory.setPassword("123");//获取连接对象Connection connection = connectionFactory.newConnection();//获取连接中通道Channel channel = connection.createChannel();//通道绑定对应消息队列//参数1:  队列名称 如果队列不存在自动创建//参数2:  用来定义队列特性是否要持久化 true 持久化队列   false 不持久化//参数3:  exclusive 是否独占队列  true 独占队列   false  不独占//参数4:  autoDelete: 是否在消费完成后自动删除队列  true 自动删除  false 不自动删除//参数5:  额外附加参数channel.queueDeclare("hello",true,false,false,null);//发布消息//参数1: 交换机名称 参数2:队列名称  参数3:传递消息额外设置  参数4:消息的具体内容channel.basicPublish("","hello", null,"hello rabbitmq".getBytes());channel.close();connection.close();}
}

开发消费者

public class Customer {public static void main(String[] args) throws IOException, TimeoutException {//创建连接工厂ConnectionFactory connectionFactory = new ConnectionFactory();connectionFactory.setHost("10.15.0.9");connectionFactory.setPort(5672);connectionFactory.setVirtualHost("/ems");connectionFactory.setUsername("ems");connectionFactory.setPassword("123");//创建连接对象Connection connection = connectionFactory.newConnection();*///创建通道Channel channel = connection.createChannel();//通道绑定对象channel.queueDeclare("hello",true,false,false,null);//消费消息//参数1: 消费那个队列的消息 队列名称//参数2: 开始消息的自动确认机制//参数3: 消费时的回调接口channel.basicConsume("hello",true,new DefaultConsumer(channel){@Override //最后一个参数: 消息队列中取出的消息public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {System.out.println("===================================="+new String(body));}});}}

注意:
在使用Junit测试的时候,他是不支持多线程模型的。如果我们使用@Test去运行的话,他没法让我们的消费者去监听(运行完之后直接就杀死了该进程,不会处于监听状态),所以这里我们要换成一个main函数。
生产者则不需要注意这一点,因为它生产完消息就完事了

在这里插入图片描述
我们发现生产者生产完消息之后,会关闭通道和链接,而在消费这里我们并没有这么做。这是因为可能会导致我们的回调函数还没来得及执行,我们的通道就已经关闭。

该模型的特点:
点对点的简单消费模型。
适用于登录、注册场景

生产者、消费者开发优化

我们发现我们在开发生产者、消费者的时候前面的连接部分代码重复冗余,所以我们可以使用一个工具类对其进行封装:

public class RabbitMQUtils {private static ConnectionFactory connectionFactory;private static Properties properties;static{//重量级资源  类加载执行之执行一次connectionFactory = new ConnectionFactory();connectionFactory.setHost("10.15.0.5");connectionFactory.setPort(5672);connectionFactory.setVirtualHost("/");connectionFactory.setUsername("guest");connectionFactory.setPassword("guest");}//定义提供连接对象的方法public static Connection getConnection() {try {return connectionFactory.newConnection();} catch (Exception e) {e.printStackTrace();}return null;}//关闭通道和关闭连接工具方法public static void closeConnectionAndChanel(Channel channel, Connection conn) {try {if(channel!=null) channel.close();if(conn!=null)   conn.close();} catch (Exception e) {e.printStackTrace();}}}

我们这里使用静态代码块是因为connectionFactory是重量级资源,所以我们决定只在类加载执行时执行一次。

我们这里稍微复习一下java的静态代码块,我们会发现在一些项目源码中经常会见到他。
静态代码块语法格式:

static{}

静态代码块的特点:随着类的加载而执行,而且只执行一次
执行优先级高于非静态的初始化块,它会在类初始化的时候执行一次,执行完成便销毁,它仅能初始化类变量,即static修饰的数据成员。
那么正好我们再来提一下非静态代码块:
非静态代码块语法格式:

{}

执行的时候如果有静态初始化块,先执行静态初始化块再执行非静态初始化块,在每个对象生成时都会被执行一次,它可以初始化类的实例变量。非静态初始化块会在构造函数执行时,在构造函数主体代码执行之前被运行。
执行顺序:
静态代码块----->非静态代码块-------->构造函数

API参数细节

生产者和消费者均有一个方法queueDeclare,就是声明操作的队列:

channel.queueDeclare("hello",true,false,false,null);
  • 参数1: 队列名称
    • 如果队列不存在自动创建
  • 参数2: 用来定义队列特性是否要持久化
    • true 持久化队列
    • false 不持久化
    • 注意这里说的是队列的持久化
    • 也就是如果开启持久化的话,我们即使重启rabbitmq服务该队列也会存在,因为其内部会把队列从内存写到硬盘中去。当重启完成之后,其又会重新将硬盘中的队列读到内存中去
  • 参数3: exclusive 是否独占队列
    • true 独占队列
    • 也就是说队列只能被当前通道所绑定
    • false 不独占
  • 参数4: autoDelete: 是否在消费完成后自动删除队列
    • true 自动删除
    • false 不自动删除
    • 这里的自动删除队列是指消费者不再监听占用队列,队列才会消失
  • 参数5: 额外附加参数

注意:直连模型下,消费者和生产者的queueDeclare中的参数要保持一致,这样才能保证操作的是同一个队列

生产者:

channel.basicPublish("","hello", MessageProperties.PERSISTENT_TEXT_PLAIN,"hello rabbitmq".getBytes());
  • 参数1: 交换机名称 (我们这里没有使用交换机所以没有指定)
  • 参数2:队列名称
  • 参数3:传递消息额外设置
    • MessageProperties.PERSISTENT_TEXT_PLAIN
    • 我们可以通过此参数设置消息在队列中的持久化
  • 参数4:消息的具体内容
    • 这里是以字节的方式进行传输

消费者:

channel.basicConsume("hello",true,new DefaultConsumer(channel){@Override //最后一个参数: 消息队列中取出的消息public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {System.out.println("======"+new String(body));}
});
  • 参数1: 消费哪个队列的消息 队列名称
  • 参数2: 开始消息的自动确认机制
  • 参数3: 消费时的回调接口
    • 这里我们可以传入一个consumer对象,而这个consumer是一个接口,它有一个实现类DefaultConsumer

第二种模型(work quene)

Work queues,也被称为(Task queues),任务模型。当消息处理比较耗时的时候,可能生产消息的速度会远远大于消息的消费速度。长此以往,消息就会堆积越来越多,无法及时处理。此时就可以使用work 模型:让多个消费者绑定到一个队列,共同消费队列中的消息队列中的消息一旦消费,就会消失,因此任务是不会被重复执行的。

在这里插入图片描述

角色:

  • P:生产者:任务的发布者
  • C1:消费者-1,领取任务并且完成任务,假设完成速度较慢
  • C2:消费者-2:领取任务并完成任务,假设完成速度快

开发生产者

public class Provider {public static void main(String[] args) throws IOException {//获取连接对象Connection connection = RabbitMQUtils.getConnection();//获取通道对象Channel channel = connection.createChannel();//通过通道声明队列channel.queueDeclare("work", true, false, false, null);for (int i = 1; i <=20; i++) {//生产消息channel.basicPublish("", "work", null, (i + "hello work quene").getBytes());}//关闭资源RabbitMQUtils.closeConnectionAndChanel(channel, connection);}
}

我们这里使用了前面提到的连接工具类

我们运行我们的代码:
在这里插入图片描述
这里的Unacked代表未被确认的消息

开发消费者

如果我们对两个消费者不做任何处理:

消费者-1

public class Customer1 {public static void main(String[] args) throws IOException {//获取连接Connection connection = RabbitMQUtils.getConnection();final Channel channel = connection.createChannel();channel.queueDeclare("work",true,false,false,null);//参数1:队列名称  参数2:消息自动确认 true  消费者自动向rabbitmq确认消息消费  false 不会自动确认消息channel.basicConsume("work",false,new DefaultConsumer(channel){@Overridepublic void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {                System.out.println("消费者-1: "+new String(body));                }});}
}

消费者-2

public class Customer2 {public static void main(String[] args) throws IOException {//获取连接Connection connection = RabbitMQUtils.getConnection();final Channel channel = connection.createChannel();channel.queueDeclare("work",true,false,false,null);//参数1:队列名称  参数2:消息自动确认 true  消费者自动向rabbitmq确认消息消费  false 不会自动确认消息channel.basicConsume("work",false,new DefaultConsumer(channel){@Overridepublic void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {                System.out.println("消费者-2: "+new String(body));                }});}
}

在这种不做任何处理的情况下,消费者1、消费者2消费的消息都是一致的:
在这里插入图片描述
在这里插入图片描述

总结:默认情况下,RabbitMQ将按顺序将每个消息发送给下一个使用者。平均而言,每个消费者都会收到相同数量的消息。这种分发消息的方式称为循环。(也就是说平均分配)

而这样的话我们不难想到一个问题:加入我们的消费者1处理的比较慢,消费者2处理的比较快。这就导致消费者1的消息会在队列中造成滞留,消费者2可能已经处理完闲着了。这样的情况下平均分配显然也会影响效率,并且导致消息再队列中的积累。

我们可以模拟一下这个情况,我们在消费者1中添加一个线程睡眠:

在这里插入图片描述

这个时候我们运行发现,在消费者2将自己的消息打印完之后,消费者1的消息只打印了一条:
在这里插入图片描述

在这里插入图片描述

那么能不能用第二种模型实现一种能者多劳的模式呢?

消息自动确认机制

Doing a task can take a few seconds. You may wonder what happens if one of the consumers starts a long task and dies with it only partly done. With our current code, once RabbitMQ delivers a message to the consumer it immediately marks it for deletion. In this case, if you kill a worker we will lose the message it was just processing. We’ll also lose all the messages that were dispatched to this particular worker but were not yet handled.

But we don’t want to lose any tasks. If a worker dies, we’d like the task to be delivered to another worker.
完成一项任务可能需要几秒钟。你可能想知道,如果其中一个消费者开始了一项长任务,但只完成了一部分任务就去世了,会发生什么。使用我们当前的代码,一旦RabbitMQ将消息传递给消费者,它会立即将其标记为删除。在这种情况下,如果你杀死一个消费者,我们将丢失它正在处理的消息。我们还将丢失发送给该特定工作人员但尚未处理的所有消息。
但我们不想失去任何任务。如果一名消费者死亡,我们希望将任务交付给另一名消费者。

自动确认是指,当消息一旦被Consumer接收到,则自动确认收到,并将相应 message 从 RabbitMQ 的消息缓存中移除。但是在实际业务处理中,很可能消息接收到,业务处理出现异常,那么该消息就会丢失。如果设置了手动确认方式,则需要在业务处理成功后,调用channel.basicAck(),手动签收,如果出现异常,则调用channel.basicNack()方法,让其自动重新发送消息。

在这里插入图片描述

我们使用能者多劳的模式需要进行两步额外的操作:

  • 设置通道一次只能消费一个消息

  • 关闭消息的自动确认,开启手动确认消息

Customer1:

public class Customer1 {public static void main(String[] args) throws IOException {//获取连接Connection connection = RabbitMQUtils.getConnection();final Channel channel = connection.createChannel();channel.basicQos(1);//每一次只能消费一个消息channel.queueDeclare("work",true,false,false,null);//参数1:队列名称  参数2:消息自动确认 true  消费者自动向rabbitmq确认消息消费  false 不会自动确认消息channel.basicConsume("work",false,new DefaultConsumer(channel){@Overridepublic void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {try{Thread.sleep(2000);}catch (Exception e){e.printStackTrace();}System.out.println("消费者-1: "+new String(body));// 参数1:确认队列中那个具体消息 参数2:是否开启多个消息同时确实channel.basicAck(envelope.getDeliveryTag(),false);}});}
}

Customer2:

public class Customer2 {public static void main(String[] args) throws IOException {//获取连接Connection connection = RabbitMQUtils.getConnection();final Channel channel = connection.createChannel();channel.basicQos(1);channel.queueDeclare("work",true,false,false,null);channel.basicConsume("work",false,new DefaultConsumer(channel){@Overridepublic void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {System.out.println("消费者-2: "+new String(body));//手动确认  参数1:手动确认消息标识  参数2:false 每次确认一个channel.basicAck(envelope.getDeliveryTag(), false);}});    }
}

我们对上面两段代码的一些参数或方法做出解释:

basicQos()

void basicQos(int prefetchSize, int prefetchCount, boolean global) throws IOException;

参数:

  • prefetchSize:消息的大小

  • prefetchCount:会告诉RabbitMQ不要同时给一个消费者推送多于N个消息,即一旦有N个消息还没有ack,则该consumer将block掉,直到有消息ack

  • global:是否将上面设置应用于channel,简单点说,就是上面限制是channel级别的还是consumer级别

channel.basicAck()

void basicAck(long deliveryTag, boolean multiple) throws IOException;

参数:

  • deliveryTag:该消息的index

  • multiple:是否批量处理.

    • true:将一次性ack所有小于deliveryTag的消息

envelope.getDeliveryTag()

在这里插入图片描述
这个方法是表示消息的唯一标识ID,返回的是一个正整数,是rabbitmq来自增设置的

第三种模型(fanout)

fanout 扇出 也称为广播

在这里插入图片描述

在广播模式下,消息发送流程是这样的:

  • 可以有多个消费者
  • 每个消费者有自己的queue(队列)
    • 我们这里创建的队列是临时的,用完之后就会删除
  • 每个队列都要绑定到Exchange(交换机)
  • 生产者发送的消息,只能发送到交换机,交换机来决定要发给哪个队列,生产者无法决定。
  • 交换机把消息发送给绑定过的所有队列
  • 队列的消费者都能拿到消息。实现一条消息被多个消费者消费

使用场景:
比如我们的商品购物车,在我们结算的时候,我们可能会跟多个系统进行交互,订单系统、库存系统等等。这个时候我们的购物车信息会被多条队列给消费。

在fanout模式下,路由的相关配置没有意义,相关参数可以空着

开发生产者

public class Provider {public static void main(String[] args) throws IOException {//获取连接对象Connection connection = RabbitMQUtils.getConnection();Channel channel = connection.createChannel();//将通道声明指定交换机   //参数1: 交换机名称    参数2: 交换机类型  fanout 广播类型channel.exchangeDeclare("logs","fanout");//发送消息channel.basicPublish("logs","",null,"fanout type message".getBytes());//释放资源RabbitMQUtils.closeConnectionAndChanel(channel,connection);}
}

channel.exchangeDeclare("logs","fanout");:

  • 参数1: 交换机名称
  • 参数2: 交换机类型
    • fanout是广播类型

开发消费者

public class Customer1 {public static void main(String[] args) throws IOException {//获取连接对象Connection connection = RabbitMQUtils.getConnection();Channel channel = connection.createChannel();//通道绑定交换机channel.exchangeDeclare("logs","fanout");//临时队列String queueName = channel.queueDeclare().getQueue();//绑定交换机和队列channel.queueBind(queueName,"logs","");//消费消息channel.basicConsume(queueName,true,new DefaultConsumer(channel){@Overridepublic void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {System.out.println("消费者1: "+new String(body));}});}
}

其他几个消费者同理

我们创建三个消费者,把他们开启之后,再开启我们的生产者,测试结果如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

第四种模型(Routing)

在这里插入图片描述
第五种模型其实是第四种模型的一个分支,如果我们叫第四种模型为路由的话,那么我们可以说第五种模型是动态路由。第四种模型我们也可以叫做direct模型(直连)

在Fanout模式中,一条消息,会被所有订阅的队列都消费。但是,在某些场景下,我们希望不同的消息被不同的队列消费。这时就要用direct类型的Exchange。

在Routing模型下:

  • 队列与交换机的绑定,不能是任意绑定了,而是要指定一个RoutingKey(路由key)
  • 消息的发送方在 向 Exchange发送消息时,也必须指定消息的 RoutingKey
  • Exchange不再把消息交给每一个绑定的队列,而是根据消息的Routing Key进行判断,只有队列的Routingkey与消息的 Routing key完全一致,才会接收到消息

流程:

在这里插入图片描述

图解:

  • P:生产者,向Exchange发送消息,发送消息时,会指定一个routing key。
  • X:Exchange(交换机),接收生产者的消息,然后把消息递交给 与routing key完全匹配的队列
  • C1:消费者,其所在队列指定了需要routing key 为 error 的消息
  • C2:消费者,其所在队列指定了需要routing key 为 info、error、warning 的消息

开发生产者

public class Provider {public static void main(String[] args) throws IOException {//获取连接对象Connection connection = RabbitMQUtils.getConnection();//获取连接通道对象Channel channel = connection.createChannel();String exchangeName = "logs_direct";//通过通道声明交换机  参数1:交换机名称  参数2:direct  路由模式channel.exchangeDeclare(exchangeName,"direct");//发送消息String routingkey = "error";channel.basicPublish(exchangeName,routingkey,null,("指定的route key"+key+"的消息").getBytes());//关闭资源RabbitMQUtils.closeConnectionAndChanel(channel,connection);}
}

这个时候我们就已经可以看到我们的交换机了:
在这里插入图片描述

开发消费者

我们这里开发两个消费者:

  • 消费者1拿到routekey为error的消息
  • 消费者2拿到routekey为info、error、warning的消息
public class Customer1 {public static void main(String[] args) throws IOException {Connection connection = RabbitMQUtils.getConnection();Channel channel = connection.createChannel();String exchangeName = "logs_direct";//通道声明交换机以及交换的类型channel.exchangeDeclare(exchangeName,"direct");//创建一个临时队列String queue = channel.queueDeclare().getQueue();//基于route key绑定队列和交换机channel.queueBind(queue,exchangeName,"error");//获取消费的消息channel.basicConsume(queue,true,new DefaultConsumer(channel){@Overridepublic void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {System.out.println("消费者1: "+ new String(body));}});}
}
public class Customer2 {public static void main(String[] args) throws IOException {Connection connection = RabbitMQUtils.getConnection();Channel channel = connection.createChannel();String exchangeName = "logs_direct";//声明交换机 以及交换机类型 directchannel.exchangeDeclare(exchangeName,"direct");//创建一个临时队列String queue = channel.queueDeclare().getQueue();//临时队列和交换机绑定channel.queueBind(queue,exchangeName,"info");channel.queueBind(queue,exchangeName,"error");channel.queueBind(queue,exchangeName,"warning");//消费消息channel.basicConsume(queue,true,new DefaultConsumer(channel){@Overridepublic void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {System.out.println("消费者2: "+new String(body));}});}
}

我们测试一下:

测试生产者发送Route key为error的消息时

在这里插入图片描述

在这里插入图片描述

测试生产者发送Route key为info的消息时

在这里插入图片描述

在这里插入图片描述


第五种模型(Topic)

Topic类型的ExchangeDirect相比,都是可以根据RoutingKey把消息路由到不同的队列。只不过Topic类型Exchange可以让队列在绑定Routing key 的时候使用通配符!这种模型Routingkey 一般都是由一个或多个单词组成,多个单词之间以”.”分割,例如: item.insert

在这里插入图片描述

  • *(star) :匹配不多不少恰好1个词
  • #(hash): 匹配0个或多个词

例如:

audit.#    匹配audit.irs.corporate或者 audit.irs 等
audit.*   只能匹配audit.irs

开发生产者

public class Provider {public static void main(String[] args) throws IOException {//获取连接对象Connection connection = RabbitMQUtils.getConnection();Channel channel = connection.createChannel();//声明交换机以及交换机类型 topicchannel.exchangeDeclare("topics","topic");//发布消息String routekey = "user";channel.basicPublish("topics",routekey,null,("这里是topic动态路由模型,routekey: ["+routekey+"]").getBytes());//关闭资源RabbitMQUtils.closeConnectionAndChanel(channel,connection);}
}

开发消费者

我们还是开发两个消费者:

消费者1Routing Key中使用*通配符方式

public class Customer1 {public static void main(String[] args) throws IOException {//获取连接Connection connection = RabbitMQUtils.getConnection();Channel channel = connection.createChannel();//声明交换机以及交换机类型channel.exchangeDeclare("topics","topic");//创建一个临时队列String queue = channel.queueDeclare().getQueue();//绑定队列和交换机  动态统配符形式route keychannel.queueBind(queue,"topics","user.*");//消费消息channel.basicConsume(queue,true,new DefaultConsumer(channel){@Overridepublic void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {System.out.println("消费者1: "+ new String(body));}});}
}

消费者2中Routing Key中使用#通配符方式

public class Customer2 {public static void main(String[] args) throws IOException {//获取连接Connection connection = RabbitMQUtils.getConnection();Channel channel = connection.createChannel();//声明交换机以及交换机类型channel.exchangeDeclare("topics","topic");//创建一个临时队列String queue = channel.queueDeclare().getQueue();//绑定队列和交换机  动态统配符形式route keychannel.queueBind(queue,"topics","user.#");//消费消息channel.basicConsume(queue,true,new DefaultConsumer(channel){@Overridepublic void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {System.out.println("消费者2: "+ new String(body));}});}
}

这个时候我们测试的结果就是:

  • 消费者2可以拿到消息
  • 消费者1拿不到消息

相关文章:

【RabbitMQ】常用消息模型详解

文章目录 AMQP协议的回顾RabbitMQ支持的消息模型第一种模型(直连)开发生产者开发消费者生产者、消费者开发优化API参数细节 第二种模型(work quene)开发生产者开发消费者消息自动确认机制 第三种模型(fanout)开发生产者开发消费者 第四种模型(Routing)开发生产者开发消费者 第五…...

图像拼接后丢失数据,转tiff报错rasterfile failed: an unknown

图像拼接后丢失数据 不仅是数据丢失了&#xff0c;还有个未知原因报错 部分数据存在值不存在的情况 原因 处理遥感数据很容易&#xff0c;磁盘爆满了 解决方案 清理一些无用数据&#xff0c;准备买个2T的外接硬盘用着了。 然后重新做处理...

Nginx之日志模块解读

目录 基本介绍 配置指令 access_log&#xff08;访问日志&#xff09; error_log&#xff08; 错误日志&#xff09; 基本介绍 Nginx日志主要分为两种&#xff1a;access_log(访问日志)和error_log(错误日志)。Nginx日志主要记录以下信息&#xff1a; 记录Nginx服务启动…...

latex方程组编写,一种可以保证方程编号自适应的方法

问题描述&#xff1a; 在利用latex编写方程组时&#xff0c;可以有很多种方法&#xff0c;但不总是编辑好的公式能够显示出编号&#xff0c;故提出一种有效的方程组编写方法 方法&#xff1a; \begin{equation}X_{ t1}\left \{ \begin{matrix}\frac{x_{i}}{a} \quad\quad 0&l…...

深度学习基础 2D卷积(1)

什么是2D卷积 2D参数量怎么计算 以pytorch为例子&#xff0c;2D卷积在设置的时候具有以下参数&#xff0c;具有输入通道的多少&#xff08;这个决定了卷积核的通道数量&#xff09;&#xff0c;滤波器数量&#xff0c;这个是有多少个滤波器&#xff0c;越多提取的特征就越有用…...

OpenCV DNN C++ 使用 YOLO 模型推理

OpenCV DNN C 使用 YOLO 模型推理 引言 YOLO&#xff08;You Only Look Once&#xff09;是一种流行的目标检测算法&#xff0c;因其速度快和准确度高而被广泛应用。OpenCV 的 DNN&#xff08;Deep Neural Networks&#xff09;模块为我们提供了一个简单易用的 API&#xff0…...

第八章 Linux文件系统权限

目录 8.1 文件的一般权限 1.修改文件或目录的权限---chmod命令 2.对于文件和目录&#xff0c;r&#xff0c;w&#xff0c;x有不同的作用&#xff1a; 3.修改文件或目录的所属主和组---chown,chgrp 8.2 文件和目录的特殊权限 三种通过字符描述文件权限 8.3 ACL 权限 1.A…...

XXL-JOB源码梳理——一文理清XXL-JOB实现方案

分布式定时任务调度系统 流程分析 一个分布式定时任务&#xff0c;需要具备有以下几点功能&#xff1a; 核心功能&#xff1a;定时调度、任务管理、可观测日志高可用&#xff1a;集群、分片、失败处理高性能&#xff1a;分布式锁扩展功能&#xff1a;可视化运维、多语言、任…...

java做个qq机器人

前置的条件 机器人是基于mirai框架实现的。根据官方的文档&#xff0c;建议使用openjdk11。 我这里使用的编辑工具是idea2023 在idea中新建一个maven项目&#xff0c;虽然可以使用gradle进行构建&#xff0c;不过我这里由于网络问题没有跑通。 pom.xml <dependency>&l…...

前端 | AjaxAxios模块

文章目录 1. Ajax1.1 Ajax介绍1.2 Ajax作用1.3 同步异步1.4 原生Ajax 2. Axios2.1 Axios下载2.2 Axios基本使用2.3 Axios方法 1. Ajax 1.1 Ajax介绍 Ajax: 全称&#xff08;Asynchronous JavaScript And XML&#xff09;&#xff0c;异步的JavaScript和XML。 1.2 Ajax作用 …...

高效的ProtoBuf

一、背景 Google ProtoBuf介绍 这篇文章我们讲了怎么使用ProtoBuf进行序列化&#xff0c;但ProtoBuf怎么做到最高效的&#xff0c;它的数据又是如何压缩的&#xff0c;下面先看一个例子&#xff0c;然后再讲ProtoBuf压缩机制。 二、案例 网上有各种序列化方式性能对比&#…...

删除SQL记录

删除记录的方式汇总&#xff1a; 根据条件删除&#xff1a;DELETE FROM tb_name [WHERE options] [ [ ORDER BY fields ] LIMIT n ] 全部删除&#xff08;表清空&#xff0c;包含自增计数器重置&#xff09;&#xff1a;TRUNCATE tb_namedelete和truncate的区别&#xff1a; d…...

数据结构--》探索数据结构中的字符串结构与算法

本文将带你深入了解串的基本概念、表示方法以及串操作的常见算法。通过深入理解串的相关概念和操作&#xff0c;我们将能够更好地应用它们来解决算法问题。 无论你是初学者还是进阶者&#xff0c;本文将为你提供简单易懂、实用可行的知识点&#xff0c;帮助你更好地掌握串在数据…...

云安全之等级保护详解

等级保护概念 网络安全等级保护&#xff0c;是对信息系统分等级实行安全保护&#xff0c;对信息系统中使用的安全产品实行按等级管理&#xff0c;对信息系统中发生的信息安全事件分等级进行响应、处置。 网络安全等级保护的核心内容是&#xff1a;国家制定统一的政策、标准&a…...

VUE状态持久化,储存动态路由

1. vuex persistPlugin.js 文件 const routerKey "ROUTER_KEY";export default (store) > {// 刷新页面时&#xff0c;存储改变的数据window.addEventListener("beforeunload", () > {localStorage.setItem(routerKey, JSON.stringify(store.stat…...

微信小程序代驾系统源码(含未编译前端,二开无忧) v2.5

简介&#xff1a; 如今有越来越多的人在网上做代驾&#xff0c;打造一个代驾平台&#xff0c;既可以让司机增加一笔额外的收入&#xff0c;也解决了车主酒后不能开发的问题&#xff0c;代驾系统基于微信小程序开发的代驾系统支持一键下单叫代驾&#xff0c;支持代驾人员保证金…...

1797_GNU pdf阅读器evince

全部学习汇总&#xff1a; GreyZhang/g_GNU: After some years I found that I do need some free air, so dive into GNU again! (github.com) 近段时间经历了很多事情&#xff0c;终于想找一点技术上的自由气氛。或许&#xff0c;没有什么比GNU的一些软件探索更适合填充这样的…...

网络-跨域解决

文章目录 前言一、跨域是什么&#xff1f;二、跨域的解决1.JSONP2.前端代理dev环境3.后端设置请求头CORS4.运维nginx代理 总结 前言 本文主要介绍跨域问题介绍并提供了四种解决办法。 一、跨域是什么&#xff1f; 准确的来说是浏览器存在跨域问题&#xff0c;浏览器为了安全考…...

git提交代码的流程

1.拉取代码 当你进入了一家公司就需要拉去公司的代码进行开发,此时你的项目小组长会给你个地址拉代码, git clone 公司项目的地址 此时如果不使用了这个方式拉去代码,拉去的是master分支上的代码,但是很多数的情况下&#xff0c;公司的项目可能会在其它的分支上,因此到公…...

【SpringBoot】配置文件详解

配置文件详解 一. 配置文件作用二. 配置文件的格式1. properties 配置文件说明①. properties 基本语法②. 读取配置⽂件③. properties 缺点 2. yml 配置⽂件说明①. yml 基本语法②. yml 使用进阶 3. properties VS yml 三. 设置不同环境的配置⽂件 一. 配置文件作用 整个项…...

一文讲懂-五险一金

假设在“北京”&#xff1a;这里的数值并不代表任何真实的城市或地区&#xff0c;只是为了说明计算方法。 工资: 月工资为 6000 元。养老保险: 单位比例: 20% 个人比例: 8%医疗保险: 单位比例: 10% 个人比例: 2%失业保险: 单位比例: 2% 个人比例: 0.5%工伤保险: 单位比例: 0.5…...

判断三条边是否构成三角形(Python实现)

组成三角形的三条边a,b,c需满足条件: ab>c ac>b bc>a 已知&#xff1a;三角形任意三条边的长度之和大于第三条边。 解题&#xff1a;定义3个变量a、b、c&#xff0c;让用户输入任意三个数字赋值给三个变量。判断三个变量中是否任意两个之和大于第三个数值。 判断条件之…...

The directory ‘*‘ or its parent directory is not owned by the current user

python安装编译时出现如下错误 The directory /home/admin/.cache/pip/http or its parent directory is not owned by the current user and the cache has been disabled. Please check the permissions and owner of that directory. If executing pip with sudo, you may …...

leetcode做题笔记162. 寻找峰值

峰值元素是指其值严格大于左右相邻值的元素。 给你一个整数数组 nums&#xff0c;找到峰值元素并返回其索引。数组可能包含多个峰值&#xff0c;在这种情况下&#xff0c;返回 任何一个峰值 所在位置即可。 你可以假设 nums[-1] nums[n] -∞ 。 你必须实现时间复杂度为 O(…...

nginx负载转发源请求http/https:X-Forwarded-Proto及nginx中的转发报头

今天在排查服务器的问题时最后定位到服务器因为经过了运维这一层的处理&#xff0c;转发过来的请求不管用户请求的是https还是http&#xff0c;我们的proxy服务器收到的都是80端口上的http。于是联系相关部门了解有没有现成的可用的这样一个字段来获得这个值。公司用的也是标准…...

Docker compose插件安装

添加docker源 # Add Dockers official GPG key: sudo apt-get update sudo apt-get install ca-certificates curl gnupg sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/do…...

【数据结构与算法】树、二叉树的概念及结构(详解)

前言: &#x1f4a5;&#x1f388;个人主页:​​​​​​Dream_Chaser&#xff5e; &#x1f388;&#x1f4a5; ✨✨专栏:http://t.csdn.cn/oXkBa ⛳⛳本篇内容:c语言数据结构--树以及二叉树的概念与结构 目录 一.树概念及结构 1.树的概念 1.1树与非树 树的特点&#xff1…...

函数指针数组指针(指向函数指针数组的指针)

一、什么是函数指针数组指针&#xff1f; 本质是指针&#xff0c;指向函数指针数组&#xff0c;存放函数指针数组的地址。 代码如下&#xff1a; pfArr是函数指针数组 p是函数指针数组指针 int main() {int(*pfArr[])(int, int) { Add,Sub };//函数指针数组int(*(*p)[])(int, …...

经典算法-----汉诺塔问题

前言 今天我们学习一个老经典的问题-----汉诺塔问题&#xff0c;可能在学习编程之前我们就听说过这个问题&#xff0c;那这里我们如何去通过编程的方式去解决这么一个问题呢&#xff1f;下面接着看。 汉诺塔问题 问题描述 这里是引用汉诺塔问题源自印度一个古老的传说&#x…...

博客之站项目测试报告

项目背景项目功能测试计划Bug总结升级自动化测试正常登录流程 项目背景 1&#xff1a;博客之站系统是采用前后端分离的方式来实现&#xff1b;使用MySQL、Redis数据库储存相关数据&#xff1b;同时部署到云服务器上。 2&#xff1a;包含注册页、登录页、博客列表页、个人列表页…...

眉山做网站的公司/无锡seo网站管理

写一个方法void triangle(int a,int b,int c)&#xff0c;判断三个参数是否能构成一个三角形。如果不能则抛出异常IllegalArgumentException&#xff0c;显示异常信息&#xff1a;a,b,c “不能构成三角形”&#xff1b;如果可以构成则显示三角形三个边长。在主方法中得到命令行…...

淘宝关闭网站建设类目/做企业网站建设公司哪家好

转眼进入8月&#xff0c;室外气温一度攀升&#xff0c;跟人做防晒一般&#xff0c;汽车也要做好养护。比如汽车长期在外不能暴晒&#xff0c;如何做防晒&#xff0c;车胎保养&#xff0c;夏季频繁降雨如何做好养护等&#xff0c;这些都是困扰车主的保养难题。那么到底夏季汽车保…...

南阳网站建设培训班/怎么引流推广自己的产品

首先默认情况下&#xff0c;innerHTML会被认为是不安全的&#xff0c;因此屏蔽掉了样式。 不生效场景 我们通过innerHTML引入一段html格式的文本&#xff0c;文本中有style样式&#xff0c;目标是希望字体是红色的&#xff0c;而实际效果却仍然是默认的黑色。 TS文件 impor…...

合肥网站建设服务平台/seo的作用有哪些

方案背景 在 5G 万物互联时代&#xff0c;将物理世界的数据进行数字化采集、传输和分析&#xff0c;最终通过丰富的物联网应用实现智慧化&#xff0c;是未来发展的大势所趋 。随着物联网在各行各业快速和深入的发展应用&#xff0c;各种终端设备联网的需求强烈。在物联网快速发…...

云主机放多个网站/站内推广方式

1&#xff1a;echo $JAVA_HOME 使用$JAVA_HOME的话能定位JDK的安装路径的前提是配置了环境变量$JAVA_HOME&#xff0c;否则如下所示&#xff0c;根本定位不到JDK的安装路径2&#xff1a;which java 首先要申明一下which java是定位不到安装路径的。which java定位到的是java程序…...

比较冷门的视频网站做搬运/seo工具优化软件

1.CSS3动画 CSS3动画简介 通过改变元素的属性值来实现动画效果的。 animation实现动画主要由两部分组成&#xff1a;通过类似flash动画的关键帧来生明一个动画&#xff1b;在animation属性中调用关键帧声明的动画实现一个更为复杂的动画效果 animation属性的浏览器兼容性 CSS3…...