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

服务异步通讯之 SpringAMQP【微服务】

文章目录

    • 一、初识 MQ
      • 1. 同步通讯
      • 2. 异步通讯
      • 3. MQ 常见框架
    • 二、RabbitMQ 入门
      • 1. 概述和安装
      • 2. 常见消息模型
      • 3. 基础模型练习
    • 三、SpringAMQP
      • 1. 简单队列模型
      • 2. 工作队列模型
      • 3. 发布订阅模型
        • 3.1 Fanout Exchange
        • 3.2 Direct Exchange
        • 3.3 Topic Exchange

一、初识 MQ

1. 同步通讯

同步和异步的区别:
① 同步通讯类似于打电话,是一对一的同时发生的通讯,因此它的时效性更好(一步走完才能走下一步);
② 异步通讯类似于发短信,给一个人发的同时还可以给别人发,支持多线操作,但是由于通讯不同步,所以它的时效性差(多步可以同时走)。

微服务间基于 Feign 的调用就属于同步调用,比如支付服务内部一般都会调用订单服务,这个时候它就必须要等待对方的回应才能进行后面的操作,显然这个过程是实时性的,但也存在一定的问题。

同步通讯劣势:
① 耦合度高。每次加入新的需求,都要修改原来的代码;
② 性能下降。调用者需要等待服务提供者的响应,响应时间等于每次调用的时间之和,如果调用链过长会直接导致性能下降;
③ 资源浪费。调用链中的每个服务在等待响应的过程中,不能释放所占用的资源。
A 服务调用 B 服务,在 B 未执行完之前,A 一直干等着,它占用的资源也不会释放;
④ 级联失败。如果服务提供者出现问题,所有的调用方也会跟着出现问题。 A 服务里面同时调用了 B 服务和 C 服务,B 服务出故障导致 A 所占用的资源一直得不到释放,一次、两次、无数次这样的请求最终导致 A 的资源被耗尽,而 A 服务里面还有 C,C 是正常的,但由于 A 的资源已被耗尽了,导致 A 调用 C 的业务时也会出现问题。

2. 异步通讯

异步调用常见的实现就是事件驱动模式。

异步调用引入一个 Broker 事件代理者,当服务消费者接收到前端请求后,就会发布一个事件给 Broker,然后由 Broker 广播,将事件通知给所有的提供者,让这些提供者各自开始行动。

同步通讯中消费者会在代码中依次调用提供者,而异步通讯中的消费者不再负责调用提供者,它只管发布事件到 Broker,由提供者自行订阅事件就可以了。

异步通讯优势:
① 解除了服务与服务之间的耦合。消费者不再负责调用提供者,只管发布事件到 Broker 就可以了, 所以一旦有新的业务出现,直接去订阅事件就可以了;
② 性能提升,吞吐量提高。消费者发布完事件后立马就会响应给客户,至于提供者什么时候运行完我们并不在意, 只要最终能做完就行了,所以业务的总耗时就为消费者的请求时间和事件的发布时间之和;
③ 不存在资源浪费。 什么是资源浪费?就是占着资源却不用。以前消费者会占着资源等待提供者运行完毕,现在服务之间已经彻底解耦了,又怎么会浪费;
④ 解决级联失败问题。服务之间没有强依赖,不必担心级联失败问题, 即使某一个提供者挂掉了,也不会影响消费者;
⑤ 流量削峰。 当大量的用户请求过来的时候,消费者会发布大量的事件给 Broker,因为提供者处理业务的能力有限,所以 Broker 在这里会对事件做一个缓冲,具体的提供者根据自己的能力处理业务,压力全给到 Broker 就可以了,最终对微服务起到一个保护的作用。

异步通讯劣势:
① 依赖于 Broker 的可靠性、安全性和吞吐能力;
② 架构复杂,业务没有明显的流程线,不好追踪管理。

异步通讯只是通知提供者去干,至于什么时候干完谁都不知道。实际上大多数情况下都会使用同步通讯,因为提供者返回的结果消费者立马要用,我们更追求的是时效性,而对并发并没有很高的要求!

