【微服务】RabbitMQ与SpringAMQP消息队列
一、初识MQ
1. 同步通讯
同步通讯就好比双方打电话,可以实时响应,但只能一对一,只能同时和一个人聊天。
异步通讯就好比两个人发信息,你发信息给对方,对方不一定给你回复,但是可以一对多,可以同时和多个人聊天。
之前学习的Feign远程调用就属于同步方式,虽然调用可以实时得到结果,但存在下面的问题:
同步通讯
优点:
- 时效性强
缺点:
- 耦合度高:增加业务需要改动代码
- 性能和吞吐量低:后面的服务处理需要等待前面的服务结束才能执行,花费时间多
- 级联失败:前一个服务挂了,后面的服务无法正常运行
- 有额外的资源消耗
2. 异步通讯
异步调用常见实现就是事件驱动模式,它能够很好的解决同步通讯的弊端。
以购买商品为例,用户支付成功后需要调用订单服务、仓储服务更改订单状态及物流信息。
- 支付服务是一个事件发布者(publisher),支付成功后发布一个成功的事件(event), 事件携带订单id。
- 订单服务、仓储服务是事件订阅者(consumer),订阅支付成功的事件,监听到事件后各自完成自身的使命。
- 为了降低事件订阅者和事件发布者的耦合度,它们之间有个中介 Broker 。发布者发布事件到Broker,不关心谁来订阅事件。订阅者从Broker订阅事件,不关心谁发来的消息。
异步通讯
优点:
- 耦合度低:
- 吞吐量提升: 调用间没有阻塞,不会造成无效的资源占用
- 故障隔离
- 流量削峰: 不管发布了多少事件,都由Broker 接收,订阅者按照自己速度正常执行,可以把 Broker 看作一个缓冲池。
缺点
- 依赖于Broker 的可靠性、安全性、吞吐能力
- 架构复杂了,业务没有明显的流程线,不好追踪管理
知道了同步通讯和异步通讯的优缺点,该如何选择呢?
如果业务对于并发性要求不高,但对于时效性要求高,比如要查询某个信息,而且立马需要使用查询的信息。推荐使用同步通讯。
异步通讯适合高并发,性能要求高的场景使用。
3. MQ 常见技术
MQ (MessageQueue),中文是消息队列,字面来看就是存放消息的队列。也就是事件驱动架构中的Broker。
国内用的比较多的就是RabbitMQ、RocketMQ、Kafka。因为kafka 吞吐量高,可靠性低,所以适用于数据量大,但对数据安全不高的场景使用,如日志。而RabbitMQ和RocketMQ稳定性较强,吞吐量也不差,更适合对稳定性要求高的场景。对于中小型企业,更强调稳定性,可以使用RabbitMQ;如果是大厂,需要做更深入的定制,可以使用RocketMQ。
追求可用性:Kafka、 RocketMQ 、RabbitMQ
追求可靠性:RabbitMQ、RocketMQ
追求吞吐能力:RocketMQ、Kafka
追求消息低延迟:RabbitMQ、Kafka
二、RabbitMQ快速入门
RabbitMQ是基于Erlang语言开发的开源消息通信中间件,官网地址:https://www.rabbitmq.com/
1. 安装RabbitMQ
选择在服务器中使用Docker容器化来安装
- 下载镜像
在控制台输入如下指令拉取镜像:
docker pull rabbitmq:3-management
- 安装MQ
执行下面的命令来创建并运行rabbitmq容器:
docker run \-e RABBITMQ_DEFAULT_USER=zhangsan \-e RABBITMQ_DEFAULT_PASS=123456 \--name mq \--hostname mq1 \-p 15672:15672 \-p 5672:5672 \-d \rabbitmq:3-management
上述命令解析:
- -e 表示设置环境变量; --name 容器名称; --hostname 主机名,集群用到;
- RABBITMQ_DEFAULT_USER: MQ管理界面登录账号
- RABBITMQ_DEFAULT_PASS: MQ管理界面登录密码
- 15672 :ui管理平台访问端口
- 5672 :内部消息通信端口
- -d : 后台运行
访问控制台
在浏览器中输入http://192.168.30.130:15672 (服务器ip+端口),访问并登录后可以看到如下界面。
2. MQ整体架构
消息发送者发送消息到交换机,交换机把信息路由到队列中,队列会把信息暂存起来,消息消费者会去消息队列中获取数据。
3. 消息队列模型
常用消息队列模型有5种。基本消息队列(BasicQueue)、工作消息队列(WorkQuez)是基于队列来发送消息的。另外三种又根据交换机类型不同分为三种发布订阅,广播(Fanout Exchange)、路由(Direct Exchange)、主题(Topic Exchange)。
3.1 基本消息队列模型
官方的HelloWorld是基于最基础的消息队列模型来实现的,只包括三个角色:
- publisher: 消息发布者,将消息发送到队列queue。
- queue: 消息队列,负责接受并缓存消息。
- consumer: 订阅队列,处理队列中的消息。
基本消息队列的消息发送流程
1. 建立connection
2. 创建channel
3. 利用channel声明队列
4. 利用channel向队列发送消息
public class PublisherTest {@Testpublic void testSendMessage() throws IOException, TimeoutException {// 1.建立连接ConnectionFactory factory = new ConnectionFactory();// 1.1.设置连接参数,分别是:主机名、端口号(mq内部消息通信端口)、vhost、用户名、密码factory.setHost("192.168.30.130");factory.setPort(5672);factory.setVirtualHost("/");factory.setUsername("zhangsan");factory.setPassword("123456");// 1.2.建立连接Connection connection = factory.newConnection();// 2.创建通道ChannelChannel channel = connection.createChannel();// 3.创建队列String queueName = "simple.queue";channel.queueDeclare(queueName, false, false, false, null);// 4.发送消息String message = "hello, rabbitmq!";channel.basicPublish("", queueName, null, message.getBytes());System.out.println("发送消息成功:【" + message + "】");// 5.关闭通道和连接channel.close();connection.close();}
}
基本消息队列的消息接收流程
1. 建立connection
2. 创建channel
3. 利用channel声明队列
4. 定义consumer的消费行为handleDelivery()
5. 利用channel将消费者与队列绑定
public class ConsumerTest {public static void main(String[] args) throws IOException, TimeoutException {// 1.建立连接ConnectionFactory factory = new ConnectionFactory();// 1.1.设置连接参数,分别是:主机名、端口号(mq内部消息通信端口)、vhost、用户名、密码factory.setHost("192.168.30.130");factory.setPort(5672);factory.setVirtualHost("/");factory.setUsername("zhangsan");factory.setPassword("123456");// 1.2.建立连接Connection connection = factory.newConnection();// 2.创建通道ChannelChannel channel = connection.createChannel();// 3.创建队列String queueName = "simple.queue";channel.queueDeclare(queueName, false, false, false, null);// 4.订阅消息channel.basicConsume(queueName, true, new DefaultConsumer(channel){@Overridepublic void handleDelivery(String consumerTag, Envelope envelope,AMQP.BasicProperties properties, byte[] body) throws IOException {// 5.处理消息String message = new String(body);System.out.println("接收到消息:【" + message + "】");}});System.out.println("等待接收消息。。。。");}
}
为什么在消息发布中创建队列后,消息接受者也要创建队列呢?
这是因为消息发送者和消息接收者两个的执行顺序是不确定的,有可能前者比后者快,也有可能前者比后者慢。这样是为了保险起见。同个队列,只会创建一次,不必担心创建多次队列。
三、SpringAMQP快速入门
1. 概述
官方定义的基本消息队列模型实现还是比较复杂的,因此我们会利用SpringAMQP来实现。想要了解SpringAMQP,就必须先了解什么是AMPQP。
AMPQP 全称为 Advanced Message Queuing Protocol,是用于在应用程序之间传递业务消息的开放标准。该协议与语言和平台无关,更符合微服务中独立性的要求。也就是说AMQP是一种协议,适用于各种开发语言。
Spring AMQP是基于AMQP协议定义的一套API规范,提供了模板来发送和接收消息。包含两部分,其中spring-amgp是基础抽象,spring-rabbit是底层的默认实现。
Spring AMQP官网地址:https://spring.io/projects/spring-amqp
2. BasicQueue 基本消息队列
流程如下
1. 在父工程中引入spring-amqp的依赖。
2. 在publisher服务中利用RabbitTemplate发送消息到simple.queue这个队列。
3. 在consumer服务中编写消费逻辑,绑定simple.queue这个队列。
步骤1:因为publisher和consumer服务都需要amgp依赖,这里把依赖放到父工程mg-demo中。
<!--AMQP依赖,包含RabbitMQ-->
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
步骤2:在publisher中编写测试方法,向simple.queue发送消息。
- 在publisher服务中编写application.yml,添加mq连接信息
spring:rabbitmq:host: 192.168.30.130 # 主机名port: 5672 # 端口(mq内部消息通信端口)virtual-host: / # 虚拟主机username: zhangsan # 用户名password: 123456 # 密码
- 在publisher服务中新建一个测试类,编写测试方法
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringAMQPTest {@Autowiredprivate RabbitTemplate rabbitTemplate;@Testpublic void testSimpleQueue() {String queueName = "simple.queue";String message = "hello,Spring AMPQ";rabbitTemplate.convertAndSend(queueName, message);}
}
运行该测试方法,到RabbitMQ管理页面,找到Queue(队列),进入队列找到 Get messages查看发送的信息。
发送信息小结
- 引入amgp的starter依赖。因为消息发送者和接受者都要该以来,故放到了父工程依赖上
- yml 文件上配置RabbitMQ地址
- 利用RabbitTemplate的convertAndSend方法
步骤3:在consumer中编写消费逻辑,监听simple.queue。
- 在consumer服务中编写application.yml,添加mq连接信息
spring:rabbitmq:host: 192.168.30.130 # 主机名port: 5672 # 端口(MQ内部通信端口)virtual-host: / # 虚拟主机username: zhangsan # 用户名password: 123456 # 密码
- 在consumer服务中新建一个类,编写消费逻辑:
当simple queue队列有消息时,就会来到此类中,接受发送到的消息。一旦消息被接受到了,就会从队列中删除,且不能恢复。
@Component
public class SpringRabbitListener {@RabbitListener(queues = "simple.queue") // 该注解实时监听消息,queues 指定队列名称public void listenSimpleQueueMessage(String msg) {System.out.println("Spring 消费者接受到的消息:" + "【" + msg + "】");}
}
启动该模块启动类。在控制台看到了发送的消息。在MQ管理页面的队列中,消息在队列中删除了。
3. WorkQuez 工作消息队列
如果只有一个消费者,恰好这个消费者处理速度比较慢,每秒钟处理10个消息。队列中的消息来的比较多,每秒发送20个消息,那么多余的十个消息就没有处理,就会滞留在队列中,而队列时在内存中的,总有一个时刻队列会爆满,后面的消息就不能存到队列中,就会出问题。Work queue,工作队列,可以提高消息处理速度,避免队列消息堆积。一条消息只能被其中一个消费者获取。
案例:在publisher服务中定义测试方法,每秒产生50条消息,发送到s imple.queue在consumer服务中定义两个消息监听者,都监听simple.queue队列消费者1每秒处理50条消息,消费者2每秒处理10条消息。
步骤1:生产者循环发送消息到simple.queue队列
- 在publisher服务中添加一个测试方法,循环发送50条消息到simple.queue队列
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringAMQPTest {@Autowiredprivate RabbitTemplate rabbitTemplate;@Testpublic void testWorkQueue() throws InterruptedException {String queueName = "simple.queue";String message = "hello,message_";for (int i = 0; i < 50; i++) {rabbitTemplate.convertAndSend(queueName, message + i);}// 避免发送太快Thread.sleep(20);}}
步骤2:编写两个消费者,都监听simple.queue
@Component
public class SpringRabbitListener {@RabbitListener(queues = "simple.queue")public void listenSimpleQueueMessage(String msg) throws InterruptedException {System.out.println("Spring 消费者1接受到的消息:" + "【" + msg + "】" + LocalTime.now());Thread.sleep(25);}@RabbitListener(queues = "simple.queue")public void listenSimpleQueueMessage2(String msg) throws InterruptedException {System.err.println("Spring 消费者2接受到的消息:" + "【" + msg + "】" + LocalTime.now());Thread.sleep(100);}
}
步骤3:修改消费者的application.yml文件,设置preFetch这个值,可以控制预取消息的上限
spring:rabbitmq:host: 192.168.30.130 # 主机名port: 5672 # 端口virtual-host: / # 虚拟主机username: zhangsan # 用户名password: 123456 # 密码listener:simple:prefetch: 1 # 每次只能获取一条消息,处理完成才能获取下一条消息
先启动消息消费者,再启动消息发布者。可以发现能力越大,处理越多。
4. FanoutExchange 发布订阅
发布订阅模式与之前案例的区别就是允许将同一消息发送给多个消费者。实现方式是加入了exchange(交换机)。
常见exchange发布订阅类型包括:Fanout ( 广播) ; Direct (路由) ; Topic (话题)
注意:
exchange负责消息路由,而不是存储,路由失败则消息丢失。
Fanout Exchange 会将接收到的消息广播到每一个跟其绑定的queue。
实现思路:
- 在consumer服务中,利用代码声明队列、交换机,并将两者绑定
- 在consumer服务中,编写两个消费者方法,分别监听fanout.queue1和fanout.queue2
- 在publisher中编写测试方法,向itcast.fanout发送消息
步骤1:在consumer服务声明Exchange、Queue、Binding
SpringAMQP提供了声明交换机、队列、绑定关系的API
在消息接受者中创建一个配置类
@Configuration
public class FanoutConfig {// 声明交换机@Beanpublic FanoutExchange fanoutExchange() {return new FanoutExchange("zhangsan.fanout");}// 声明队列1@Beanpublic Queue fanoutQueue1() {return new Queue("fanout.queue1");}// 绑定队列1到交换机@Beanpublic Binding fanoutBinding1(Queue fanoutQueue1, FanoutExchange fanoutExchange) {return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);}// 声明队列@Beanpublic Queue fanoutQueue2() {return new Queue("fanout.queue2");}// 绑定队列2到交换机@Beanpublic Binding fanoutBinding2(Queue fanoutQueue2, FanoutExchange fanoutExchange) {return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange);}}
启动Comsumer模块,在RabbitMQ管理页面可以在 Exchanges找到bindings,发现已经绑定了我们定义的队列。
步骤2:在consumer服务声明两个消费者
在consumer服务的SpringRabbitListener类中,添加两个方法,分别监听fanout.queue1和fanout.queue2。
@Component
public class SpringRabbitListener {@RabbitListener(queues = "fanout.queue1")public void listenFanoutQueue1(String msg) throws InterruptedException {System.err.println("消费者接受到fanout.queue1的消息:" + "【" + msg + "】");Thread.sleep(100);}@RabbitListener(queues = "fanout.queue2")public void listenFanoutQueue2(String msg) throws InterruptedException {System.err.println("消费者接受到fanout.queue2的消息:" + "【" + msg + "】");Thread.sleep(100);}
}
步骤3:在publisher服务的SpringAmqpTest类中添加测试方法。
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringAMQPTest {@Autowiredprivate RabbitTemplate rabbitTemplate;@Testpublic void testSendFanoutExchange() {// 交换机名String exchangeName = "zhangsan.fanout";// 消息String message = "hello,everyone";// 发送消息rabbitTemplate.convertAndSend(exchangeName, "", message);}
}
先启动消息接受者,再启动消息发布者,控制台结果如下。
5. DirectExchange 发布订阅
Direct Exchange 将接收到的消息根据规则路由到指定的Queue,因此称为路由模式(routes)。
- 每一个Queue都与Exchange设置一个BindingKey
- 发布者发送消息时,指定消息的RoutingKey
- Exchange将消息路由到BindingKey与消息RoutingKey一致的队列
案例:
步骤1:在consumer服务声明Exchange、Queue
- 在consumer服务中,编写两个消费者方法,分别监听direct.queue1和direct.queue2,
- 并利用@RabbitListener声明Exchange、Queue、RoutingKey
@Component
public class SpringRabbitListener {@RabbitListener(bindings = @QueueBinding(value = @Queue(name = "direct.queue1"),exchange = @Exchange(name = "zhangsan.direct", type = ExchangeTypes.DIRECT),key = {"red", "blue"}))public void listenDirectQueue1(String msg) {System.err.println("消费者接受到 direct.queue1的消息:" + "【" + msg + "】");}@RabbitListener(bindings = @QueueBinding(value = @Queue(name = "direct.queue2"),exchange = @Exchange(name = "zhangsan.direct", type = ExchangeTypes.DIRECT),key = {"red", "yellow"}))public void listenDirectQueue2(String msg) {System.err.println("消费者接受到 direct.queue2的消息:" + "【" + msg + "】");}}
步骤2:在publisher服务发送消息到DirectExchange
在publisher服务的SpringAmqpTest类中添加测试方法
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringAMQPTest {@Autowiredprivate RabbitTemplate rabbitTemplate;@Testpublic void testSendDirectExchange() {// 交换机名String exchangeName = "zhangsan.direct";// 消息String message = "hello,red";// 发送消息, 当路由规则是 red,两个队列都能收到消息,路由规则是blue,队列1收到消息,路由规则是yellow,队列2收到消息rabbitTemplate.convertAndSend(exchangeName, "red", message);}
}
路由规则是red时
路由规则时yellow时
6. TopicExchange 发布订阅
TopicExchange与DirectExchange类似,区别在于TopicExchange 的 routingKey必须是多个单词的列表,并且以 . 分割。 DirectExchange的 routingKey是一个单词的列表。
Queue与Exchange指定BindingKey时可以使用通配符:
#:代指0个或多个单词
*:代指一个单词
如下图,如果只关心china的所有信息,bindingKey可以设置成china.#;只关心所有国家的天气 ,bindingKey可以设置成 #.weather, 是不是有一种分组的感觉呢?
案例:利用SpringAMQP演示TopicExchange的使用
实现思路如下:
- 1. 利用@RabbitListener声明Exchange、Queue、RoutingKey
- 2. 在consumer服务中,编写两个消费者方法,分别监听topic.queue1和topic.queue2
- 3. 在publisher中编写测试方法,向itcast. topic发送消息
步骤1: 在consumer服务声明Exchange、Queue
在consumer服务中,编写两个消费者方法,分别监听topic.queue1和topic.queue2,并利用@RabbitListener声明Exchange、Queue、RoutingKey。
@Component
public class SpringRabbitListener {@RabbitListener(bindings = @QueueBinding(value = @Queue(name = "topic.queue1"),exchange = @Exchange(name = "zhangsan.topic", type = ExchangeTypes.TOPIC),key = "china.#"))public void listenTopicQueue1(String msg) {System.out.println("消费者接受到 topic.queue1的消息:" + "【" + msg + "】");}@RabbitListener(bindings = @QueueBinding(value = @Queue(name = "topic.queue2"),exchange = @Exchange(name = "zhangsan.topic", type = ExchangeTypes.TOPIC),key = "#.news"))public void listenTopicQueue2(String msg) {System.out.println("消费者接受到 topic.queue2的消息:" + "【" + msg + "】");}}
步骤2:在publisher服务发送消息到TopicExchange
在publisher服务的SpringAmqpTest类中添加测试方法:
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringAMQPTest {@Autowiredprivate RabbitTemplate rabbitTemplate;@Testpublic void testSendTopicExchange() {// 交换机名String exchangeName = "zhangsan.topic";// 消息String message = "今天我学微服务了";// 发送消息rabbitTemplate.convertAndSend(exchangeName, "china.news", message);}
}
7. 扩展(消息转换器)
7.1 引言
在SpringAMQP的发送方法中,接收消息的类型是Object,也就是说我们可以发送任意对象类型的消息,SpringAMQP会帮我们序列化为字节后发送。
下面做个小测试:我们发送一个对象,到MQ管理页面查看是否能发送成功
步骤1:在consumer中利用@Bean声明一个队列:
@Configuration
public class FanoutConfig {// 声明一个队列@Beanpublic Queue objectQueue() {return new Queue("object.queue");}}
步骤2:在publisher中发送消息以测试:
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringAMQPTest {@Autowiredprivate RabbitTemplate rabbitTemplate;@Testpublic void testSendObject() {// 准备消息Map<String,Object> msg = new HashMap<>();msg.put("name", "Jack");msg.put("age", 21);// 发送消息rabbitTemplate.convertAndSend("object.queue", msg);}
}
步骤3:先启动消息接受者,再启动消息发送者,到MQ管理页面,找到Queue队列,查看消息Get Message, 发现我们发送的消息已经序列化了。但这种序列号并不太友好,简单的数据序列化了这么多数据。第一个会影响传输效率;第二个会占用额外的存储空间。
7.2 消息转换器
Spring对消息对象的处理是由org.springframework.amqp.support.converter.MessageConverter来处理的。而默认实现是SimpleMessageConverter,基于JDK的ObjectOutputStream完成序列化。如果要修改只需要定义一个MessageConverter 类型的Bean即可。推荐用JSON方式序列化,步骤如下:
消息发送
步骤1:我们在publisher服务引入依赖
<dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId>
</dependency>
步骤2: publisher服务声明MessageConverter, Bean方法一般都是写在配置类中,这里我写在publisher的启动类中,因为启动类也是个配置类。
@Beanpublic MessageConverter messageConverter() {return new Jackson2JsonMessageConverter();}
重新启动测试方法,到MQ管理页面,发现我们发送的消息不是序列化的形式了,而是json的形式。
消息接收
步骤1:在consumer服务引入Jackson依赖
<dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId>
</dependency>
步骤2:在consumer服务定义MessageConverter , Bean方法一般都是写在配置类中,这里我写在publisher的启动类中,因为启动类也是个配置类。
@Beanpublic MessageConverter jsonMessageConverter(){return new Jackson2JsonMessageConverter();}
步骤3:定义一个消费者,监听object.queue队列并消费消息
@Component
public class SpringRabbitListener {@RabbitListener(queues = "object.queue")public void listenObjectQueue(Map<String, Object> msg) {System.out.println("收到消息:【" + msg + "】"); }
}
步骤4:重启consumer
8. 小结
8.1 声明绑定的2种方式
方式1:基于配置类方式声明队列、交换机、绑定队列和交换机
@Configuration
public class FanoutConfig {// 声明队列1@Beanpublic Queue fanoutQueue1() {return new Queue("fanout.queue1");}// 声明交换机@Beanpublic FanoutExchange fanoutExchange() {return new FanoutExchange("zhangsan.fanout");}// 绑定队列1到交换机@Beanpublic Binding fanoutBinding1(Queue fanoutQueue1, FanoutExchange fanoutExchange) {return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);}
}
方式2:基于@RabbitListener注解来声明队列、交换机和绑定队列和交换机的方式
- Queue:用于声明队列,可以用工厂类QueueBuilder构建
- Exchange:用于声明交换机,可以用工厂类ExchangeBuilder构建
- Binding:用于声明队列和交换机的绑定关系,可以用工厂类BindingBuilder构建
@Component
public class SpringRabbitListener {@RabbitListener(bindings = @QueueBinding(value = @Queue(name = "topic.queue1"),exchange = @Exchange(name = "zhangsan.topic", type = ExchangeTypes.TOPIC),key = "china.#"))public void listenTopicQueue1(String msg) {System.out.println("消费者接受到 topic.queue1的消息:" + "【" + msg + "】");}
}
8.2 五种消息队列的区别
基本消息队列:一个队列绑定一个消费者,消息一旦被使用,立刻删除,不能回溯。
工作队列:一个队列绑定多个消费者,一条消息只被一个消费者使用。
广播发布订阅(FanoutExchange):发布订阅模式与上面的2种队列区别在于,允许将同一消息发送给多个消费者。不能指定某个队列接收消息。
路由发布订阅(DirectExchange):路由发布订阅与广播发布订阅的区别在于,路由发布订阅可以根据 routingKey 匹配对应的队列,把消息发送给指定的队列。
话题发布订阅(TopicExchange):话题发布订阅与路由发布订阅的区别在于,话题发布订阅可以支持模糊匹配,*表示一个,#表示0个或多个。只有符合 routingKey 的队列才能接受到消息。类似于发布朋友圈时哪些分组好友可见一样的效果。
相关文章:
【微服务】RabbitMQ与SpringAMQP消息队列
一、初识MQ 1. 同步通讯 同步通讯就好比双方打电话,可以实时响应,但只能一对一,只能同时和一个人聊天。 异步通讯就好比两个人发信息,你发信息给对方,对方不一定给你回复,但是可以一对多,可…...
C++设计模式-中介者模式
动机(Motivation) 多个对象相互关联的情况,对象之间常常会维持一种复杂的引用关系,如果遇到一些需求的更改,这种直接的引用关系将面临不断的变化。在这种情况下,可以使用一种”中介对象“来管理对象间的关联关系,避免…...
js实现分页效果
分页总结: 先写好html格式和css样式 再写js时先将他们都获取过来,设置一个k为页数的下标,num为每页的数据数量,pages为页数,并且获取json数据,再将data获取到全局 写for循环,设置一个变量为i…...
【WRF-Urban】多层建筑能源参数化模型概述:原理
【WRF-Urban】多层建筑能源参数化模型概述:原理 1 概述1.1 原理1.2 使用步骤 2参考 多层建筑能源参数化(Multi-layer Building Energy Parameterization, BEP)模型是一种用于模拟城市环境中多层建筑群的能量交换和微气候影响的参数化模型。该…...
ShuffleNet V2:高效卷积神经网络架构设计的实用指南
摘要 https://arxiv.org/pdf/1807.11164 当前,神经网络架构设计大多以计算复杂度的间接指标,即浮点运算数(FLOPs)为指导。然而,直接指标(例如速度)还取决于其他因素,如内存访问成本…...
1123--collection接口,list接口,set接口
目录 一 java 1. 集合 2. 集合框架图--remember 3. collection接口 3.1 collection接口的常用方法 3.1.1 add() 3.1.2 remove()-返回删除后的对象 3.1.3 contains()--返回布尔值 3.1.4 size&…...
基于BindingList的WinForm数据绑定机制与DataGridView动态刷新技术
前言: 本文以连接SQLite为例进行代码演示 一、首先建立里一个模型类 public class MyData{public int id { get; set; }public string name { get; set; }public int age { get; set; }public string sex { get; set; }public string address { get; set; }} 二、…...
大数据新视界 -- Hive 数据分区:精细化管理的艺术与实践(上)(7/ 30)
💖💖💖亲爱的朋友们,热烈欢迎你们来到 青云交的博客!能与你们在此邂逅,我满心欢喜,深感无比荣幸。在这个瞬息万变的时代,我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…...
对于相对速度的重新理解 - 插一句
因为发了太多的公式,系统提示插入图片太频繁,下一个部分稍后再写。 这里要强调一下: 狭义相对论的平方和形式,其实就是因为分不清虚数单位的大小才写成这个样子。或者用物理语言来说,就是认为所有惯性系的“光速”都…...
css基础(27)_行内、行内块元素之间的空白问题
行内、行内块元素之间的空白问题 产生的原因:行内元素、行内块元素,彼此之间的换行会被浏览器解析为一个空白字符。 案例一: <!DOCTYPE html> <html lang"zh"> <head><meta charset"UTF-8">&…...
如果接口返回值图片有很长一串码,需要添加前缀
需要在前面添加前缀:data:image/jpeg;base64,然后将值赋值给<img :src"originalImage" /> this.tableLists.map((item)>{item.originalImage "data:image/jpeg;base64,"item.originalImage})以上方法会导致出现一个小bug,…...
《AI大模型开发笔记》——ollama应用全面解析
入门篇 1 ollama是什么? Ollama 是一个支持在本地运行大语言模型的工具,兼容 Windows、Linux 和 MacOS 操作系统。使用 Ollama,您仅需一行命令即可启动模型。 2 如何安装? Windows和MacOS用户,从下面链接下载安装即可: 下载地址:https://ollama.com/download Linux系…...
Paddle Inference部署推理(三)
三:Paddle Inference推理 导出模型 Paddle Inference支持使用飞桨静态图模型进行推理,您可以通过以下两种方式获取静态图模型: (1)飞桨框架导出推理模型 飞桨框架在训练模型过程中,会在本地存储最终训练…...
CSP/信奥赛C++语法基础刷题训练(23):洛谷P1217:[USACO1.5] 回文质数 Prime Palindromes
CSP/信奥赛C语法基础刷题训练(23):洛谷P1217:[USACO1.5] 回文质数 Prime Palindromes 题目描述 因为 151 151 151 既是一个质数又是一个回文数(从左到右和从右到左是看一样的),所以 151 151 …...
《跨越语言壁垒:Python 人工智能原型到 C++可执行程序的转型之路》
在人工智能的广阔天地里,Python 以其简洁易用和丰富的库资源成为众多开发者快速搭建人工智能原型的首选语言。然而,在一些对性能和资源控制要求极高的场景下,C则展现出无可比拟的优势。那么,如何将 Python 中开发的人工智能原型代…...
flowable流程图详细绘制教程
文章目录 前言一、flowable是什么?回答下之前的问题 二、flowable-modeler使用1. 使用步骤2.开始绘制弄一个请假的流程 三 加载该流程总结 前言 flowable有些晦涩难懂的东西: 我最开始接触的时候,还是用的activity,当时觉得好复杂,那么这次经过我自己在…...
Figma入门-基本操作制作登录页
Figma入门-基本操作制作登录页 前言 在之前的工作中,大家的原型图都是使用 Axure 制作的,印象中 Figma 一直是个专业设计软件。 最近,很多产品朋友告诉我,很多原型图都开始用Figma制作了,并且很多组件都是内置的&am…...
在windows操作系统上,用git与github账户连接
一、环境准备 1.1 git软件 1.2 github账号 1.3 创建一个项目目录,比如 D:\project\gitproject 二、开始操作 1. 进入项目目录下,右键,如图,打开git bash命令行 2. 在命令行输入以下三个命令 $ git config --global user.name &quo…...
springboot系列--拦截器执行原理
一、拦截器核心概念 一、定义 拦截器(Interceptor)是框架级别的组件,用于在请求的不同阶段(如到达控制器之前(也就是接口)、处理完成之后)动态地拦截和处理 HTTP 请求。 二、使用场景 一、用户…...
数据可视化复习2-绘制折线图+条形图(叠加条形图,并列条形图,水平条形图)+ 饼状图 + 直方图
目录 目录 一、绘制折线图 1.使用pyplot 2.使用numpy 编辑 3.使用DataFrame 编辑 二、绘制条形图(柱状图) 1.简单条形图 2.绘制叠加条形图 3.绘制并列条形图 4.水平条形图 编辑 三、绘制饼状图 四、绘制散点图和直方图 1.散点图 2…...
STM32F10x 定时器
使用定时器实现:B5 E5的开关 添加相关的.h路径文件 添加相关的.c配置文件 led.h文件 用于声明LED函数 #ifndef __LED_H //没有定义__LED_H #define __LED_H //就定义__LED_H #define LED1_ON GPIO_ResetBits(GPIOB,GPIO_Pin_5) #defi…...
VBA技术资料MF230:展开所有折叠视图并恢复
我给VBA的定义:VBA是个人小型自动化处理的有效工具。利用好了,可以大大提高自己的工作效率,而且可以提高数据的准确度。“VBA语言専攻”提供的教程一共九套,分为初级、中级、高级三大部分,教程是对VBA的系统讲解&#…...
【数据结构OJ】【图论】图综合练习--拓扑排序
题目描述 已知有向图,顶点从0开始编号,求它的求拓扑有序序列。 拓扑排序算法:给出有向图邻接矩阵 1.逐列扫描矩阵,找出入度为0且编号最小的顶点v 2.输出v,并标识v已访问 3.把矩阵第v行全清0 重复上述步骤࿰…...
网络安全防范
网络安全防范技术 所属课程网络攻防实践作业要求第六次作业 实践内容 学习总结 PDR,$$P^2$$DR安全模型。 防火墙(Firewall): 网络访问控制机制,布置在网际间通信的唯一通道上。 不足:无法防护内部威胁&…...
Linux nc 命令详解
简介 nc 全称 netcat,是一个在 Linux 中多功能的网络工具,通常用于通过 TCP 或 UDP 读取和写入网络连接,也能作为客户端或服务端用来 debug,测试,网络问题分析。 常用示例 检查端口是否是打开的 nc -zv <hostna…...
解决 Gradle 报错:`Plugin with id ‘maven‘ not found` 在 SDK 开发中的问题
在 SDK 开发过程中,使用 Gradle 构建和发布 SDK 是常见的任务。在将 SDK 发布为 AAR 或 JAR 包时,你可能会使用 apply plugin: maven 来发布到本地或远程的 Maven 仓库。但是,随着 Gradle 版本的更新,特别是从 Gradle 7 版本开始&…...
stm32cubemx+VSCODE+GCC+makefile 开发环境搭建
title: stm32cubemxVSCODEGCCmakefile 开发环境搭建 tags: FreertosHalstm32cubeMx 文章目录 内容往期内容导航第一步准备环境vscode 插件插件配置点灯 内容 往期内容导航 第一步准备环境 STM32CubeMXVSCODEMinGWOpenOcdarm-none-eabi-gcc 然后把上面下载的软件 3 4 5 bin 文…...
postgresql|数据库开发|python的psycopg2库按指定顺序批量执行SQL文件(可离线化部署)
一、 psycopg2简介 psycopg2库是python的一个可直接操作postgresql数据库的类库,是一个用于Python编程语言的PostgreSQL数据库适配器。它允许开发人员使用Python语言与PostgreSQL数据库进行交互和操作,不同于java,需要专用的一个驱动&#…...
学习ASP.NET Core的身份认证(基于Cookie的身份认证3)
用户通过验证后调用HttpContext.SignInAsync函数将用户的身份信息保存在认证Cookie中,以便后续的请求可以验证用户的身份,该函数原型如下所示,其中properties参数的主要属性已在前篇文章中学习,本文学习scheme和principal的意义及用法。 public static …...
Java 中的 HashMap 原理详解:底层结构与实现机制
HashMap 是 Java 中最常用的数据结构之一,它以其高效的存取速度在众多应用场景中被广泛使用。理解 HashMap 的底层实现原理,对提升开发效率、优化性能以及编写高效的代码都至关重要。本文将深入探讨 HashMap 的数据结构、存储机制、解决冲突的策略、扩容…...
数据库MYSQL——表的设计
文章目录 前言三大范式:几种实体间的关系:一对一关系:一对多关系:多对多关系: 前言 之前的博客中我们讲解的是关于数据库的增删改查与约束的基本操作, 是在已经创建数据库,表之上的操作。 在实…...
CUDA补充笔记
文章目录 一、不同核函数前缀二、指定kernel要执行的线程数量三、线程需要两个内置坐标变量来唯一标识线程四、不是blocksize越大越好,上限一般是1024个blocksize 一、不同核函数前缀 二、指定kernel要执行的线程数量 总共需要线程数是: 1 * N N个线程…...
OSI七层模型和TCP/IP五层模型详细介绍
这里写目录标题 一.OSI含义二.OSI七层模型1.应用层2.表示层3.会话层4.传输层5.网络层6.数据链路层7.物理层 TCP/IP五层协议1.应用层2.运输层运行在TCP上的协议运行在UDP上的协议 3.网络层IP协议配套使用的协议 4.数据链路层 四.网络协议分层的好处 一.OSI含义 OSI即是开放式通…...
mac安装Pytest、Allure、brew
安装环境 安装pytest 命令 pip3 install pytest 安装allure 命令:brew install allure 好吧 那我们在安装allure之前 我们先安装brew 安装brew 去了官网复制了命令 还是无法下载 如果你们也和我一样可以用这个方法哦 使用国内的代码仓库来执行brew的安装脚本…...
Linux/Windows/OSX 上面应用程序重新启动运行。
1、Linux/OSX 上面重新运行程序,直接使用 execvp 函数就可以了,把main 函数传递来的 argv 二维数组(命令行参数)传进去就可以,注意不要在 fork 出来的子进程搞。 2、Windows 平台可以通过 CreateProcess 函数来创建新的…...
自定义 Kafka 脚本 kf-use.sh 的解析与功能与应用示例
Kafka:分布式消息系统的核心原理与安装部署-CSDN博客 自定义 Kafka 脚本 kf-use.sh 的解析与功能与应用示例-CSDN博客 Kafka 生产者全面解析:从基础原理到高级实践-CSDN博客 Kafka 生产者优化与数据处理经验-CSDN博客 Kafka 工作流程解析:…...
Odoo :免费且开源的农牧行业ERP管理系统
文 / 开源智造Odoo亚太金牌服务 引言 提供农牧企业数字化、智能化、无人化产品服务及全产业链高度协同的一体化解决方案,提升企业智慧种养、成本领先、产业互联的核心竞争力。 行业典型痛点 一、成本管理粗放,效率低、管控弱 产品研发过程缺少体系化…...
AI的自我陷阱:大型神经网络训练中的模型崩溃现象
10月7日,发布在arxiv上的一篇名为Strong Model Collapse的论文,由Meta 、纽约大学和加州大学洛杉矶分校的研究人员共同发表的研究表明在训练大型神经网络(如 ChatGPT 和 Llama)时,由于训练语料库中包含合成数据而导致的…...
Python(下载安装)
简介 开发工具:pycharm, VS Code 1. 下载(解释器程序) Download Python | Python.org 2. 安装(解释器程序) 双击下载后的exe文件 查看版本:python(并可以执行python代码) 3. 下载…...
记录一种在内核空间向用户空间通知中断的方法
记录一种在内核空间向用户空间通知中断的方法 0.前言1.代码实现1)内核设备驱动实现2)消息通知实现3)测试程序 2.解析 参考文章:Linux驱动实践:中断处理函数如何【发送信号】给应用层? 0.前言 最近在项目中遇到一个需求,需要将一个…...
Apache Maven 标准文件目录布局
Apache Maven 采用了一套标准的目录布局来组织项目文件。这种布局提供了一种结构化和一致的方式来管理项目资源,使得开发者更容易导航和维护项目。理解和使用标准目录布局对于有效的Maven项目管理至关重要。本文将探讨Maven标准目录布局的关键组成部分,并…...
【vim】使用 gn 组合命令实现搜索选中功能
gn是Vim 7.4新增的一个操作(motion),作用是跳到并选中下一个搜索匹配项。 具体说,Vim里执行搜索后,执行n操作只会跳转到下一个匹配项,而不选中它。但是我们往往需要对匹配项执行一些修改操作,例…...
解决登录Google账号遇到手机上Google账号无法验证的问题
文章目录 场景小插曲解决方案总结 场景 Google账号在新的设备上登录的时候,会要求在手机的Google上进行确认验证,而如果没有安装Google play就可能出现像我一样没有任何弹框,无法实现验证 小插曲 去年,我在笔记本上登录了Googl…...
基于YOLOv10深度学习的公共安全持刀行为检测系统研究与实现(PyQt5界面+数据集+训练代码)
随着社会的不断进步和城市化进程的加快,人口密度的增加和社会结构的复杂化使得公共安全问题日益凸显。近年来,各类公共安全事件频发,其中持刀行为作为一种典型的暴力行为,已成为威胁公共安全的严重因素之一。这种行为在公共场所发…...
HP6心率血压传感器
目录 一、介绍 1、工作原理概述 2、具体实现步骤 二、HP6的通信及配置 1、通信接口 2、器件地址/命令 3、校验 三、程序设计 ①IIC通信相关基础函数 ②HP6相关基础函数 一、介绍 HP6心率血压传感器的原理主要基于光电容积脉搏波描记法(PPG)&…...
深入理解 JVM 中的 G1 垃圾收集器原理、算法、过程和参数配置
引言 Java 虚拟机(JVM)中的垃圾收集器(Garbage Collector,简称 GC)是自动内存管理的核心部分。G1(Garbage-First)垃圾收集器是 Oracle 在 JDK 7u4 版本中引入的一种新型垃圾收集器,…...
细说敏捷:敏捷四会之standup meeting
上一篇文章中,我们讨论了 敏捷四会 中 冲刺计划会 的实施要点,本篇我们继续分享敏捷四会中实施最频繁,团队最容易实施但往往也最容易走形的第二个会议:每日站会 关于每日站会的误区 站会是一个比较有标志性的仪式活动࿰…...
MySQL系列之身份鉴别(安全)
导览 前言Q:如何保障MySQL数据库身份鉴别的有效性一、有效性检查1. 用户唯一2. 启用密码验证3. 是否存在空口令用户4. 是否启用口令复杂度校验5. 是否设置口令的有效期6. 是否限制登录失败尝试次数7. 是否设置(超过尝试次数)锁定的最小时长8.…...
vue3封装Element Plus table表格组件
支持绝大部分Element Plus原有设置属性,支持分页,支持动态适配高度 效果展示 组件代码: <template><div class"table-wrap" ref"tableWrap"><el-tableclass"w100 h100":data"tableInfo.…...
SFP+光模块介绍
SFP光模块介绍 1 SFP光模块简介(Small Form -Factor Pluggable)2 光模块管脚定义 1 SFP光模块简介(Small Form -Factor Pluggable) 光模块(Optical Module)由光电子器件、功能电路和光接口等组成,光电子器件包括激光发射器(Laser Transmitte…...