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

MQ快速入门【详细】个人笔记 讲解通俗易懂

1.同步通讯和异步通讯

  • 同步通讯:如果举个例子来说,同步通讯就像是两个人在打电话,一方说的话,能够立马传给另一方,消息的时效性非常高,但是相对的,只能是给一个人通讯,如果这个时候,有另一个人想和你建立联系进行通讯,那么抱歉,你必须得先结束当下的通讯,才能和其他人通讯
  • 异步通讯:如果把同步通讯比作打电话,那么异步通讯就像是在发微信,我发给另外一个人消息,但是这个人不一定会立马看见并且回复我,通信的时效性不强,但是,它可以同时给多方发送消息,试想一下,我们平时发微信的时候,是可以同时给多个人发送消息,并且也能够同时接收多个人发过来的消息

同步通讯——打电话 


异步通讯——发微信


 2.在某些场景中使用同步通讯的缺陷

比如微服务直接相互调用的feign中间件,使用的就是同步调用,具体来看缺陷有以下几点

 

  1. 这是一个简单的用户支付模块牵扯的服务,用户在支付完之后,后台需要调用如订单服务、仓储服务、短信服务之类的模块来完善这个支付功能,由于是支付功能来直接调用的其他服务,所以服务之间的耦合性较高,当我们要再添加一个积分服务,来让支付服务调用,那么就需要修改支付服务中的代码,麻烦!
  2. 在支付业务调用别的服务的时候,由于是同步通讯,所以只能一个一个调用,当前服务调用完之后,才能调用别的服务,比如上面这个情况,支付服务调用完订单服务之后,才能调用仓储服务,调用完仓储服务后,才能调用短信服务,耗时太长了
  3. 假如仓储服务突然挂掉了,那么支付服务在调用这个仓储服务时就会停滞在这里,那么当有更多的用户来进行支付,那么就有越来越多的请求卡在这一块,当支付服务的资源被耗尽之后,也就会挂掉,像这样一条链上的服务,有其中一个服务挂掉,相应的也会影响别的服务跟着挂掉,就是级联失效问题

3.异步调用方案解决相关问题

还是上面的这个例子,俗话说,在web架构中没有什么是加一层解决不了的,所以我们在支付服务和其他被调用的服务之间加一个代理人Broker), 你可以理解成,当我们去吃饭的时候,和我们交涉的前台人员,我们向前台点餐,而不会直接向厨房点餐(当然,路边小摊只有老板一个人做饭+点餐,当我没说)那么此时这种情况,我们就属于客户端,厨房大厨就是服务端,而这个负责接待我们的前台人员就是代理人,他来代替我们和厨房之间通信


当用户支付成功之后,支付服务就会通知代理人BROKER,说:“有一个顾客已经成功下单了,订单是1001”,那么这个BROKER就会通知其余服务,说:“客户订单1001已经支付完成了,你们赶紧来处理一下!”此时,这些服务就会来完成各自的业务。(但是要注意的是,被调用的服务要通过BROKER订阅事件,相当于让这些服务有能力接收到BROKER发布的事件,不接收到BROKER发布的事件,自然是无法处理这个事件的)

  1. 在整个过程中,支付服务在用户支付成功之后,就只会通知一下BROKER,发布一个用户完成支付的事件,其余的,比如调用其他业务,它都不再伸手了,联想下现实生活,当我们跟前台点完餐之后,也确实不会再和后台的厨师交流什么了!所以这样就解除了服务与服务之间的耦合了
  2. 当支付服务被用户使用并且成功支付之后,除了发布事件以外,它就不再干别的了,所以又能继续接待新用户的支付,至于后台的别的服务,什么时候去接收处理这个订单事件,就不是用户和支付服务管的了,这样数据的吞吐量就增高了 ,试想一下我们平时淘宝购物的时候,我们只要支付成功了并且前台给我们回复支付成功的消息之后,是不是就不会管人家后台的事情了,倘若是同步通讯,那我们要等到后台将这个事件处理完毕之后才会得到答复,在此之间我们就会一直占着这个支付服务,是不是太麻烦了!
  3. 由于异步通讯服务之间的依赖性不强,所以当有其中一个服务挂掉了之后,也不会影响其他服务的正常运行,比如,仓储服务挂了,他并不会有影响支付服务的正常运行,不用担心级联失败的问题
  4. 随着业务量增加,请求越来越多,假如一下子来了十个订单完成的服务,但是订单服务、仓储服务这些只能在同一时间处理一个事件,这时,BROKER就会起到一个缓冲的作用,就像水库里的水坝一样,他可以将事件排成队列,当其他服务处理完一个事件之后,再放给他们下一个事件,这种操作称之为流量削峰