3. MQ 常见框架

MQ 即消息队列,也就是事件驱动架构中的 Broker。

在这里插入图片描述

比较:
① RabbitMQ 支持多种协议,这意味着它支持的功能更多,其中 AMQP 可以实现跨语言通讯,而 RocketMQ 和 Kafka是自定义的协议;
② RabbitMQ、RocketMQ 和 Kafka 都支持主从集群,可用性较高;
③ Kafka的吞吐量非常高,但是高吞吐量带来的牺牲就是消息的延迟性和不可靠性,容易出现消息丢失的情况。

Kafka 适用于海量数据的传输,但是对数据的安全要求不高的情况(比如日志信息),RabbitMQ 和 RocketMQ 的稳定性更强,更适合对稳定性要求较高的场景(比如业务通信),如果要做自定义协议,我们可以选择 RocketMQ,因为 RockerMQ 是基于 Java 语言开发的,且支持自定义协议。

二、RabbitMQ 入门

1. 概述和安装

RabbitMQ 的整体结构如下:
在这里插入图片描述

消息的发布者将来会把消息发送到 exchange 交换机,交换机负责将消息路由到具体的 queue 队列,队列负责暂存消息,消息的接收者就会从队列中拿取并处理消息。每个用户会有自己的虚拟主机,各个虚拟主机之间是相互隔离的,这样可以避免干扰!

① 将本地的压缩文件上传至 Linux,并加载为一个镜像

在这里插入图片描述

在这里插入图片描述

② 创建并运行 MQ 容器,15672 是 RibbitMQ 控制台的端口,5672 是消息通讯的端口(发消息、收消息)

docker run \
-e RABBITMQ_DEFAULT_USER=zxe \
-e RABBITMQ_DEFAULT_PASS=856724bb \
--name mq \
--hostname mq1 \
-p 15672:15672 \
-p 5672:5672 \
-d \
rabbitmq:3-management

在这里插入图片描述

③ 浏览器访问一下 RabbitMQ 的控制台,根据刚才设置的用户名和密码登录

在这里插入图片描述

在这里插入图片描述

每个模块的含义:
① Overview(总览):主要可以看到 RabbitMQ 的一些节点信息;
② Connections(连接):将来消息的发布者和接收者都应该与 RabbitMQ 建立连接;
③ Channels(通道):是操作 MQ 的工具,建立连接之后必须创建一个 Channel 通道,提供者和消费者才能基于 Channel 完成消息的发送或接收;
④ Exchanges(交换机):消息的路由器,负责将消息路由到对面;
⑤ Queues(队列):用来做消息存储;
⑥ Admin(管理):管理当前用户信息;
⑦ virtual host(虚拟主机):是对 queue、exchange 等资源的逻辑分组。

2. 常见消息模型

RabbitMQ 官方文档:rabbitmq.com

查看官方文档可以看到官方给出的很多 Demo 案例,但是只有前五个和消息的接收发送有关。

其中,前两个是基于队列发送的,也就是说消息的发布者和订阅者之间直接通过队列连接,没有交换机。而后三个属于发布订阅模式,有交换机,根据交换机的类型分为广播、路由和主题三种。

在这里插入图片描述

在这里插入图片描述

3. 基础模型练习

官方的 HelloWorld 是基于最基础的消息队列模型来实现的,只包括三个角色:消息发布者、消息队列和订阅者。

① 发布消息

