RabbitMq交换机详解
目录
- 1.交换机类型
- 2.Fanout交换机
- 2.1.声明队列和交换机
- 2.2.消息发送
- 2.3.消息接收
- 2.4.总结
- 3.Direct交换机
- 3.1.声明队列和交换机
- 3.2.消息接收
- 3.3.消息发送
- 3.4.总结
- 4.Topic交换机
- 4.1.说明
- 4.2.消息发送
- 4.3.消息接收
- 4.4.总结
- 5.Headers交换机
- 5.1.说明
- 5.2.消息发送
- 5.3.消息接收
- 5.4.总结
- 5.5.交换机路由键(Routing Key)的处理方式
- 6.声明队列和交换机
- 6.1.基本API
- 6.2.fanout示例
- 6.3.direct示例
- 6.4.基于注解声明
- 7.消息转换器
- 7.1.测试默认转换器
- 7.2.配置JSON转换器
- 7.3.消费者接收Object
1.交换机类型
在之前的两个测试案例中直发消息队列,都没有交换机,生产者直接发送消息到队列。而一旦引入交换机,消息发送的模式会有很大变化:

可以看到,在订阅模型中,多了一个exchange角色,而且过程略有变化:
- Publisher:生产者,不再发送消息到队列中,而是发给交换机
- Exchange:交换机,一方面,接收生产者发送的消息。另一方面,知道如何处理消息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。到底如何操作,取决于Exchange的类型。
- Queue:消息队列也与以前一样,接收消息、缓存消息。不过队列一定要与交换机绑定。
- Consumer:消费者,与以前一样,订阅队列,没有变化
Exchange(交换机)只负责转发消息,不具备存储消息的能力,因此如果没有任何队列与Exchange绑定,或者没有符合路由规则的队列,那么消息会丢失!
交换机的类型有四种:
- Fanout:广播,将消息交给所有绑定到交换机的队列。我们最早在控制台使用的正是Fanout交换机
- Direct:订阅,基于RoutingKey(路由key)发送给订阅了消息的队列
- Topic:通配符订阅,与Direct类似,只不过RoutingKey可以使用通配符
- Headers:头匹配,基于MQ的消息头匹配,用的较少。
2.Fanout交换机
Fanout,英文翻译是扇出,我觉得在MQ中叫广播更合适。
在广播模式下,消息发送流程是这样的:

- 1) 可以有多个队列
- 2) 每个队列都要绑定到Exchange(交换机)
- 3) 生产者发送的消息,只能发送到交换机
- 4) 交换机把消息发送给绑定过的所有队列
- 5) 订阅队列的消费者都能拿到消息
我们的计划是这样的:

- 创建一个名为
shen.fanout的交换机,类型是Fanout - 创建两个队列
fanout.queue1和fanout.queue2,绑定到交换机shen.fanout
2.1.声明队列和交换机
在控制台创建队列fanout.queue1:

在创建一个队列fanout.queue2:

然后再创建一个交换机:

然后绑定两个队列到交换机:


2.2.消息发送
在publisher服务的SpringAmqpTest类中添加测试方法:
@Test
void testFanoutQueue() throws InterruptedException {String exchangeName = "shen.fanout";for (int i = 0; i < 50; i++) {String msg = "hello,everyone,message_" + i;rabbitTemplate.convertAndSend(exchangeName, null,msg);Thread.sleep(20);}
}
2.3.消息接收
在consumer服务的SpringRabbitListener中添加两个方法,作为消费者:
@RabbitListener(queues = "fanout.queue1")
public void listenFanoutQueue1(String msg) {System.out.println("消费者1接收到Fanout消息:【" + msg + "】");
}@RabbitListener(queues = "fanout.queue2")
public void listenFanoutQueue2(String msg) {System.out.println("消费者2接收到Fanout消息:【" + msg + "】");
}
2.4.总结
交换机的作用是什么?
- 接收publisher发送的消息
- 将消息按照规则路由到与之绑定的队列
- 不能缓存消息,路由失败,消息丢失
- FanoutExchange的会将消息路由到每个绑定的队列
3.Direct交换机
在Fanout模式中,一条消息,会被所有订阅的队列都消费。但是,在某些场景下,我们希望不同的消息被不同的队列消费。这时就要用到Direct类型的Exchange。

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