同样的,有优点就有缺点,这样加一个BROKER,那么基本上中间通讯就全部依赖这个BROKER了,那么就吃这个中间代理人的可靠性、安全性、吞吐能力了,不得不说这个服务员是真不好干。

4.RabbitMQ

MQ(MessageQueue),消息队列。用来存放消息也就是刚刚在上面提到的事件。这个MQ相当于就是事件驱动框架里的BROKER

MQ的具体实现有很多种,例如RabbitMQ,ActiveMQ,RocketMQ和Kafka,下面来重点介绍一下RabbitMQ 

4.1.安装RabbitMQ

这里我们采用docker来部署安装,这里我们可以使用docker命令直接从仓库拉取,也可以自己上传rabbitmq的压缩包,我采用的是后者,安装包小伙伴可以自行上官网下载

  1. 下载好了压缩包,我们直接将其拖入finalshell
  2. 拖入之后会自行下载,然后使用docker load命令下载rabbitmq的镜像
  3. 然后使用docker run命令启动容器就ok了,具体命令如下
docker run \
>  -e RABBITMQ_DEFAULT_USER=itcast \
>  -e RABBITMQ_DEFAULT_PASS=123321 \
>  --name mq \
>  --hostname mq1 \
>  -p 15672:15672 \
>  -p 5672:5672 \
>  -d \
>  rabbitmq:3-management

 稍微看下这个docker run命令,首先设置了两个环境变量一个用户名,一个密码,然后设置了一个主机名,这个是集群部署时会用到,然后映射了两个端口,第一个15672是rabbitmq自己的可视化终端的端口,第二个5672是事件传输的端口

访问一下rabbitmq的可视化终端:

看一下rabbitMQ的服务框架: 

从这个框架我们可以看出,消息事件的生产者将事件转发给exchange,这个exchange担任一个路由转发的角色,它将事件路由给不同的queue,也就是存放消息的队列,最后消费者再从这个队列中取出事件来处理,其中要留意的是virtual host(虚拟主机),它类似docker的容器,具有隔离效果,每个用户绑定一个独立的虚拟主机,达到互不干扰的效果 

4.2.SpringAMQP

AMQP(Advanced Message Queuing Protocol):高级消息协议,是一个进程间传递异步消息网络协议,而SpringAMQP就是基于这种协议的消息中间件框架,它提供了一个简单的API来发送和接收异步、可靠的消息,其主要特点有以下几点:

  • 提供监听器容器,用来异步处理发送来的消息
  • 提供RabbitTemplate来接收和发送消息
  • 提供RabbitAdmin用于自动声明 queues、exchanges 和 bindings

总而言之,这个SpringAMQP框架大大减少了使用RabbitMQ的成本

怎么使用这个SpringAMQP呢?请往下看! 

4.3.一个简单的消息队列的demo

这个demo分了两个服务,一个是consumer消费者,另一个是publisher生产(发布)者,我们要做的首先是引入SpringAMQP的依赖,下面引入了一套完整的依赖,包含springboot,springamqp和junit测试(这些依赖是引入在父工程里的,子工程就不用引入了)

        <!--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></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><scope>test</scope></dependency>

紧接着,在生产者publisher服务中编写配置文件,来告诉spring我们要使用rabbitmq来进行消息的发布和接收,其中我们告诉了spring我们要使用的rabbitmq的地址,端口,用户名,密码和虚拟主机名称,host地址是我们虚拟机的地址,port端口号是我们用docker启动rabbitmq容器设置的,一般来说都是5672,用户名和密码也是在那个时候设置的,虚拟主机是rabbitmq默认的,我们可以在可视化界面里更改,这里不做演示了

spring:rabbitmq:host: 192.168.88.128port: 5672username: itcastpassword: 123321virtual-host: /

 然后就是编写测试单元来发送消息

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringAmqpTest {@Autowiredprivate RabbitTemplate rabbitTemplate;@Testpublic void testMessageToSimpleQueue(){String queueName = "simplequeue";String message = "hello AMQP";rabbitTemplate.convertAndSend(queueName,message);}
}