发布消息基本步骤:创建连接工厂 → 设置连接参数 → 建立连接 → 创建通道 → 创建队列 → 发送消息 → 关闭通道和连接。

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import org.junit.Test;import java.io.IOException;
import java.util.concurrent.TimeoutException;public class PublisherTest {@Testpublic void testSendMessage() throws IOException, TimeoutException {// 1.创建连接工厂ConnectionFactory factory = new ConnectionFactory();// 2.设置连接参数,分别是:主机名、端口号、vhost、用户名、密码factory.setHost("192.168.149.101");factory.setPort(5672);factory.setVirtualHost("/");factory.setUsername("zxe");factory.setPassword("123321");// 3.建立连接Connection connection = factory.newConnection();// 4.创建通道ChannelChannel channel = connection.createChannel();// 5.创建队列String queueName = "simple.queue";channel.queueDeclare(queueName, false, false, false, null);// 6.发送消息String message = "hello, rabbitmq!";channel.basicPublish("", queueName, null, message.getBytes());System.out.println("发送消息成功:【" + message + "】");// 7.关闭通道和连接channel.close();connection.close();}
}

在这里插入图片描述

在这里插入图片描述

② 订阅消息

订阅消息基本步骤:创建连接工厂 → 设置连接参数 → 建立连接 → 创建通道 → 创建队列 → 订阅消息 → 处理消息。

import com.rabbitmq.client.*;import java.io.IOException;
import java.util.concurrent.TimeoutException;public class ConsumerTest {public static void main(String[] args) throws IOException, TimeoutException {// 1.建立连接ConnectionFactory factory = new ConnectionFactory();// 1.1.设置连接参数,分别是:主机名、端口号、vhost、用户名、密码factory.setHost("192.168.149.101");factory.setPort(5672);factory.setVirtualHost("/");factory.setUsername("zxe");factory.setPassword("123321");// 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.处理消息,队列一有消息函数就会执行,body就是消息体String message = new String(body);System.out.println("接收到消息:【" + message + "】");}});System.out.println("等待接收消息。。。。");}
}

发布者已经创建了队列,订阅者为什么还要创建?这是因为它们的先后执行顺序是不确定的,以防万一订阅者在发布者之前执行,队列不存在的问题,其实最终也不会创建两个队列,而是有则不创建,没有就创建!

从 MQ 上接收消息也需要时间,所以函数外的代码先执行,函数内的代码后执行,这就是异步。

在这里插入图片描述

消息一旦被接收,就会立马从队列中消失。

在这里插入图片描述

三、SpringAMQP

1. 简单队列模型

上面案例中发布消息和订阅消息的步骤十分繁琐吧?接下来的 SpringAMQP 就是用来简化这些步骤的,它是一种模板协议,与语言和平台无关,利用 SpringAMQP 提供好的模板工具直接可以发送和接收消息,其实底层封装的还是 Rabbit 的客户端。

① 父工程中引入 AMQP 依赖

<!--AMQP依赖,包含RabbitMQ-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

② 编写 publisher 和 consumer 的 application.yml,添加连接信息

spring:rabbitmq:host: 192.168.150.101 #主机名port: 5672 #端口virtual-host: / #虚拟主机username: zxe #用户名password: 123321 #密码

③ SpringAMQP 方式并不会主动创建队列,需要我们提前去控制台创建 simple.queue 队列

在这里插入图片描述

④ 在 publisher 中编写测试方法,向 simple.queue 队列发送消息

主机名、端口等信息都写在配置文件里面了,代码中只需要传入队列名和所需发送的消息即可。

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringAmqpTest {@Autowiredprivate RabbitTemplate rabbitTemplate;@Testpublic void testSendMessage2SimpleQueue() {String queueName = "simple.queue";String message = "hello, spring amqp!";//发送消息rabbitTemplate.convertAndSend(queueName, message);}
}

⑤ 发布一个消息,去 RabbitMQ 控制台看一下

在这里插入图片描述

在这里插入图片描述

⑥ 在 consumer 服务中新建一个类,用于编写消费逻辑

主机名、端口等信息都写在配置文件里面了,代码中只需要指定监听的队列名和接下来的行为方法就可以了,Spring 会自动将监听到的消息以参数的形式传递给行为方法。

import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;@Component
public class SpringRabbitListener {@RabbitListener(queues = "simple.queue")public void listenSimpleQueue(String msg) {System.out.println("消息者已接收到simple.queue的消息:【" + msg + "】");}
}

⑦ 运行 Consumer 的启动类,同时去看一个 RabbitMQ 控制台,发现队列中的消息条数变成了 0,说明消息已经被成功消费了

在这里插入图片描述

在这里插入图片描述

2. 工作队列模型

以前我们发布者发送过来的消息都由一个消费者去处理,但是当发布者发送的消息很多时,一个消费者就可能处理不过来了,处理不过来的消息全部堆积在队列中,长期下去,队列肯定会爆满,最终造成数据丢失。

解决办法就是我们可以安排多个消费者,让它们共同处理队列中的消息。
消费者之间是一种合作的关系,一条消息如果被某一消费者接收了,它就会立马从队列中消失,所以不会出现重复消费的问题。

Work Queue(工作队列),可以提高消息的处理速度,避免队列消息堆积。

在这里插入图片描述

模拟 WorkQueue,基本思路如下:
① 在 publisher 服务中定义测试方法,每秒产生 50 条消息,发送到 simple.queue;
② 在 consumer 服务中定义两个消息监听者,都监听 simple.queue 队列;
③ 消费者 1 每秒处理 50 条消息,消费者 2 每秒处理 10 条消息。

① 编写 publish 代码,for 循环模拟发送 50 条消息,每发送一次我们让线程休眠 20 毫秒,这样发完 50 条消息就是 1 秒

@Test
public void testSendMessage2WorkQueue() throws InterruptedException {String queueName = "simple.queue";String message = "hello, message_";for (int i = 1; i <= 50; i++) {rabbitTemplate.convertAndSend(queueName, message + i);Thread.sleep(20);}
}

② 编写 consumer 代码,两个方法模拟两个消费者,同样设置休眠时间模拟消费者的处理速度

@RabbitListener(queues = "simple.queue")
public void listenWorkQueue1(String msg) throws InterruptedException {System.out.println("消费者1接收到消息:【" + msg + "】" + LocalTime.now());Thread.sleep(20);
}@RabbitListener(queues = "simple.queue")
public void listenWorkQueue2(String msg) throws InterruptedException {//err打印出红色字体,比较容易区分System.err.println("消费者2接收到消息:【" + msg + "】" + LocalTime.now());Thread.sleep(200);
}

③ 运行之后我们发现,消费者并没有在一秒内将消息处理完,且消费者1 只处理偶数消息,消费者2 只处理奇数消息,且消费者1 提前就处理完了,消费者2 晚了好久才把消息处理完

在这里插入图片描述

这是因为我们 RabbitMQ 的预取机制所导致的。当消息进入队列之后,我们的消费者并不是立马去消费这些消息,而是先根据轮询的方式把消息平均分配给所有的消费者,这就是预取。
它不管每个消费者的消费能力如何,一律平均分配,这就出现了消费者1 处理完毕,消费者2 还在继续的问题。

④ 所以接下来,可以去配置文件里设置消费预取限制,设置 preFetch 值可以控制预期消息的上限

spring:rabbitmq:host: 192.168.149.100 #主机名port: 5672 #端口virtual-host: / #虚拟主机username: zxe #用户名password: 856724bb #密码listener:simple:prefetch: 1 #每次只能获取一条消息,处理完成后才能获取下一个消息

⑤ 配置完之后重启服务,果然在一秒内完成消费

在这里插入图片描述

3. 发布订阅模型

发布订阅模式与之前案例的区别就是,它允许将同一消息发送给多个消费者(这里的多个消费者指的是多个不同的服务),实现方式是加入了 exchange 交换机。

发布者将消息发送给交换机,交换机负责将消息路由到不同的队列,即一个消息同时被多个消费者消费。

注意交换机只负责消息的路由,而不是存储,如果路由失败则消息丢失!

3.1 Fanout Exchange

Fanout Exchange 会将接收到的消息路由到每一个跟其绑定的 queue,因此称为广播模式。

一次发布,多个消费者都能接受。

在这里插入图片描述

① 声明队列、交换机,并将队列绑定到交换机上

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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class FanoutConfig {//声明交换机@Beanpublic FanoutExchange fanoutExchange() {return new FanoutExchange("zxe.fanout");}//声明队列@Beanpublic Queue fanoutQueue1() {return new Queue("fanout.queue1");}@Beanpublic Queue fanoutQueue2() {return new Queue("fanout.queue2");}//将队列绑定到交换机,参数为上面声明的方法名@Beanpublic Binding fanoutBinding1(Queue fanoutQueue1, FanoutExchange fanoutExchange) {return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);}@Beanpublic Binding fanoutBinding2(Queue fanoutQueue2, FanoutExchange fanoutExchange) {return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange);}
}

② 运行项目,测试以下

在这里插入图片描述

在这里插入图片描述

② 编写消费者监听代码,分别监听两个队列的消息

@RabbitListener(queues = "fanout.queue1")
public void listenFanoutQueue1(String msg) {System.out.println("消费者已接收到fanout.queue1的消息:【" + msg + "】");
}
@RabbitListener(queues = "fanout.queue2")
public void listenFanoutQueue(String msg) {System.out.println("消费者已接收到fanout.queue2的消息:【" + msg + "】");
}

③ 编写发布者代码,将消息直接发送到交换机

@Test
public void testSendFanoutExchange() {//交换机名称String exchangeName = "zxe.fanout";//消息String message = "hello, every one!";//发送消息rabbitTemplate.convertAndSend(exchangeName, "", message);
}

④ 启动项目,发布消息,并消费

在这里插入图片描述

步骤总结:
① 首先声明交换机和队列,并将队列绑定到交换机;
② 对于发布者,需要指定交换机名称和所要发布的消息;
③ 对于消费者,直接指定想要监听的队列名和行为方法即可。

3.2 Direct Exchange

Direct Exchange 会将接收到的消息根据规则路由到指定的 queue,因此称为路由模式。

原理:
① 每一个队列都会设置一个 BindingKey;
② 发布者发送消息时,会指定消息的 RoutingKey;
③ 交换机负责将消息路由到 BindingKey 和 RoutingKey 一致的队列。

BindingKey 和 RoutingKey 相当于对暗号,暗号相同则路由。

一个队列可以指定多个 BindingKey,如果多个队列的 BindingKey 都可以与 RoutingKey 匹配,那交换机就会将消息发送给多个队列。

① 用 RabbitListener 注解声明交换机、队列和 BindingKey

//用RabbitListener注解声明交换机、队列和 BindingKey
@RabbitListener(bindings = @QueueBinding(value = @Queue(name = "direct.queue1"),exchange = @Exchange("zxe.direct"),key = {"red", "blue"}
))
public void listenDirectQueue1(String msg) {System.out.println("消费者已接收到direct.queue1的消息:【" + msg + "】");
}
@RabbitListener(bindings = @QueueBinding(value = @Queue(name = "direct.queue2"),exchange = @Exchange("zxe.direct"),key = {"red", "yellow"}
))
public void listenDirectQueue2(String msg) {System.out.println("消费者已接收到direct.queue2的消息:【" + msg + "】");
}

在这里插入图片描述

② 编写发布者代码,指定交换机名,要发布的消息,以及 RoutingKey

在这里插入图片描述

③ 启动项目,可以看到队列1 接收到消息,因为它里面包含 blue

在这里插入图片描述

3.3 Topic Exchange

Topic Exchange 与 Direct Exchange 类似,区别在于 Topic Exchange 的 routingKey 必须是由多个单词组成的列表,并以 . 分隔,比如 china.news,china.weather,所以它又称为话题模式。

Queue 与 Exchange 指定 BindingKey 时可以使用通配符。
#:代指 0 个或多个单词
*:代指一个单词

比如中国相关的一切,可以这样表示:china.*
所有的新闻,可以这样表示:#.news

以前 bindingKey 需要绑定 news、weather、food 等多个 key,现在只需要一个 china.* 就搞定了,只要是 china 的我都要!

① 用 @RabbitListener 声明队列、交换机和 BindingKey,这里使用通配符来指定 key

@RabbitListener(bindings = @QueueBinding(value = @Queue(name = "topic.queue1"),exchange = @Exchange(name = "zxe.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 = "zxe.topic", type = ExchangeTypes.TOPIC),key = "#.news"))public void listenTopicQueue2(String msg) {System.out.println("消费者已接收到topic.queue2的消息:【" + msg + "】");}

② 编写发布者代码,指定 RoutingKey

@Testpublic void testSendTopicExchange() {String exchangeName = "zxe.topic";String message = "hello, china!";rabbitTemplate.convertAndSend(exchangeName, "china.weather", message);}

③ 运行一下

在这里插入图片描述

相关文章:

服务异步通讯之 SpringAMQP【微服务】

文章目录 一、初识 MQ1. 同步通讯2. 异步通讯3. MQ 常见框架 二、RabbitMQ 入门1. 概述和安装2. 常见消息模型3. 基础模型练习 三、SpringAMQP1. 简单队列模型2. 工作队列模型3. 发布订阅模型3.1 Fanout Exchange3.2 Direct Exchange3.3 Topic Exchange 一、初识 MQ 1. 同步通…...

LED闪烁

这段代码是用于STM32F10x系列微控制器的程序&#xff0c;主要目的是初始化GPIOA的Pin 0并使其按照特定的模式进行闪烁。下面是对这段代码的逐行解释&#xff1a; #include "stm32f10x.h"&#xff1a;这一行包含了STM32F10x系列微控制器的设备头文件。这个头文件包含…...

php array_diff 比较两个数组bug避坑 深入了解

今天实用array_diff出现的异常问题&#xff0c;预想的结果应该是返回 "integral_initiate">"0"&#xff0c;实际没有 先看测试代码&#xff1a; $a ["user_name">"测","see_num">0,"integral_initiate&quo…...

c++中STL的vector简单实现

文章目录 vector构造函数 vector()拷贝构造 vector()析构函数 ~vector()iterator 的定义begin()与const版本end()与const版本增删改查尾插push_back()尾删pop_back()指定位置插入insert()指定位置删除 erase() operator[]与const版本容量增容reserve()设置容量 resize() 成员函…...

C# 更改Bitmap图像色彩模式

方法一&#xff1a;直接修改RGB的值 首先将BitmapData扫描线上的所有像素复制到字节数组中&#xff0c;然后遍历数组并对每个像素的RGB值进行修改&#xff0c;最后将修改后的像素值复制回BitmapData。这个过程不会影响原始的Bitmap对象&#xff0c;但会改变锁定的位图区域的数…...

5.2 基于深度学习和先验状态的实时指纹室内定位

文献来源 Nabati M, Ghorashi S A. A real-time fingerprint-based indoor positioning using deep learning and preceding states[J]. Expert Systems with Applications, 2023, 213: 118889.&#xff08;5.2_基于指纹的实时室内定位&#xff0c;使用深度学习和前一状态&…...

AIGC时代高效阅读论文实操

大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于机器学习算法研究与应用。曾获得阿里云天池比赛第一名,CCF比赛第二名,科大讯飞比赛第三名。拥有多项发明专利。对机器学习和深度学习拥有自己独到的见解。曾经辅导过若干个非计算机专业的学生进入到算法…...

对网站进行打点(不要有主动扫描行为)

什么是打点&#xff1f; 简单来说就是获取一个演习方服务器的控制权限。 目的&#xff1a; 1. 上传一个一句话木马 2. 挖到命令执行 3. 挖到反序列化漏洞 4. 钓鱼 假设对“千峰”网站进行打点&#xff1a; 1. 利用平台 1. 利用各类平台&#xff1a; 天眼查-商业查询平…...

502. IPO(贪心算法+优先队列/堆)

整体思想&#xff1a;在满足可用资金的情况下&#xff0c;选择其中利润最大的业务&#xff0c;直到选到k个业务为止&#xff0c;注意k可能比n大。 每次选择完一个业务&#xff0c;可用资金都会变动&#xff0c;这是可选择的业务也会变化&#xff0c;因此每次将可选择的业务放在…...

设计模式篇---中介者模式

文章目录 概念结构实例总结 概念 中介者模式&#xff1a;用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显示地相互引用&#xff0c;从而使其耦合松散&#xff0c;而且可以独立地改变它们之间的交互。 就好比世界各个国家之间可能会产生冲突&#xff0c;但是当产…...

双端Diff算法

双端Diff算法 双端Diff算法指的是&#xff0c;在新旧两组子节点的四个端点之间分别进行比较&#xff0c;并试图找到可复用的节点。相比简单Diff算法&#xff0c;双端Diff算法的优势在于&#xff0c;对于同样的更新场景&#xff0c;执行的DOM移动操作次数更少。 简单 Diff 算法…...

react+antd,Table表头文字颜色设置

1、创建一个自定义的TableHeaderCell组件&#xff0c;并设置其样式为红色 const CustomTableHeaderCell ({ children }) > (<th style{{ color: "red" }}>{children}</th> ); 2、将CustomTableHeaderCell组件传递到Table组件的columns属性中的titl…...

2024年1月18日Arxiv最热NLP大模型论文:Large Language Models Are Neurosymbolic Reasoners

大语言模型化身符号逻辑大师&#xff0c;AAAI 2024见证文本游戏新纪元 引言&#xff1a;文本游戏中的符号推理挑战 在人工智能的众多应用场景中&#xff0c;符号推理能力的重要性不言而喻。符号推理涉及对符号和逻辑规则的理解与应用&#xff0c;这对于处理现实世界中的符号性…...

服务限流实现方案

服务限流怎么做 限流算法 计数器 每个单位时间能通过的请求数固定&#xff0c;超过阈值直接拒绝。 通过维护一个单位时间内的计数器&#xff0c;每次请求计数器加1&#xff0c;当单位时间内计数器累加到大于设定的阈值&#xff0c;则之后的请求都被绝&#xff0c;直到单位时…...

【RTOS】快速体验FreeRTOS所有常用API(1)工程创建

目录 一、工程创建1.1 新建工程1.2 配置RCC1.3 配置SYS1.4 配置外设1&#xff09;配置 LED PC132&#xff09;配置 串口 UART13&#xff09;配置 OLED I2C1 1.5 配置FreeRTOS1.6 工程设置1.7 生成代码1.8 keil设置下载&复位1.9 添加用户代码 快速体验FreeRTOS所有常用API&a…...

Red Hat Enterprise Linux 8.9 安装图解

风险告知 本人及本篇博文不为任何人及任何行为的任何风险承担责任&#xff0c;图解仅供参考&#xff0c;请悉知&#xff01;本次安装图解是在一个全新的演示环境下进行的&#xff0c;演示环境中没有任何有价值的数据&#xff0c;但这并不代表摆在你面前的环境也是如此。生产环境…...

vcruntime140.dll文件修复的几种常见解决办法,vcruntime140.dll丢失的原因

vcruntime140.dll文件是Windows操作系统中的一个重要动态链接库&#xff08;DLL&#xff09;文件&#xff0c;它是Microsoft Visual C Redistributable的一部分。当出现vcruntime140.dll文件丢失的情况时&#xff0c;可能会导致一些程序无法正常运行或出现错误提示。为了电脑能…...

SpringCloud Alibaba 深入源码 - Nacos 分级存储模型、支撑百万服务注册压力、解决并发读写问题(CopyOnWrite)

目录 一、SpringCloudAlibaba 源码分析 1.1、SpringCloud & SpringCloudAlibaba 常用组件 1.2、Nacos的服务注册表结构是怎样的&#xff1f; 1.2.1、Nacos的分级存储模型&#xff08;理论层&#xff09; 1.2.2、Nacos 源码启动&#xff08;准备工作&#xff09; 1.2.…...

算法训练营Day45

#Java #动态规划 Feeling and experiences&#xff1a; 最长公共子序列&#xff1a;力扣题目链接 给定两个字符串 text1 和 text2&#xff0c;返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 &#xff0c;返回 0 。 一个字符串的 子序列 是指这样一个新…...

【Redis漏洞利用总结】

前言 redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库&#xff0c;并提供多种语言的API。Redis默认使用 6379 端口。 一、redis未授权访问漏洞 0x01 漏洞描述 描述: Redis是一套开源的使用ANSI C编写、支持网络、可基于内存…...

SPI 动态服务发现机制

SPI&#xff08;Service Provier Interface&#xff09;是一种服务发现机制&#xff0c;通过ClassPath下的META—INF/services文件查找文件&#xff0c;自动加载文件中定义的类&#xff0c;再调用forName加载&#xff1b; spi可以很灵活的让接口和实现分离&#xff0c; 让API提…...

【C++进阶07】哈希表and哈希桶

一、哈希概念 顺序结构以及平衡树中 元素关键码与存储位置没有对应关系 因此查找一个元素 必须经过关键码的多次比较 顺序查找时间复杂度为O(N) 平衡树中为树的高度&#xff0c;即O( l o g 2 N log_2 N log2​N) 搜索效率 搜索过程中元素的比较次数 理想的搜索方法&#xff1a…...

Go 语言实现冒泡排序算法的简单示例

以下是使用 Go 语言实现冒泡排序算法的简单示例&#xff1a; package mainimport "fmt"func bubbleSort(arr []int) {n : len(arr)for i : 0; i < n-1; i {for j : 0; j < n-i-1; j {if arr[j] > arr[j1] {// 交换元素arr[j], arr[j1] arr[j1], arr[j]}}}…...

JAVA 学习 面试(五)IO篇

BIO是阻塞I/O&#xff0c;NIO是非阻塞I/O&#xff0c;AIO是异步I/O。BIO每个连接对应一个线程&#xff0c;NIO多个连接共享少量线程&#xff0c;AIO允许应用程序异步地处理多个操作。NIO&#xff0c;通过Selector&#xff0c;只需要一个线程便可以管理多个客户端连接&#xff0…...

vue3相比vue2的效率提升

1、静态提升 2、预字符串化 3、缓存事件处理函数 4、Block Tree 5、PatchFlag 一、静态提升 在vue3中的app.vue文件如下&#xff1a; 在服务器中&#xff0c;template中的内容会变异成render渲染函数。 最终编译后的文件&#xff1a; 1.静态节点优化 那么这里为什么是两部分…...

web terminal - 如何在mac os上运行gotty

gotty可以让你使用web terminal的方式与环境进行交互&#xff0c;实现终端效果 假设你已经配置好了go环境&#xff0c;首先使用go get github.com/yudai/gotty命令获取可执行文件&#xff0c;默认会安装在$GOPATH/bin这个目录下&#xff0c;注意如果你的go版本比较高&#xff…...

机械设计-哈工大课程学习-螺纹连接

圆柱螺纹主要几何参数螺纹参数 ①外径&#xff08;大径&#xff09;&#xff0c;与外螺纹牙顶或内螺纹牙底相重合的假想圆柱体直径。螺纹的公称直径即大径。 ②内径&#xff08;小径&#xff09;&#xff0c;与外螺纹牙底或内螺纹牙顶相重合的假想圆柱体直径。 ③中径&#xff…...

ai绘画|stable diffusion的发展史!简短易懂!!!

手把手教你入门绘图超强的AI绘画&#xff0c;用户只需要输入一段图片的文字描述&#xff0c;即可生成精美的绘画。给大家带来了全新保姆级教程资料包 &#xff08;文末可获取&#xff09; 一、stable diffusion的发展史 本文目标&#xff1a;学习交流 对于熟悉SD的同学&#x…...

水塘抽样算法

水塘抽样算法 1、问题描述 最近经常能看到面经中出现在大数据流中的随机抽样问题 即&#xff1a;当内存无法加载全部数据时&#xff0c;如何从包含未知大小的数据流中随机选取k个数据&#xff0c;并且要保证每个数据被抽取到的概率相等。 假设数据流含有N个数&#xff0c;我…...

easyui渲染隐藏域<input type=“hidden“ />为textbox可作为分割条使用

最近在修改前端代码的时候&#xff0c;偶然发现使用javascript代码渲染的方式将<input type"hidden" />渲染为textbox时&#xff0c;会显示一个神奇的效果&#xff0c;这个textbox输入框并不会隐藏&#xff0c;而是显示未一个细条&#xff0c;博主发现非常适合…...