- 声明一个名为
shen.direct的交换机 - 声明队列
direct.queue1,绑定shen.direct,bindingKey为blud和red - 声明队列
direct.queue2,绑定shen.direct,bindingKey为yellow和red - 在
consumer服务中,编写两个消费者方法,分别监听direct.queue1和direct.queue2 - 在publisher中编写测试方法,向
shen.direct发送消息
3.1.声明队列和交换机
首先在控制台声明两个队列direct.queue1和direct.queue2,这里不再展示过程:

然后声明一个direct类型的交换机,命名为shen.direct:

然后使用red和blue作为key,绑定direct.queue1到shen.direct:


同理,使用red和yellow作为key,绑定direct.queue2到shen.direct,步骤略,最终结果:

3.2.消息接收
在consumer服务的SpringRabbitListener中添加方法:
@RabbitListener(queues = "direct.queue1")
public void listenDirectQueue1(String msg) {System.out.println("消费者1接收到direct.queue1的消息:【" + msg + "】");
}@RabbitListener(queues = "direct.queue2")
public void listenDirectQueue2(String msg) {System.out.println("消费者2接收到direct.queue2的消息:【" + msg + "】");
}
3.3.消息发送
在publisher服务的SpringAmqpTest类中添加测试方法:
@Test
public void testSendDirectExchange() {// 交换机名称String exchangeName = "shen.direct";// 消息String message = "红色警报!日本乱排核废水,导致海洋生物变异,惊现哥斯拉!";// 发送消息rabbitTemplate.convertAndSend(exchangeName, "red", message);
}
由于使用的red这个key,所以两个消费者都收到了消息:

我们再切换为blue这个key:
@Test
public void testSendDirectExchange() {// 交换机名称String exchangeName = "shen.direct";// 消息String message = "最新报道,哥斯拉是居民自治巨型气球,虚惊一场!";// 发送消息rabbitTemplate.convertAndSend(exchangeName, "blue", message);
}
你会发现,只有消费者1收到了消息:

3.4.总结
描述下Direct交换机与Fanout交换机的差异?
- Fanout交换机将消息路由给每一个与之绑定的队列
- Direct交换机根据RoutingKey判断路由给哪个队列
- 如果多个队列具有相同的RoutingKey,则与Fanout功能类似
- 可以看出Direct交换机的功能更加强大,想发送给全部队列就给他们一个相同的RoutingKey,根据分类发送就指定一个RoutingKey。
4.Topic交换机
4.1.说明
Topic类型的Exchange与Direct相比,都是可以根据RoutingKey把消息路由到不同的队列。
只不过Topic类型Exchange可以让队列在绑定BindingKey 的时候使用通配符!
BindingKey 一般都是有一个或多个单词组成,多个单词之间以.分割,例如: item.insert
通配符规则:
#:匹配一个或多个词*:匹配不多不少恰好1个词
举例:
item.#:能够匹配item.spu.insert或者item.spuitem.*:只能匹配item.spu
图示:

假如此时publisher发送的消息使用的RoutingKey共有四种:
china.news代表有中国的新闻消息;china.weather代表中国的天气消息;japan.news则代表日本新闻japan.weather代表日本的天气消息;
解释:
topic.queue1:绑定的是china.#,凡是以china.开头的routing key都会被匹配到,包括:china.newschina.weather
topic.queue2:绑定的是#.news,凡是以.news结尾的routing key都会被匹配。包括:china.newsjapan.news
接下来,我们就按照上图所示,来演示一下Topic交换机的用法。
首先,在控制台按照图示例子创建队列、交换机,并利用通配符绑定队列和交换机。此处步骤略。最终结果如下:

4.2.消息发送
在publisher服务的SpringAmqpTest类中添加测试方法:
/*** topicExchange*/
@Test
public void testSendTopicExchange() {// 交换机名称String exchangeName = "shen.topic";// 消息String message = "喜报!孙悟空大战哥斯拉,胜!";// 发送消息rabbitTemplate.convertAndSend(exchangeName, "china.news", message);
}
4.3.消息接收
在consumer服务的SpringRabbitListener中添加方法:
@RabbitListener(queues = "topic.queue1")
public void listenTopicQueue1(String msg){System.out.println("消费者1接收到topic.queue1的消息:【" + msg + "】");
}@RabbitListener(queues = "topic.queue2")
public void listenTopicQueue2(String msg){System.out.println("消费者2接收到topic.queue2的消息:【" + msg + "】");
}
4.4.总结
描述下Direct交换机与Topic交换机的差异?
- Topic交换机接收的消息RoutingKey必须是多个单词,以
**.**分割 - Topic交换机与队列绑定时的bindingKey可以指定通配符
#:代表0个或多个词*:代表1个词
5.Headers交换机
5.1.说明
在RabbitMQ中使用Headers交换器时,你可以根据消息头里的键值对来进行路由,而不是像direct或topic交换器那样依赖路由键。一个Headers交换器允许你定义一个或多个键值对作为绑定条件,只有当消息的头信息满足这些条件时,它才会被路由到相应的队列。

Headers也支持一个称作“x-match”的特殊属性,这个属性决定了多个头信息之间是“all”匹配还是“any”匹配:
all表示所有的键值对都必须匹配。这是默认值。any表示消息只要有任何一个头信息符合条件就可以被路由。
这些属性设置在绑定队列到Headers交换机时的参数中。
接下来,我们就按照上图所示,来演示一下Headers交换机的用法。
首先,在控制台按照图示例子创建队列、交换机,并定义键值绑定队列和交换机。此处步骤略。最终结果如下:

5.2.消息发送
@Test
public void sendAnimalMessage() {String msg = "最新报道,哥斯拉是居民自治巨型气球,虚惊一场!" ;Map<String, Object> headers = new HashMap<>();headers.put("category", "animal");headers.put("type", "rabbit");rabbitTemplate.convertAndSend("shen.headers", "", msg, m -> {headers.forEach((key, value) -> m.getMessageProperties().setHeader(key, value));return m;});
}
5.3.消息接收
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;@Component
public class HeaderMessageReceiver {@RabbitListener(queues = "headers.queue")public void receiveMessage(String message) {System.out.println("Received with Headers Exchange: " + message);}
}
5.4.总结
-
RabbitMQ的Headers交换机和Direct和Topic交换机稍有不同。他们的路由行为是基于消息头部(header)中的键值对进行匹配的,这意味着消息是根据它提供的header信息路由到不同的队列中的。
-
headers的交换机关注消息header中的内容来决定消息该被送往哪个或哪些队列。这种类型的交换机提供了最为灵活的路由方式,允许你通过多个属性来定义路由规则。 -
当你绑定一个队列到Headers交换机时,你可以指定一个或多个header作为匹配规则。与Direct交换机的路由键相比,Headers交换机使用的是一组键值对。
-
Headers交换机行为
匹配:如果设置的x-match属性为all,那么只有当消息的header与绑定时指定的所有键值对都相匹配时,消息才会被路由到对应的队列。如果设置为any,那么只需消息header中的任一键值对与绑定时指定的某一个键值对匹配,消息就会被路由到对应的队列。
忽略路由键:与Direct或Topic交换机不同,发送到Headers交换机的消息在使用basic.publish方法时通常设置路由键为一个空字符串,因为Headers交换机并不使用这个字段来决定消息的路由。
Headers交换机是RabbitMQ中较为高级且灵活的特性,它为路由提供了额外的维度,但这也意味着使用和理解上会更复杂一些。在实际应用中,如果消息的路由决策需要根据多个属性来做更复杂的判断,那么Headers交换机可能会是个不错的选择。
5.5.交换机路由键(Routing Key)的处理方式
在RabbitMQ中,交换器(Exchange)的类型决定了消息如何路由到队列中。所有交换器在发送消息时都可以接收一个路由键(Routing Key)参数,但是不同类型的交换器对路由键的处理方式各不相同。fanout、direct、topic 和 headers 是几种常见的交换器类型,它们对路由键的处理逻辑有所区别:
-
Fanout Exchange:
- Routing Key:在
fanout交换器上,路由键会被忽略。不管发布到交换器上的消息的路由键是什么,消息都会被发送到所有绑定到该交换器的队列。 - 用途:当您想要将消息广播到所有队列时使用。
- Routing Key:在
-
Direct Exchange:
- Routing Key:在
direct交换器上,一个消息会被路由到与消息的路由键完全匹配的队列。 - 用途:当你想要定点发送消息到指定的队列时使用。
- Routing Key:在
-
Topic Exchange:
- Routing Key:
topic交换器允许使用通配符进行模糊匹配。路由键可以有多个词(words),用点(.)隔开。通配符可以是星号(*)匹配一个词或者井号(#)匹配零个或多个词。 - 用途:适用于发送到多个队列,但队列订阅不是完全相同的情况,实现了模式匹配。
- Routing Key:
-
Headers Exchange:
- Routing Key:
headers交换器不依赖于路由键的匹配规则。它使用头信息中的键值对进行匹配。 - 用途:当你想根据消息内容的多个属性来路由消息时使用。
- Routing Key:
即使 fanout 交换器能让你设定路由键,但它并不会使用这个值来决定消息的路由。在 fanout 交换机的情况下,设置路由键不会产生任何影响。
direct 和 topic 交换器会根据路由键来决定目标队列,而 headers 交换器则完全忽略路由键,转而使用消息头的键值对来进行路由决定。
要体会它们的区别,最好是实际去设置不同的交换器,并发布带有不同路由键或头信息的消息,然后观察消息如何被路由到不同的队列。这样通过实践,你将更容易理解每种类型的交换器和路由行为的差异。
6.声明队列和交换机
在之前我们都是基于RabbitMQ控制台来创建队列、交换机。但是在实际开发时,队列和交换机是程序员定义的,将来项目上线,又要交给运维去创建。那么程序员就需要把程序中运行的所有队列和交换机都写下来,交给运维。在这个过程中是很容易出现错误的。
因此推荐的做法是由程序启动时检查队列和交换机是否存在,如果不存在自动创建。
6.1.基本API
SpringAMQP提供了几个类,用来声明队列、交换机及其绑定关系:
Queue: 用于声明队列,可以用工厂类QueueBuilder构建。Exchange:用于声明交换机,可以用工厂类ExchangeBuilder构建。Binding:用于声明队列和交换机的绑定关系,可以用工厂类BindingBuilder构建。
SpringAMQP提供了一个Queue类,用来创建队列:

SpringAMQP还提供了一个Exchange接口,来表示所有不同类型的交换机:

我们可以自己创建队列和交换机,不过SpringAMQP还提供了ExchangeBuilder来简化这个过程:

而在绑定队列和交换机时,则需要使用BindingBuilder来创建Binding对象:

6.2.fanout示例
注意,发送方只关心发送就行了,一般都是消费者关心是什么样子的,所以在消费方声明。
在consumer中创建一个类,声明队列和交换机:
package com.example.consumer.config;import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class FanoutConfiguration {/*** 声明交换机** @return Fanout类型交换机*/@Beanpublic FanoutExchange fanoutExchange() {//ExchangeBuilder.fanoutExchange("").build();return new FanoutExchange("shen.fanout");}/*** 第1个队列*/@Bean(name = "queue1")public Queue fanoutQueue1() {//QueueBuilder.durable("").build();//持久的,带向磁盘。return new Queue("fanout.queue1");//默认持久}/*** 绑定队列和交换机*/@Beanpublic Binding fanoutBuiding1(@Qualifier(value = "queue1") Queue fanoutQueue1, FanoutExchange fanoutExchange) {return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);}/*** 第2个队列*/@Beanpublic Queue fanoutQueue2() {//QueueBuilder.durable("").build();//持久的,带向磁盘。return new Queue("fanout.queue2");//默认持久}/*** 绑定队列和交换机*/@Beanpublic Binding fanoutBuiding2() {/*在spring中所有加了 Bean 的方法都会被动态代理,所以此处并不是真正的调用了这个方法 fanoutQueue4(),而是检查 spring容器 中有没有代理这个 fanoutQueue4()方法,代理了,直接返回这个 bean 对象,而不是真正的去执行。*/return BindingBuilder.bind(fanoutQueue2()).to(fanoutExchange());}}
6.3.direct示例
direct模式由于要绑定多个KEY,会非常麻烦,每一个Key都要编写一个binding:
package com.example.consumer.config;import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class DirectConfiguration {/*** 声明交换机** @return Direct类型交换机*/@Beanpublic DirectExchange directExchange() {return ExchangeBuilder.directExchange("shen.direct").build();}/*** 第1个队列*/@Beanpublic Queue directQueue1() {return new Queue("direct.queue1");}/*** 绑定队列和交换机*/@Beanpublic Binding bindingQueue1WithRed(Queue directQueue1, DirectExchange directExchange) {return BindingBuilder.bind(directQueue1).to(directExchange).with("red");}/*** 绑定队列和交换机*/@Beanpublic Binding bindingQueue1WithBlue(Queue directQueue1, DirectExchange directExchange) {return BindingBuilder.bind(directQueue1).to(directExchange).with("blue");}/*** 第2个队列*/@Beanpublic Queue directQueue2() {return new Queue("direct.queue2");}/*** 绑定队列和交换机*/@Beanpublic Binding bindingQueue2WithRed() {return BindingBuilder.bind(directQueue2()).to(directExchange()).with("red");}/*** 绑定队列和交换机*/@Beanpublic Binding bindingQueue2WithYellow(Queue directQueue2, DirectExchange directExchange) {return BindingBuilder.bind(directQueue2).to(directExchange).with("yellow");}}
6.4.基于注解声明
基于@Bean的方式声明队列和交换机比较麻烦,Spring还提供了基于注解方式来声明。
例如,我们同样声明Direct模式的交换机和队列:
@RabbitListener(bindings = @QueueBinding(value = @Queue(name = "direct.queue1"),exchange = @Exchange(name = "shen.direct", type = ExchangeTypes.DIRECT),key = {"red", "blue"}
))
public void listenDirectQueue1(String msg){System.out.println("消费者1接收到direct.queue1的消息:【" + msg + "】");
}@RabbitListener(bindings = @QueueBinding(value = @Queue(name = "direct.queue2"),exchange = @Exchange(name = "shen.direct", type = ExchangeTypes.DIRECT),key = {"red", "yellow"}
))
public void listenDirectQueue2(String msg){System.out.println("消费者2接收到direct.queue2的消息:【" + msg + "】");
}
是不是简单多了。
再试试Topic模式:
@RabbitListener(bindings = @QueueBinding(value = @Queue(name = "topic.queue1"),exchange = @Exchange(name = "shen.topic", type = ExchangeTypes.TOPIC),key = "china.#"
))
public void listenTopicQueue1(String msg){System.out.println("消费者1接收到topic.queue1的消息:【" + msg + "】");
}@RabbitListener(bindings = @QueueBinding(value = @Queue(name = "topic.queue2"),exchange = @Exchange(name = "shen.topic", type = ExchangeTypes.TOPIC),key = "#.news"
))
public void listenTopicQueue2(String msg){System.out.println("消费者2接收到topic.queue2的消息:【" + msg + "】");
}
7.消息转换器
Spring的消息发送代码接收的消息体是一个Object:

而在数据传输时,它会把你发送的消息序列化为字节发送给MQ,接收消息的时候,还会把字节反序列化为Java对象。
只不过,默认情况下Spring采用的序列化方式是JDK序列化。众所周知,JDK序列化存在下列问题:
- 数据体积过大
- 有安全漏洞
- 可读性差
我们来测试一下。
7.1.测试默认转换器
1)创建测试队列
首先,我们在consumer服务中声明一个新的配置类:

利用@Bean的方式创建一个队列,具体代码:
package com.example.consumer.config;import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class MessageConfig {@Beanpublic Queue objectQueue() {return new Queue("object.queue");}
}
注意,这里我们先不要给这个队列添加消费者,我们要查看消息体的格式。
重启consumer服务以后,该队列就会被自动创建出来了:

2)发送消息
我们在publisher模块的SpringAmqpTest中新增一个消息发送的代码,发送一个Map对象:
@Test
public void testSendMap() throws InterruptedException {// 准备消息Map<String,Object> msg = new HashMap<>();msg.put("name", "jack");msg.put("age", 21);// 发送消息rabbitTemplate.convertAndSend("object.queue", msg);
}
发送消息后查看控制台:

可以看到消息格式非常不友好。
7.2.配置JSON转换器
显然,JDK序列化方式并不合适。我们希望消息体的体积更小、可读性更高,因此可以使用JSON方式来做序列化和反序列化。
在publisher和consumer两个服务中都引入依赖:
<dependency><groupId>com.fasterxml.jackson.dataformat</groupId><artifactId>jackson-dataformat-xml</artifactId><version>2.9.10</version>
</dependency>
注意,如果项目中引入了spring-boot-starter-web依赖,则无需再次引入Jackson依赖。
配置消息转换器,在publisher和consumer两个服务的启动类中添加一个Bean即可:
@Bean
public MessageConverter messageConverter(){// 1.定义消息转换器Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();// 2.配置自动创建消息id,用于识别不同消息,也可以在业务中基于ID判断是否是重复消息jackson2JsonMessageConverter.setCreateMessageIds(true);return jackson2JsonMessageConverter;
}
消息转换器中添加的messageId可以便于我们将来做幂等性判断。
此时,我们到MQ控制台删除object.queue中的旧的消息。然后再次执行刚才的消息发送的代码,到MQ的控制台查看消息结构:

可以看到,消息大小小了很多,只要24bytes了。
7.3.消费者接收Object
我们在consumer服务中定义一个新的消费者,publisher是用Map发送,那么消费者也一定要用Map接收,格式如下:
@RabbitListener(queues = "object.queue")
public void listenSimpleQueueMessage(Map<String, Object> msg) throws InterruptedException {System.out.println("消费者接收到object.queue消息:【" + msg + "】");
}
相关文章:
RabbitMq交换机详解
目录 1.交换机类型2.Fanout交换机2.1.声明队列和交换机2.2.消息发送2.3.消息接收2.4.总结 3.Direct交换机3.1.声明队列和交换机3.2.消息接收3.3.消息发送3.4.总结 4.Topic交换机4.1.说明4.2.消息发送4.3.消息接收4.4.总结 5.Headers交换机5.1.说明5.2.消息发送5.3.消息接收5.4.…...
智能优化算法应用:基于适应度相关算法3D无线传感器网络(WSN)覆盖优化 - 附代码
智能优化算法应用:基于适应度相关算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用:基于适应度相关算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.适应度相关算法4.实验参数设定5.算法…...
spring之基于注解管理Bean
学习的最大理由是想摆脱平庸,早一天就多一份人生的精彩;迟一天就多一天平庸的困扰。各位小伙伴,如果您: 想系统/深入学习某技术知识点… 一个人摸索学习很难坚持,想组团高效学习… 想写博客但无从下手,急需…...
Wireshark在云计算中的应用
第一章:Wireshark基础及捕获技巧 1.1 Wireshark基础知识回顾 1.2 高级捕获技巧:过滤器和捕获选项 1.3 Wireshark与其他抓包工具的比较 第二章:网络协议分析 2.1 网络协议分析:TCP、UDP、ICMP等 2.2 高级协议分析:HTTP…...
三菱plc学习入门(一,认识三菱plc)
今天就开始对三菱的plc软件入一个门,希望小编的文章对读者和初学者有所帮助!欢迎评论指正,废话不多说,下面开始学习。 目录 plc的型号介绍 M表示什么? T表示什么? R表示什么? 为什么三菱没…...
设计模式——中介者模式
引言 中介者模式是一种行为设计模式, 能让你减少对象之间混乱无序的依赖关系。 该模式会限制对象之间的直接交互, 迫使它们通过一个中介者对象进行合作。 问题 假如你有一个创建和修改客户资料的对话框, 它由各种控件组成, 例如…...
【 USRP安装教程】MATLAB 2023B
步骤 matlabdocusrp驱动包 doc 安装包内容列表 双击“R2023b_Doc_Windows.iso” 打开cmd 查看盘符 切换盘符 因为是F盘,所以cmd输入:“F:” F:进入可安装界面 cd F:\bin\win64安装离线文档库 .\mpm install-doc --matlabroot"C:\MATLAB\R202…...
AI绘画中UNet用于预测噪声
介绍 在AI绘画领域中,UNet是一种常见的神经网络架构,广泛用于图像相关的任务,尤其是在图像分割领域中表现突出。UNet最初是为了解决医学图像分割问题而设计的,但其应用已经扩展到了多种图像处理任务。 特点 对称结构:…...
解决 Hbuilder打包 Apk pad 无法横屏 以及 H5 直接打包 成Apk
解决 Hbuilder打包 Apk pad 无法横屏 前言云打包配置 前言 利用VUE 写了一套H5 想着 做一个APP壳 然后把 H5 直接嵌进去 客户要求 在pad 端 能够操作 然后页面风格 也需要pad 横屏展示 云打包 配置 下面是manifest.json 配置文件 {"platforms": ["iPad"…...
云原生之深入解析如何在K8S环境中使用Prometheus来监控CoreDNS指标
一、什么是 Kubernetes CoreDNS? CoreDNS 是 Kubernetes 环境的DNS add-on 组件,它是在控制平面节点中运行的组件之一,使其正常运行和响应是 Kubernetes 集群正常运行的关键。DNS 是每个体系结构中最敏感和最重要的服务之一。应用程序、微服…...
Unity3D UDP传输大文件怎么提高速度详解
前言 Unity3D是一款强大的游戏开发引擎,但是在处理大文件传输时,往往会遇到速度较慢的问题。本文将详细介绍如何通过使用UDP协议来提高大文件传输的速度,并给出相应的技术详解和代码实现。 对惹,这里有一个游戏开发交流小组&…...
数据结构——栈和队列的应用
1.栈在括号匹配中的应用 算法的思想如下; 1)初始设置一个空栈,顺序读入括号。 2)若是右括号,则或使置于栈顶的最急迫期待得以消解,或是不合法的情况(括号序列不 匹配,退出程序)。 3)若是左括号,则作为一个新的更急迫…...
第7章 排序
前言 在这一章,我们讨论数组元素的排序问题。为简单起见,假设在我们的例子中数组只包含整数,虽然更复杂的结构显然也是可能的。对于本章的大部分内容,我们还假设整个排序工作能够在主存中完成,因此,元素的个…...
AR眼镜光学方案_AR眼镜整机硬件定制
增强现实(Augmented Reality,AR)技术通过将计算机生成的虚拟物体或其他信息叠加到真实世界中,实现对现实的增强。AR眼镜作为实现AR技术的重要设备,具备虚实结合、实时交互的特点。为了实现透视效果,AR眼镜需要同时显示真实的外部世…...
Linux shell编程学习笔记36:read命令
*更新日志 *2023-12-18 1.根据[美] 威廉肖特斯 (Willian shotts)所著《Linux命令行大全(第2版)》 更新了-e、-i、-r选项的说明 2.更新了 2.8 的实例,增加了gif动图 3.补充了-i的应用实例 2.1…...
Python表达式
表达式 本章将解释 Python 中组成表达式的各种元素的的含义。 语法注释: 在本章和后续章节中,会使用扩展 BNF 标注来描述语法而不是词法分析。 当(某种替代的)语法规则具有如下形式 name :: othername并且没有给出语义,则这种…...
风速预测(六)基于Pytorch的EMD-CNN-GRU并行模型
目录 前言 1 风速数据EMD分解与可视化 1.1 导入数据 1.2 EMD分解 2 数据集制作与预处理 2.1 先划分数据集,按照8:2划分训练集和测试集 2.2 设置滑动窗口大小为96,制作数据集 3 基于Pytorch的EMD-CNN-GRU并行模型预测 3.1 数据加载&a…...
【Stm32-F407】全速DAP仿真器下载程序
文章内容如下: 1) 全速DAP仿真器简介2) 全速DAP仿真器下载程序流程 1) 全速DAP仿真器简介 1)全速DAP仿真器简介 DAP全称 Data Acquisition Processor,是一种用于数据采集和实时控制的设备。本文使用的全速DAP仿真器遵循ARM公司的CMSIS-DAP标准ÿ…...
ArcGIS Pro SDK导出的几何XML和Json
本博主会持续更新关于ArcGIS Pro SDK的相关内容,请读者关注一下 圆 XML <PolygonN xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance" xmlns:xs"http://www.w3.org/2001/XMLSchema" xmlns:typens"http://www.esri.com/schemas/…...
随笔记录-springboot_LoggingApplicationListener+LogbackLoggingSystem
环境:springboot-2.3.1 加载日志监听器初始化日志框架 SpringApplication#prepareEnvironment SpringApplicationRunListeners#environmentPrepared EventPublishingRunListener#environmentPrepared SimpleApplicationEventMulticaster#multicastEvent(Applicati…...
Leetcode 3576. Transform Array to All Equal Elements
Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接:3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到…...
【Java学习笔记】Arrays类
Arrays 类 1. 导入包:import java.util.Arrays 2. 常用方法一览表 方法描述Arrays.toString()返回数组的字符串形式Arrays.sort()排序(自然排序和定制排序)Arrays.binarySearch()通过二分搜索法进行查找(前提:数组是…...
iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版分享
平时用 iPhone 的时候,难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵,或者买了二手 iPhone 却被原来的 iCloud 账号锁住,这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...
postgresql|数据库|只读用户的创建和删除(备忘)
CREATE USER read_only WITH PASSWORD 密码 -- 连接到xxx数据库 \c xxx -- 授予对xxx数据库的只读权限 GRANT CONNECT ON DATABASE xxx TO read_only; GRANT USAGE ON SCHEMA public TO read_only; GRANT SELECT ON ALL TABLES IN SCHEMA public TO read_only; GRANT EXECUTE O…...
SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现
摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序,以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务,提供稳定高效的数据处理与业务逻辑支持;利用 uniapp 实现跨平台前…...
反射获取方法和属性
Java反射获取方法 在Java中,反射(Reflection)是一种强大的机制,允许程序在运行时访问和操作类的内部属性和方法。通过反射,可以动态地创建对象、调用方法、改变属性值,这在很多Java框架中如Spring和Hiberna…...
Java面试专项一-准备篇
一、企业简历筛选规则 一般企业的简历筛选流程:首先由HR先筛选一部分简历后,在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如:Boss直聘(招聘方平台) 直接按照条件进行筛选 例如:…...
Rapidio门铃消息FIFO溢出机制
关于RapidIO门铃消息FIFO的溢出机制及其与中断抖动的关系,以下是深入解析: 门铃FIFO溢出的本质 在RapidIO系统中,门铃消息FIFO是硬件控制器内部的缓冲区,用于临时存储接收到的门铃消息(Doorbell Message)。…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
Python ROS2【机器人中间件框架】 简介
销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...