注意:这是一个springboot的测试类,所以要加上SpringBootTest的注解,由于我们要使用注入的类(这里用autowired自动注入的RabbitTemplate),所以还要添加上RunWith的注解,这个注解在Junit5中是被携带上的,在Junit4中没有,所以我们得单另声明下

使用RabbitTemplate类的convertAndSent方法来完成消息的发送,参数有两个,一个是队列的名称,另一个是消息内容,这里的队列的名称,我是事先创建好的,没有的可以去rabbitmq可视化界面创建一个

运行测试单元之后:

点击队列名称,我们进入到这个队列中查看一下消息内容:

发现正是我们刚刚发送的消息内容 

好了,到这里发送消息的服务已经看完了,我们再来看看接收消息的服务

和发送消息的步骤类似:

  • 优先引入springamqp的依赖,不过刚刚说了,我们是在父工程中引入的,所以子工程就不用引入了
  • 然后就是编写配置文件,和消息发送方的一样
  • 最后一步,就是编写接收的代码

刚刚在上面说了springamqp的特性了,它提供一个专门来监听消息的容器,我们只需要把容器创建好,就能接收发送来的消息了,代码如下:

@Component
public class SpringRabbitListener {@RabbitListener(queues = "simplequeue")public void listenSimpleQueue(String msg){System.out.println("接收到消息了:" + msg);}
}

创建一个消息监听类,并把它注册到spring的容器中,指定好监听的队列,这里监听的是simplequeue这个队列,创建一个接收消息的方法,注意参数类型和发送的消息类型是一致的

成功接收到消息: 


注意:Queue中存放的消息,一旦被消费者处理了之后,就销毁了,有种阅后即焚的感觉,所以这个消息被消费者接收到之后,我们再看Queue队列中,发现已经没有消息了 


5.五种消息发布模型

  • 基本消息队列(Basic Queue)
  • 工作消息队列(Work Queue)
  • 发布订阅模式(Publish、Subscribe)其中根据交换机的不同分了三种
  1. 广播(Fanout Exchange)
  2. 路由(Direct Exchange)
  3. 主题(Topic Exchange)

5.1.基本消息队列

基本消息队列就是刚刚4.3中讲的那个demo, 由发布者publisher,队列queue,消费者consumer组成


5.2.工作消息队列 

工作消息队列可以有更多的消费者来同时监听同一个队列,让消息处理的效率提高

剩下的三种模式我们之后再介绍,现在我们来重点讲讲这个工作消息队列的处理机制

  • 假如现在有一种情况,同一个队列绑定了两个消费者,但这两个消费者处理消息的能力不一样,第一个消费者处理能力强于第二个消费者,我们来看看会不会处理能力强的消费者会处理更多的消息
    /*** 发送50次消息,总共一秒钟发完* @throws InterruptedException*/@Testpublic void testMessageToSimpleQueue() throws InterruptedException {String queueName = "simplequeue";String message = "hello AMQP";for (int i = 0; i < 50; i++) {rabbitTemplate.convertAndSend(queueName,message);Thread.sleep(20);}}

消息发送方,一共发送50次消息,共1秒发送完

    @RabbitListener(queues = "simplequeue")public void listenSimpleQueue1(String msg){System.out.println("消费者1接收到消息了:" + msg + LocalDateTime.now());}@RabbitListener(queues = "simplequeue")public void listenSimpleQueue2(String msg) throws InterruptedException {System.err.println("消费者2接收到消息了:" + msg + LocalDateTime.now());Thread.sleep(100);}

 消息接收方,明显可以看到,接收者2的能力要差于接收者1

运行看看结果:

运行结果我没有截全,这里说一下,总共发送了50次消息,但是两个消费者是把50次消息平分成两部分,每人处理25条消息,但是由上面截图可以看到,消费者2处理速度明显要慢些

  • 这就奇了怪了,明明消费者2的能力不够,居然还是和消费者1处理一样的消息,这是为什么呢?
  • 其实是rabbitmq默认的消息预取机制造成的,有多个消息来时,Queue会把这些消息预先投递给两个消费者,先不管能不能处理,先一人一个把消息分完,然后再各自处理分到的消息,所以性能好的消费者早早就把消息处理完了,而能力差的消费者就要慢些

那么我们怎么控制这个消息预期机制呢?让能力强的消费者多处理一些消息

在配置中,有个prefetch属性,可以控制消息预取数量的上限, 刚刚在上面的例子中,没有设置,那么就默认预取数量上限是无限,来多少消息,我就拿多少,拿完再处理,导致能力强和能力弱的消费者都能分到一样的数量。

那么我们将这个值设置成1,每个消费者预取数量上限是1,表示每个消费者最多预取1条消息,处理完了,再预取下一条消息,这样处理消息数量的多少就完全看消费者的能力了


5.3.发布订阅模式

发布订阅模式

发布订阅模式引入了交换机的概念,它可以控制消息发送给指定的队列,框架模式图如下:

交换机只负责消息的路由,不负责消息的存储,意味着一旦消息从交换机中发送失败之后,消息就会丢失 

5.3.1.广播

广播(Fanout Exchange)

刚刚在上面说了,根据交换机的不同,发布订阅模式分成了三种,广播模式就是其中之一;其功能是,将消息发送给每一个与该交换机绑定的队列,可以理解成up主和粉丝的关系,up主一旦推出了新的视频,那么就会推送给每一个关注了该up主的粉丝 

该怎么实现?

之前说过,publisher只管发送消息,其他的不管,所以这里的交换机、队列的创建都在消费者中完成


创建一个配置类:

@Configuration
public class FanoutConfig {/*** 创建一个交换机* @return*/@Beanpublic FanoutExchange fanoutExchange(){//创建并指定交换机的名称return new FanoutExchange("itcast.fanout");}/*** 创建队列1* @return*/@Beanpublic Queue fanoutQueue1(){//创建并指定队列1的名称return new Queue("fanout.queue1");}/*** 创建队列2* @return*/@Beanpublic Queue fanoutQueue2(){//创建并指定队列2的名称return new Queue("fanout.queue2");}/*** 将队列1绑定到交换机上* @param fanoutQueue1* @param fanoutExchange* @return*/@Beanpublic Binding fanoutBinding1(Queue fanoutQueue1,FanoutExchange fanoutExchange){return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);}/*** 将队列2绑定到交换机上* @param fanoutQueue2* @param fanoutExchange* @return*/@Beanpublic Binding fanoutBinding2(Queue fanoutQueue2,FanoutExchange fanoutExchange){return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange);}
}

在配置类中,我们创建了两个队列,一个交换机,还进行了两个队列和交换机的绑定,其中调用了三个类,FanoutExchange、Queue、Binding,都将其注册到spring中,spring就会帮我们自动进行装配,当服务一启动之后,spring就会将这些带有Bean注解的方法创建成bean然后由spring统一管理,当spring容器启动之后,会自动创建这些bean的对象,还有一个点由Bean注解的方法,其创建的bean的id是方法名


创建监听容器,接收队列里的消息:

   //监听队列fanout.queue1@RabbitListener(queues = "fanout.queue1")public void listenFanoutQueue1(String msg) throws InterruptedException {System.out.println("消费者1接收到fanout.queue1的消息了:" + msg);}//监听队列fanout.queue2@RabbitListener(queues = "fanout.queue2")public void ListenFanoutQueue2(String msg){System.out.println("消费者2接收到fanout.queue2的消息了:" + msg);}

在publisher中发送消息:

  @Test//给交换机FanoutExchange发送消息public void testMessageToFanoutExchange(){String exchangeName = "itcast.fanout";String message = "你好,每一个消费者";//指定发送的交换机,key,消息内容rabbitTemplate.convertAndSend(exchangeName,"",message);}

在这里发送消息的方法里多了一个参数,需要注意,该参数是routingkey,只不过这里是null,在下一个模型中会讲到

测试: 


publish发送给交换机消息, 然后交换机将消息路由给每一个与之绑定的队列,之后comsumer再处理


5.3.2.路由

路由(Routing Exchange)

刚刚介绍的广播是将消息全部发送给与之绑定的队列,那么这个路由就是将消息发送给指定的队列,指定的准则就是刚刚在上面提到的routingkey 

该怎么实现呢?

方式一:

1.创建一个配置类,像5.3.1一样,在其中创建队列和交换机,并且进行绑定

@Configuration
public class DirectConfig {//创建交换机@Beanpublic DirectExchange directExchange(){return new DirectExchange("itcast.direct");}//创建队列queue1@Beanpublic Queue queue1(){return new Queue("direct.queue1");}//创建队列queue2@Beanpublic Queue queue2(){return new Queue("direct.queue2");}//将queue1绑定在交换机上@Beanpublic Binding directBinding1(DirectExchange directExchange,Queue queue1){return BindingBuilder.bind(queue1).to(directExchange).with("blue");}//将queue2绑定在交换机上public Binding directBinding2(DirectExchange directExchange,Queue queue2){return BindingBuilder.bind(queue2).to(directExchange).with("yellow");}
}

需要注意的是,在绑定队列和交换机的时候,要添加上RoutingKey


2.创建监听容器

   @RabbitListener(queues = "direct.queue1")public void ListenDirectMessage1(String msg){System.out.println("blue接收到了消息:"+msg);}@RabbitListener(queues = "direct.queue2")public void ListenDirectMessage2(String msg){System.out.println("yellow接收到了消息:"+msg);}

3.发送消息

@Test
public void testMessageToDirectExchange1(){String exchangeName = "itcast.direct";String message = "你好,被指定的消费者";rabbitTemplate.convertAndSend(exchangeName,"blue",message);
}

需要注意的是,在发送的时候也要指定RoutingKey,也就是指定交换机向哪一个队列发送消息

方式二:

说是方式二,其实也就是摒弃了在配置类中创建交换机、队列和绑定关系,换成了在创建监听容器的时候使用注解的方式

    @RabbitListener(bindings = @QueueBinding(value = @Queue(name = "direct.queue1"),exchange = @Exchange(name = "itcast.direct",type = ExchangeTypes.DIRECT), //创建交换机并指定类型key = {"blue","red"}                             //指定队列的key))public void ListenDirectQueue1(String msg){System.out.println("blue接收到消息了:" + msg);}@RabbitListener(bindings = @QueueBinding(value = @Queue(name = "direct.queue2"),exchange = @Exchange(name = "itcast.direct",type = ExchangeTypes.DIRECT),key = {"yellow","red"}))public void ListenDirectQueue2(String msg){System.out.println("yellow接受到消息了:" + msg);}

这样的方式看起来有点乱,但好在它在创建key的时候支持数组类型,可以一次性创建多个key

5.3.3.话题

话题(Topic Exchange)

TopicExchange与DirectExchange类似,区别在于RoutingKey,TopicExchange的RoutingKey必须必须是多个单词的列表,且以  .  隔开

比如: China.news     China.vagetables   等等

  • 这样把RoutingKey更加的细分,像是一个话题一样,因此得名TopicExchange
  • Queue与Exchange指定BindingKey时可以使用通配符
  1. #  代指0个或者多个单词
  2. *  代指一个单词
  • 比如:当我给两个Queue分别指定Key叫做*.news和China.*然后我在发送消息的时候,我指定Key是China.news那么就意味着,这两个Queue都要接收到我发送的消息,因为China.news对于上面两个Key都是符合的  

6.消息转换器

刚刚在上面的例子中,我们发送的消息都是String字符串类型的,但其实发送消息的方法参数是Object,也就是说它是支持发送对象的,那么我们来试一试

这次发送一个Map类型的消息,看看队列里接收到的消息是什么样的: 

 

我们可以看到,我们发送的消息是被springamqp底层的消息转换器给序列化了,想要具体了解的可以看看这个博主的文章:

RabbitMQ发送对象之消息序列化(必踩坑的一个点)_rabbitmq传输对象序列化-CSDN博客 

采用这种序列化工具,性能不太好,而且转换出来的字节太冗杂,所以这里我们可以换一种消息转换器

引入依赖: 

   <!--Json的序列化工具--><dependency><groupId>com.fasterxml.jackson.dataformat</groupId><artifactId>jackson-dataformat-xml</artifactId></dependency>

在配置类中创建这个序列化工具的实例并让spring托管:

    @Beanpublic Jackson2JsonMessageConverter jsonMessageConverter(){return new Jackson2JsonMessageConverter();}

将这个json格式转换器的对象让spring托管之后,在底层就会顶替掉原来默认的那种序列化工具,现在我们再来发送一次消息看看

可以看到发送的消息已经被转换成Json格式了,接收消息操作也是如此,导入一样的依赖,在配置类里将序列化工具托管给spring即可

相关文章:

MQ快速入门【详细】个人笔记 讲解通俗易懂

1.同步通讯和异步通讯 同步通讯&#xff1a;如果举个例子来说&#xff0c;同步通讯就像是两个人在打电话&#xff0c;一方说的话&#xff0c;能够立马传给另一方&#xff0c;消息的时效性非常高&#xff0c;但是相对的&#xff0c;只能是给一个人通讯&#xff0c;如果这个时候&…...

react实现实时计时的最简方式

js中时间的处理&#xff0c;不借助于moment/dayjs这样的工具库&#xff0c;原生获取格式化的时间&#xff0c;最简单的实现方式可以参考下面这样。 实现效果 代码实现 封装hooks import { useState, useEffect } from "react";export function useCountTime() {c…...

时尚的社会心理机制:求同和树异这对互为矛盾的心理动机,使得人们在社会生活中互相模仿、互相追逐、互相竞争,使得时尚的钟摆永不停息。

文章目录 引言I 时尚时尚的社会心理机制时尚的分类时尚的特点时尚的表现形式II 术语时装周服饰引言 时尚(fad)又称流行,它指在一定时期内社会上或一个群体中普遍流行的,并为大多数所仿效的生活方式或行为模式。 人的心理动机常常是互相矛盾的,既要求同于人,又要求异于人。…...

HarmonyOS NEXT应用开发实战(二、封装比UniApp和小程序更简单好用的网络库)

网络访问接口&#xff0c;使用频次最高。之前习惯了uniapp下的网络接口风格&#xff0c;使用起来贼简单方便。转战到鸿蒙上后&#xff0c;原始网络接口写着真累啊&#xff01;目标让鸿蒙上网络接口使用&#xff0c;简单程度比肩uniapp&#xff0c;比Axios更轻量级。源码量也不多…...

[Hbase]一 HBase基础

1. HBase简介 1.1 HBase定义 HBase数据模型的关键在于 稀疏、分布式、多维、排序 的映射。其中映射 map指代非关系型数据库的 key-Value结构。 1.2 HBase数据模型 1)Name Space 命名空间,类似于关系型数据库的database 概念,每个命名空间下有多个表。HBase 两个自…...

React.createRef(),React.forwardRef(),forwardRef()结合next.js的link进行路由跳转

码云https://gitee.com/skyvilm/react-next.js 1.React.createRef() 作用&#xff1a;获取dom元素 使用 import React,{Component} from react export default class Index extends Componen{ constructor(props){ super(props) this.myrefReact.createRef(); //创建节点 } c…...

C++从入门到起飞之——AVL树 全方位剖析!

&#x1f308;个人主页&#xff1a;秋风起&#xff0c;再归来~&#x1f525;系列专栏&#xff1a;C从入门到起飞 &#x1f516;克心守己&#xff0c;律己则安 目录 1. AVL的概念 2. AVL树的实现 2.1 AVL树的结构 2.2 AVL树的插⼊ >AVL树插⼊⼀个值的⼤概过程 &…...

利用Fail2Ban增强Jupyter Notebook安全性以防范目录遍历攻击

利用Fail2Ban增强Jupyter Notebook安全性以防范目录遍历攻击 书接上回[^参见]一、目录遍历攻击&#xff08;Directory Traversal Attack&#xff09;二、日志记录的网络攻击示例分析三、配置 Fail2ban四、fail2ban-regex测试和验证正则表达式五、重启 Fail2Ban六、验证配置生效…...

智能贴身监测,健康生活建议,圆道妙医智能手表体验

如今热衷于运动和健康生活的爱好者越来越多&#xff0c;相关的赛事等活动也是逐年增多&#xff0c;很多朋友为了能够直观的了解自己的健康状况&#xff0c;都会配备一款智能手表&#xff0c;这样戴在身上就可以随时了解自己的心率、血氧等数据。最近我尝试了一款圆道妙医推出的…...

C++——AVL树

文章目录 一、AVL树的概念二、AVL树的实现1. AVL树的结构2. AVL树的插⼊2.1 AVL树插⼊⼀个值的⼤概过程2.2 平衡因⼦更新更新原则更新停止条件 2.3 插⼊结点及更新平衡因⼦的代码实现 3. 旋转旋转的原则右单旋左单旋左右双旋右左双旋 4.高度5.结点个数6.判断是否是AVL树7. 中序…...

极市平台 | 无人机相关开源数据集资源汇总

本文来源公众号“极市平台”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;无人机相关开源数据集资源汇总 本文介绍几个无人机有关的开源数据集&#xff0c;内附下载链接。 UAV Delievery 无人机轨迹数据集 下载链接&#xff…...

React和Vue区别,以及注意事项

目录 一、语法和框架特性的差异 二、开发习惯和注意事项 三、特别注意事项 一、语法和框架特性的差异 模板语法&#xff1a; Vue使用了类似于传统HTML的模板语法&#xff0c;通过双大括号{{ }}进行插值&#xff0c;而React则使用了JSX语法。在Vue中&#xff0c;你可以直接在…...

光伏项目难管理的问题如何解决?

1.数字化管理平台的应用 数字化是当前解决光伏项目管理难题的关键手段之一。通过建立统一的数字化管理平台&#xff0c;可以实现对光伏电站的远程监控、数据分析、故障预警及运维调度等功能。这类平台通常集成有智能算法&#xff0c;能够实时分析电站运行数据&#xff0c;及时…...

图片美化SDK解决方案,赋能H5与小程序极致体验

无论是社交媒体分享、电商产品展示&#xff0c;还是个人日常生活的记录&#xff0c;一张经过精心美化的图片总能瞬间吸引眼球&#xff0c;传递出更加丰富和动人的信息。如何在不增加应用体积、不牺牲用户体验的前提下&#xff0c;为H5页面和小程序提供媲美原生APP的图片美化功能…...

Kron Reduction消去法如何操作,矩阵推导过程

三阶矩阵消去单节点 在电力系统中,母线上的电流注入始终为0,这样的节点可以通过一定的方法消除。以三节点为例,假设注入节点3的电流为0,则: [ I 1 I 2 I 3 ] = [ I 1 I 2 0 ] = [ Y 11 Y 12 Y 13 Y 21 Y 22 Y 23 Y 31 Y 32 Y 33 ] [ V 1 V 2 V 3 ] \left[\begin{array}{…...

实时开放词汇目标检测(论文复现)

实时开放词汇目标检测&#xff08;论文复现&#xff09; 本文所涉及所有资源均在传知代码平台可获取 文章目录 实时开放词汇目标检测&#xff08;论文复现&#xff09;概述模型框架使用方式配置环境训练和评估训练评估 演示效果Gradio Demo 概述 YOLO-World是由腾讯人工智能实验…...

陪诊小程序搭建:打造便利的陪诊环境

陪诊行业作为一个新兴行业&#xff0c;随着老龄化的严重&#xff0c;在近几年中需求量日益旺盛。陪诊师为大众的就医提供了极大的便利性&#xff0c;在看病难、医疗资源紧张方面发挥了积极作用。 在陪诊行业的快速发展下&#xff0c;陪诊小程序为行业带来了便捷的模式&#xf…...

Qt5.15.2静态编译 MinGW with static OpenSSL

如果想用VS2017编译,可参考:Qt5.15.2静态编译 VS2017 with static OpenSSL 一.环境 系统:Windows 10 专业版 64位 编译器:MinGW 8.1.0 第三方工具:perl,ruby和python PS:经验证,用MinGW 12.1.0来编译Qt5.15.2会报错 我用Phthon 2.7.18虽然可以编过,但是强烈建议Pyth…...

Linux Ubuntu dbus CAPI ---- #include<dbus.h>出现“无法打开源文件dbus/xxx.h“的问题

一、确保已安装dbus库和CAPI sudo apt-get install libdbus-1-dev 二、在c_cpp_properties.json的includePath中是否配置了dbus库依赖文件所在的路径 三、编译一个简单的dbus代码&#xff0c;在编译过程中只要出现.h文件找不到的情况&#xff0c;就使用下列命令找到.h文件路径…...

React01 开发环境搭建

React 开发环境搭建 一、创建 React 项目二、项目精简 一、创建 React 项目 执行下述命令创建 react 项目 blu-react-basis npx create-react-app blu-react-basis项目目录结构如下&#xff1a; 执行下述命令启动项目 npm run start启动效果如下&#xff1a; 二、项目精简 …...

数据结构之旅(顺序表)

前言: Hello,各位小伙伴们我们在过去的60天里学完了C语言基本语法,由于小编在准备数学竞赛,最近没有给大家更新,并且没有及时回复大家的私信,小编在这里和大家说一声对不起!,小编这几天会及时给大家更新初阶数据结构的内容,然后我们来学习今天的内容吧! 一. 顺序表的概念和结…...

掌握 C# 内存管理与垃圾回收机制

内存管理是每个开发者需要了解的关键部分&#xff0c;特别是在构建高性能应用时。在 C# 中&#xff0c;垃圾回收&#xff08;Garbage Collection, GC&#xff09; 机制自动管理内存分配和释放&#xff0c;大大简化了内存管理的复杂性。然而&#xff0c;理解值类型与引用类型的区…...

【JavaEE】——初始网络原理

阿华代码&#xff0c;不是逆风&#xff0c;就是我疯 你们的点赞收藏是我前进最大的动力&#xff01;&#xff01; 希望本文内容能够帮助到你&#xff01;&#xff01; 目录 一&#xff1a;局域网 1&#xff1a;概念 二&#xff1a;局域网的连接方式 1&#xff1a;网线直连 …...

Nginx和Lua配合使用

在NGINX中使用Lua进行开发时&#xff0c;可以通过不同的配置块来指定Lua脚本的执行位置。这些配置块被称为“phase hooks”&#xff0c;即阶段挂钩。每个阶段挂钩都有其特定的作用时间和目的。以下是NGINX Lua模块中常见的配置指令及其用途&#xff1a; 常见的Phase Hooks 1.a…...

程序化交易是什么,它有哪些优势,需要注意什么?

炒股自动化&#xff1a;申请官方API接口&#xff0c;散户也可以 python炒股自动化&#xff08;0&#xff09;&#xff0c;申请券商API接口 python炒股自动化&#xff08;1&#xff09;&#xff0c;量化交易接口区别 Python炒股自动化&#xff08;2&#xff09;&#xff1a;获取…...

水库抽样算法(大数据算法作业)

时隔一个多月&#xff0c;终于想起来写大数据算法基础的实验报告&#xff0c;主要是快截止了&#xff0c;hh 这两天加急把这个报告写完了~ 接下来&#xff0c;写一写证明过程&#xff08;参考书籍&#xff1a;高等教育出版社《数据科学与工程算法基础》&#xff09;主要代码以…...

SHCTF-2024-week1-wp

文章目录 SHCTF 2024 week1 wpMisc[Week1]真真假假?遮遮掩掩![Week1]拜师之旅①[Week1]Rasterizing Traffic[Week1]有WiFi干嘛不用呢&#xff1f; web[Week1] 单身十八年的手速[Week1] MD5 Master[Week1] ez_gittt[Week1] jvav[Week1] poppopop[Week1] 蛐蛐?蛐蛐! SHCTF 2024…...

docker-comapose安装部署mysql

docker-comapose安装部署mysql version: "3.4" services:mysql:image: docker.das-security.cn/middleware/mysql:8.4.1container_name: mysqlenvironment:- MYSQL_ROOT_PASSWORD密码volumes:- /etc/localtime:/etc/localtime- ./configs/mysql/initdb:/docker-entr…...

C语言初阶-数据类型和变量【下】

紧接上期------------------------->>>C语言初阶-数据类型和变量【上】 全局变量和局部变量在内存中存储在哪⾥呢&#xff1f; ⼀般我们在学习C/C语⾔的时候&#xff0c;我们会关注内存中的三个区域&#xff1a; 栈区 、 堆区 、 静态区 。 内存的分配情况 局部变量是…...

C++:命名空间(namespace)详细介绍与案例

命名空间&#xff08;namespace&#xff09;是C中的一个重要概念&#xff0c;用于组织代码和避免名称冲突。它们允许程序员将标识符&#xff08;如变量、函数、类等&#xff09;组织在一起&#xff0c;以便在较大的程序中防止命名冲突。 1. 基本概念 命名空间的基本定义方式如…...