RabbitMQ(七)ACK 消息确认机制
目录
当我们在项目中引入了新的中间件之后,数据的风险性就要多一层考虑。那么,RabbitMQ 的消息是怎么知道有没有被消费者消费的呢?生产者又怎么确保自己发送成功了呢?这些问题将在文章中进行解答。
一、简介
1.1 背景
在 MQ 中,消费者和生产者并不直接进行通信,生产者只负责把消息发送到队列,消费者只负责从队列获取消息。
- 消费者从队列 获取到消息后,这条消息就不在队列中了。如果此时消费者所在的信道 因为网络中断没有消费到,那这条消息就 被永远地丢失了。所以,我们希望等待消费者 成功消费掉这条消息之后再删除消息。
- 生产者向交换机 发送消息后,也 不能保证消息准确发送过去了,消息就像 石沉大海 一样,所以 发送消息也需要进行消息确认。
1.2 定义
为了保证消息从队列可靠地到达消费者,RabbitMQ 提供了 消息确认机制(Message Acknowledgement)。
消费者在订阅队列时,可以指定 autoAck
参数:
autoAck=false
:RabbitMQ 会 等待消费者显式地回复确认信号 后才从内存(或磁盘)中移除消息(实际上时先打上删除标记,之后再删除)。autoAck=true
:RabbitMQ 会 自动把发送出去的消息置为确认,然后内存(或磁盘)中删除,而 不管消费者是否真正地消费到了这些消息。
采用消息确认机制后,只要设置 autoAck
参数为 false
,消费者就有足够的时间处理消息(任务),不用担心处理消息过程中消费者进程挂掉后消息丢失的问题,因为 RabbitMQ 会一直等待持有消息知道消费者显式调用 Basic.Ack
命令为止。
对于 RabbitMQ 服务器端而言,当 autoAck
参数为 false
时,队列中的消息分成了两部分:
- 一部分是 等待投递给消费者的消息;
- 另一部分是 已经投递给消费者,但是还没有收到消费者确认信号的消息。
如果 RabbitMQ 服务器端 一直没有收到消费者的确认信息,并且 消费此消息的消费者已经断开连接,则服务器端会安排 该消息重新进入队列,等待投递给下一个消费者(也可能还是原来的那个消费者)。
RabbitMQ 不会为未确认的消息设置过期时间,它 判断此消息是否需要重新投递给消费者的唯一依据是该消息连接是否已经断开,这个设计的原因是 RabbitMQ 允许消费者消费一条消息的时间可以很久很久。
1.3 如何查看确认/未确认的消息数?
RabbitMQ 的 Web 管理平台上可以看到当前队列中的 “Ready” 状态和 “Unacknowledged” 状态的消息数:
- Read 状态: 等待投递给消费者的消息数。
- Unacknowledged 状态: 已经投递给消费者但是未收到确认信号的消息树。
二、消息确认机制的分类
RabbitMQ 消息确认机制分为两大类:
消息发送确认
,又分为:- 生产者到交换机的确认;
- 交换机到队列的确认。
消息接收确认
。
2.1 消息发送确认
RabbitMQ 的消息发送确认有两种实现方式:ConfirmCallback 方法、ReturnCallback 方法。
1)ConfirmCallback方法
ConfirmCallback
是一个回调接口,用于确认消息否是到达交换机中。
配置方式:
spring.rabbitmq.publisher-confirm-type=correlated
它有三个值:
none
:禁用发布确认模式,默认值。correlated
:发布消息成功到交换机后触发回调方法。simple
:经测试有两种效果:一是和 correlated 一样会触发回调方法;二是在发布消息成功后使用 rabbitTemplate 调用 waitForConfirm 或 waitForConfirmsOrDie方法等待 broker 节点返回发送结果,根据返回结果来判定下一步的逻辑。要注意的是 waitForConfirmsOrDie 方法如果返回 false 则会关闭 channel,则接下来无法发送消息到 broker。
2)ReturnCallback方法
ReturnCallback
也是一个回调接口,用于确认消息是否在交换机中路由到了队列。
(该方法可以不使用,因为交换机和队列是在代码里面绑定的,如果消息成功投递到 Broker 后几乎不存在绑定队列失败,除非代码写错了。)
配置方式:
spring.rabbitmq.publisher-returns=true
3)代码实现方式一:统一配置
a.配置类
RabbitDirectConfig.java
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.RabbitListenerContainerFactory;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** <p> @Title RabbitDirectConfig* <p> @Description 直连交换机配置* Direct Exchange是RabbitMQ默认的交换机模式,也是最简单的模式,根据key全文匹配去寻找队列。** @author ACGkaka* @date 2023/1/12 15:09*/
@Slf4j
@Configuration
public class RabbitDirectConfig {public static final String DIRECT_EXCHANGE_NAME = "TEST_DIRECT_EXCHANGE";public static final String DIRECT_ROUTING_NAME = "TEST_DIRECT_ROUTING";public static final String DIRECT_QUEUE_NAME = "TEST_DIRECT_QUEUE";@Beanpublic RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {RabbitTemplate rabbitTemplate = new RabbitTemplate();rabbitTemplate.setConnectionFactory(connectionFactory);// 设置开启Mandatory,才能触发回调函数,无论消息推送结果怎么样都强制调用回调函数rabbitTemplate.setMandatory(true);//设置message序列化方法rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());// 设置消息发送到交换机(Exchange)回调rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {if (ack) {log.info(">>>>>>>>>>【INFO】消息发送到交换机(Exchange)成功, 相关数据: {}", correlationData);} else {log.error(">>>>>>>>>>【ERROR】消息发送到交换机(Exchange)失败, 错误原因: {}, 相关数据: {}", cause, correlationData);}});// 设置消息发送到队列(Queue)回调(经测试,只有失败才会调用)rabbitTemplate.setReturnsCallback((returnedMessage) -> {log.error(">>>>>>>>>>【ERROR】消息发送到队列(Queue)失败:响应码: {}, 响应信息: {}, 交换机: {}, 路由键: {}, 消息内容: {}",returnedMessage.getReplyCode(), returnedMessage.getReplyText(), returnedMessage.getExchange(), returnedMessage.getRoutingKey(), returnedMessage.getMessage());});return rabbitTemplate;}/*** 消息监听-反序列化*/@Beanpublic RabbitListenerContainerFactory<?> rabbitListenerContainerFactory(ConnectionFactory connectionFactory) {SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();factory.setConnectionFactory(connectionFactory);factory.setMessageConverter(new Jackson2JsonMessageConverter());return factory;}/*** 队列,命名:testDirectQueue** @return 队列*/@Beanpublic Queue testDirectQueue() {// durable:是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效// exclusive:默认false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于durable。// autoDelete:是否自动删除,当没有生产者或消费者使用此队列,该队列会自动删除。// 一般设置一下队列的持久化就好,其余两个默认falsereturn new Queue(DIRECT_QUEUE_NAME, true);}/*** Direct交换机,命名:testDirectExchange* @return Direct交换机*/@BeanDirectExchange testDirectExchange() {return new DirectExchange(DIRECT_EXCHANGE_NAME, true, false);}/*** 绑定 将队列和交换机绑定,并设置用于匹配键:testDirectRouting* @return 绑定*/@BeanBinding bindingDirect() {return BindingBuilder.bind(testDirectQueue()).to(testDirectExchange()).with(DIRECT_ROUTING_NAME);}
}
a.生产者
SendMessageController.java
import com.demo.config.RabbitDirectConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;/*** <p> @Title SendMessageController* <p> @Description 推送消息接口** @author ACGkaka* @date 2023/1/12 15:23*/
@Slf4j
@RestController
public class SendMessageController {/*** 使用 RabbitTemplate,这提供了接收/发送等方法。*/@Autowiredprivate RabbitTemplate rabbitTemplate;@GetMapping("/sendDirectMessage")public String sendDirectMessage() {String messageId = String.valueOf(UUID.randomUUID());String messageData = "Hello world.";String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));Map<String, Object> map = new HashMap<>();map.put("messageId", messageId);map.put("messageData", messageData);map.put("createTime", createTime);// 将消息携带绑定键值:TEST_DIRECT_ROUTING,发送到交换机:TEST_DIRECT_EXCHANGErabbitTemplate.convertAndSend(RabbitDirectConfig.DIRECT_EXCHANGE_NAME, RabbitDirectConfig.DIRECT_ROUTING_NAME, map);return "OK";}}
c.消费者
DirectReceiver.java
import com.demo.config.RabbitDirectConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;import java.util.Map;/*** <p> @Title DirectReceiver* <p> @Description 直连交换机监听类** @author ACGkaka* @date 2023/1/12 15:59*/
@Slf4j
@Component
public class DirectReceiver {@RabbitListener(queues = RabbitDirectConfig.DIRECT_QUEUE_NAME)public void process(Map<String, Object> testMessage) {System.out.println("DirectReceiver消费者收到消息:" + testMessage.toString());}}
d.测试结果
成功发送时,执行结果:
交换机错误时,执行结果:
路由键错误时,执行结果:
4)代码实现方式二:单独配置
除了在配置类里面统一设置回调方法外,还可以在每次推送消息到队列时,手动使用 CorrelationData
指定回调方法。
@GetMapping("/sendDirectMessage2")
public String sendDirectMessage2() {String messageId = String.valueOf(UUID.randomUUID());String messageData = "Hello world.";String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));Map<String, Object> map = new HashMap<>();map.put("messageId", messageId);map.put("messageData", messageData);map.put("createTime", createTime);//生成唯一标识CorrelationData correlationData = new CorrelationData(messageId);//不管成功失败都会调用confirm或者throwable,这是异步调用correlationData.getFuture().addCallback(confirm -> {// 设置消息发送到交换机(Exchange)回调if (confirm != null && confirm.isAck()) {log.info(">>>>>>>>>>【INFO】发送成功ACK,msgId: {}, message: {}", correlationData.getId(), map);} else {log.error(">>>>>>>>>>【ERROR】发送失败NACK,msgId: {}, message: {}", correlationData.getId(), map);}},throwable -> {//发生错误,链接mq异常,mq未打开等...报错回调System.out.println("发送失败throwable = " + throwable + ", id:" + correlationData.getId());});// 将消息携带绑定键值:TEST_DIRECT_ROUTING,发送到交换机:TEST_DIRECT_EXCHANGErabbitTemplate.convertAndSend(RabbitDirectConfig.DIRECT_EXCHANGE_NAME, RabbitDirectConfig.DIRECT_ROUTING_NAME, map, correlationData);return "OK";
}
2.2 消息接收确认
消费者确认发生在 监听队列的消费者处理业务失败,如:发生了异常、不符合要求的数据等。这些场景就 需要我们手动处理消息,比如:重新发送消息或者丢弃消息。
RabbitMQ 的 消息确认机制(ACK)
默认是自动确认的。自动确认会 在消息发送给消费者后立即确认,但 存在丢失消息的可能。如果消费端消费逻辑抛出了异常,假如我们使用了事务的回滚,也只是保证了数据的一致性,消息还是丢失了。也就是消费端没有处理成功这条消息,那么就相当于丢失了消息。
消息的确认模式有三种:
AcknowledgeMode.NONE
:自动确认。(默认)AcknowledgeMode.AUTO
:根据情况确认。AcknowledgeMode.MANUAL
:手动确认。(推荐)
消费者收到消息后,手动调用 Channel 的 basicAck()
/basicReject()
/basicNack()
方法后,RabbitMQ 收到消息后,才认为本次投递完成。
basicAck()
:用于确认当前消息。basicReject()
:用于拒绝当前消息,可以自定义是否重回队列。basicNack()
:用于批量拒绝消息(这是 AMPQ 0-9-1 的 RabbitMQ 扩展)。
1)basicAck() 方法
basicAck()
方法 用于确认当前消息,Channel 类中的方法定义如下:
void basicAck(long deliveryTag, boolean multiple) throws IOException;
参数说明:
- long deliveryTag: 当一个消费者向 RabbitMQ 注册后,会建立起一个 Channel。RabbitMQ 会用
basic.deliver
方法向消费者推送消息,这个方法携带了一个deliveryTag
,它代表了 RabbitMQ 向该 Channel 投递的这条消息的唯一标识ID,是一个单调递增的正整数,deliveryTag
的范围仅限于当前 Channel。 - boolean multiple: 是否批处理,一般为 false,当该参数为 true 时,则可以一次性确认
deliveryTag
小于等于传入值的所有消息。
2)basicReject() 方法
basicReject()
方法 用于明确拒绝当前的消息。RabbitMQ 在 2.0.0 版本开始引入,Channel 类中的方法定义如下:
void basicReject(long deliveryTag, boolean requeue) throws IOException;
参数说明:
- long deliveryTag: 当一个消费者向 RabbitMQ 注册后,会建立起一个 Channel。RabbitMQ 会用
basic.deliver
方法向消费者推送消息,这个方法携带了一个deliveryTag
,它代表了 RabbitMQ 向该 Channel 投递的这条消息的唯一标识ID,是一个单调递增的正整数,deliveryTag
的范围仅限于当前 Channel。 - boolean requeue: 是否重新放回队列。
- 如果参数为 true,则 RabbitMQ 会重新将这条消息存入队列,以便发送给下一个订阅的消费者。
- 如果参数为 false,则 RabbitMQ 会立即把消息从队列中移除,不会把它发送给新的消费者。
3)basicNack() 方法
basicNack()
方法 用于批量拒绝消息。由于 basicReject() 方法一次只能拒绝一条消息,如果想批量拒绝消息,则可以使用 basicNack() 方法。Channel 类中的方法定义如下:
参数说明:
- long deliveryTag: 当一个消费者向 RabbitMQ 注册后,会建立起一个 Channel。RabbitMQ 会用
basic.deliver
方法向消费者推送消息,这个方法携带了一个deliveryTag
,它代表了 RabbitMQ 向该 Channel 投递的这条消息的唯一标识ID,是一个单调递增的正整数,deliveryTag
的范围仅限于当前 Channel。 - boolean multiple: 是否批处理,一般为 false,当该参数为 true 时,则可以一次性确认
deliveryTag
小于等于传入值的所有消息。 - boolean requeue: 是否重新放回队列。
- 如果参数为 true,则 RabbitMQ 会重新将这条消息存入队列,以便发送给下一个订阅的消费者。
- 如果参数为 false,则 RabbitMQ 会立即把消息从队列中移除,不会把它发送给新的消费者。
4)代码实现
a.配置方式一:代码配置
如果我们之前配置了 Jackson2JsonMessageConverter.java
的序列化方式,那么我们可以接着指定消费方的消息确认模式为 AcknowledgeMode.MANUL
。
/*** 消息监听配置*/
@Bean
public RabbitListenerContainerFactory<?> rabbitListenerContainerFactory(ConnectionFactory connectionFactory) {SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();// 设置连接工厂factory.setConnectionFactory(connectionFactory);// 设置消息确认模式factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);// 设置反序列化factory.setMessageConverter(new Jackson2JsonMessageConverter());return factory;
}
b.配置方式二:配置文件
我们可以直接在 application.yml
中进行如下配置:
# 确认模式,默认auto,自动确认;manual:手动确认
spring.rabbitmq.listener.simple.acknowledge-mode=manual
注意: yaml中指定的是消费端容器的默认配置,如果我们在代码中有自定义注入
RabbitListenerContainerFactory
示例之后,还需要使用默认配置,需要在代码中进行设置,如下所示:
@Autowired
private SimpleRabbitListenerContainerFactoryConfigurer configurer;/*** 消息监听配置*/
@Bean
public RabbitListenerContainerFactory<?> rabbitListenerContainerFactory(ConnectionFactory connectionFactory) {SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();// 设置连接工厂factory.setConnectionFactory(connectionFactory);// 采用yaml中的配置configurer.configure(factory, connectionFactory);// 设置反序列化factory.setMessageConverter(new Jackson2JsonMessageConverter());return factory;
}
c.生产者
SendMessageController.java
import com.demo.config.RabbitDirectConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;/*** <p> @Title SendMessageController* <p> @Description 推送消息接口** @author ACGkaka* @date 2023/1/12 15:23*/
@Slf4j
@RestController
public class SendMessageController {/*** 使用 RabbitTemplate,这提供了接收/发送等方法。*/@Autowiredprivate RabbitTemplate rabbitTemplate;@GetMapping("/sendDirectMessage")public String sendDirectMessage() {String messageId = String.valueOf(UUID.randomUUID());String messageData = "Hello world.";String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));Map<String, Object> map = new HashMap<>();map.put("messageId", messageId);map.put("messageData", messageData);map.put("createTime", createTime);// 将消息携带绑定键值:TEST_DIRECT_ROUTING,发送到交换机:TEST_DIRECT_EXCHANGErabbitTemplate.convertAndSend(RabbitDirectConfig.DIRECT_EXCHANGE_NAME, RabbitDirectConfig.DIRECT_ROUTING_NAME, map);return "OK";}}
d.消费者
DirectReceiver.java
import com.demo.config.RabbitDirectConfig;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;import java.io.IOException;
import java.util.Map;/*** <p> @Title DirectReceiver* <p> @Description 直连交换机监听类** @author ACGkaka* @date 2023/1/12 15:59*/
@Slf4j
@Component
public class DirectReceiver {@RabbitListener(queues = RabbitDirectConfig.DIRECT_QUEUE_NAME)public void process(Map<String, Object> testMessage, Message message, Channel channel) throws IOException {try {log.info("DirectReceiver消费者收到消息: {}", testMessage.toString());// 手动答应消费完成,从队列中删除该消息channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);} catch (Exception e) {log.error("DirectReceiver消费者消费失败,原因: {}", e.getMessage(), e);// 手动答应消费完成,从队列中删除该消息(不重回队列)channel.basicReject(message.getMessageProperties().getDeliveryTag(),false);}}}
e.测试结果
场景一:消费者进行手动确认,生产者推送2条消息:
可以看到,生产者推送2条消息后立马被消费了。
场景二:消费者不进行手动确认,生产者推送2条消息:
虽然消费者消费完毕,但是由于没有进行手动确认,所以2条消息会一直处于 Unacked
状态,直到消费者下线。
关闭 SpringBoot 程序,消费者下线后,消息由 Unacked
状态转为 Ready
状态,等待下一个消费者上线后重新进行消费。
整理完毕,完结撒花~ 🌻
参考地址:
1.RabbitMQ(4):消息确认机制详解,https://juejin.cn/post/7029232312197840904
2.RabbitMQ消息确认机制(ACK),https://blog.csdn.net/pan_junbiao/article/details/112956537
3.RabbitMQ高级,https://blog.csdn.net/hnhroot/article/details/125921527
4.关于rabbitMQ在yml配置手动ack不生效,重复答应的问题,https://blog.csdn.net/love_Saber_Archer/article/details/109111088
相关文章:
RabbitMQ(七)ACK 消息确认机制
目录 一、简介1.1 背景1.2 定义1.3 如何查看确认/未确认的消息数? 二、消息确认机制的分类2.1 消息发送确认1)ConfirmCallback方法2)ReturnCallback方法3)代码实现方式一:统一配置a.配置类a.生产者c.消费者d.测试结果 …...
ubuntu 编译内核报错
Ubuntu 编译 Linux 内核经常会遇到如下错误: 如果报错 canonical-certs.pem: 如下: make[1]: *** No rule to make target ‘debian/canonical-certs.pem’, needed by ‘certs/x509_certificate_list’. Stop. make: *** [Makefile:1868: …...
Python之自然语言处理库snowNLP
一、介绍 SnowNLP是一个python写的类库,可以方便的处理中文文本内容,是受到了TextBlob的启发而写的,由于现在大部分的自然语言处理库基本都是针对英文的,于是写了一个方便处理中文的类库,并且和TextBlob不同的是&…...
C# 语法进阶 委托
1.委托 委托是一个引用类型,其实他是一个类,保存方法的指针 (指针:保存一个变量的地址)他指向一个方法,当我们调用委托的时候这个方法就立即被执行 关键字:delegate 运行结果: 思…...
开源可观测性平台Signoz(四)【链路监控及数据库中间件监控篇】
转载说明:如果您喜欢这篇文章并打算转载它,请私信作者取得授权。感谢您喜爱本文,请文明转载,谢谢。 前文链接: 开源可观测性平台Signoz系列(一)【开篇】 开源可观测性平台Signoz&…...
【嵌入式开发 Linux 常用命令系列 4.2 -- git .gitignore 使用详细介绍】
文章目录 .gitignore 使用详细介绍.gitignore 文件的位置.gitignore 语法规则使用示例注意事项 .gitignore 使用详细介绍 .gitignore 文件是一个特殊的文本文件,它告诉 Git 哪些文件或目录是可以被忽略的,即不应该被纳入版本控制系统。这主要用于避免一…...
【熔断限流组件resilience4j和hystrix】
文章目录 🔊博主介绍🥤本文内容起因resilience4j落地实现pom.xml依赖application.yml配置接口使用 hystrix 落地实现pom.xml依赖启动类上添加注解接口上使用 📢文章总结📥博主目标 🔊博主介绍 🌟我是廖志伟…...
微服务雪崩问题及解决方案
雪崩问题 微服务中,服务间调用关系错综复杂,一个微服务往往依赖于多个其它微服务。 微服务之间相互调用,因为调用链中的一个服务故障,引起整个链路都无法访问的情况。 如果服务提供者A发生了故障,当前的应用的部分业务…...
008、所有权
所有权可以说是Rust中最为独特的一个功能了。正是所有权概念和相关工具的引入,Rust才能够在没有垃圾回收机制的前提下保障内存安全。 因此,正确地了解所有权概念及其在Rust中的实现方式,对于所有Rust开发者来讲都是十分重要的。在本文中&…...
千里马2023年终总结-android framework实战
背景: hi粉丝朋友们: 2023年马上就过去了,很多学员朋友也都希望马哥这边写个年终总结,因为这几个月时间都忙于新课程halsystracesurfaceflinger专题的开发,差点都忘记了这个事情了,今天特别花时间来写个bl…...
vue3中pinia的使用及持久化(详细解释)
解释一下pinia: Pinia是一个基于Vue3的状态管理库,它提供了类似Vuex的功能,但是更加轻量化和简单易用。Pinia的核心思想是将所有状态存储在单个store中,并且将store的行为和数据暴露为可响应的API,从而实现数据&#…...
安装 yarn、pnpm、功能比较
安装 yarn 官网:https://classic.yarnpkg.com/ 快速、可靠和安全的依赖性管理。 Yarn是您代码的软件包管理器。它允许您使用和共享(例如JavaScript)与来自世界各地的其他开发人员一起编写代码。Yarn是一个新的快速安全可信赖的可以替代 NP…...
计算机专业个人简历范文(8篇)
HR浏览一份简历也就25秒左右,如果你连「好简历」都没有,怎么能找到好工作呢? 如果你不懂得如何在简历上展示自己,或者觉得怎么改简历都不出彩,那请你一定仔细读完。 互联网运营个人简历范文> 男 22 本科 AI简历…...
几个实用网站
论文短语:https://www.phrasebank.manchester.ac.uk/ 翻译:https://www.deepl.com/en/translator 润色:https://quillbot.com/ 榜单:www.paperwithcode.com ****NLP民工的乐园: 几乎最全的中文NLP资源库:****https…...
Pycharm 切换interpreter---python的环境和第三方库问题
这篇回答两个问题: 1.为什么在 pycharm中打开新的project,切换interpreter 之后发现自己之前装的库消失了? 2.为什么 interpreter 切换到python3.8了, terminal 还是在 3.9?? 问题的关键:搞懂什…...
TP-LINK 路由器忘记密码 - 恢复出厂设置
TP-LINK 路由器忘记密码 - 恢复出厂设置 1. 恢复出厂设置2. 创建管理员密码3. 上网设置4. 无线设置5. TP-LINK ID6. 网络状态References 1. 恢复出厂设置 在设备通电的情况下,按住路由器背面的 Reset 按钮直到所有指示灯同时亮起后松开。 2. 创建管理员密码 3. 上网…...
关闭 Elasticsearch 集群的安全性设置
关闭 Elasticsearch 集群的安全性设置,特别是如果您正在使用 X-Pack,涉及到修改 Elasticsearch 的配置。以下是一般步骤,但请注意,这可能会使您的 Elasticsearch 集群面临安全风险,因此建议仅在开发或测试环境中执行此…...
[技术分享]一招解决 MySQL 中 DDL 被阻塞的问题
爱可生开源社区. 爱可生开源社区,提供稳定的MySQL企业级开源工具及服务,每年1024开源一款优良组件,并持续运营维护。 背景 之前碰到客户咨询定位DDL阻塞的相关问题,整理了一下方法,如何解决DDL被阻塞的问题。下面,就这个问题,整理了一下思路: 怎么判断一个 DDL 是…...
Windows搭建Emby媒体库服务器,无公网IP远程访问本地影音文件
文章目录 1.前言2. Emby网站搭建2.1. Emby下载和安装2.2 Emby网页测试 3. 本地网页发布3.1 注册并安装cpolar内网穿透3.2 Cpolar云端设置3.3 Cpolar内网穿透本地设置 4.公网访问测试5.结语 1.前言 在现代五花八门的网络应用场景中,观看视频绝对是主力应用场景之一&…...
自动化测试系列 之 Python单元测试框架unittest
一、概述 什么是单元测试 单元测试是一种软件测试方法,是测试最小的可测试单元,通常是一个函数或一个方法。 在软件开发过程中,单元测试作为一项重要的测试方法被广泛应用。 为什么需要单元测试 单元测试是软件开发中重要的一环…...
C语言朴素算法
#include <stdio.h> #include <string.h>// 朴素算法,用于字符串匹配 void naiveMatch(char* text, char* pattern) {int textLength strlen(text); // 计算文本串长度int patternLength strlen(pattern); // 计算模式串长度for …...
【力扣题解】P501-二叉搜索树中的众数-Java题解
👨💻博客主页:花无缺 欢迎 点赞👍 收藏⭐ 留言📝 加关注✅! 本文由 花无缺 原创 收录于专栏 【力扣题解】 文章目录 【力扣题解】P501-二叉搜索树中的众数-Java题解🌏题目描述💡题解…...
Wnmp本地部署结合内网穿透实现任意浏览器远程访问本地服务
最近,我发现了一个超级强大的人工智能学习网站。它以通俗易懂的方式呈现复杂的概念,而且内容风趣幽默。我觉得它对大家可能会有所帮助,所以我在此分享。点击这里跳转到网站。 文章目录 前言1.Wnmp下载安装2.Wnmp设置3.安装cpolar内网穿透3.1…...
深信服AF防火墙配置SSL VPN
防火墙版本:8.0.85 需提前确认防火墙是是否有SSL VPN的授权,确认授权用户数量 1、确认内外网接口划分 2、网络→SSL VPN,选择内外网接口地址 3、SSL VPN→用户管理→新增一个SSL VPN的用户 4、新增L3VPN资源,类型选择Other&…...
在Spring Cloud中使用Gateway 网关
我们在上述文章中介绍了相关Spring Cloud的五大核心组件,现在我们来了解一下关于Spring Cloud的网关,关于使用网关,我们同时也需要知道他在一个架构中起到的作用,并且,我们需要知道网关具体的相关功能,本篇…...
【Python】配置环境变量
Python配置Windows系统环境变量 打开电脑属性 ——> 高级系统设置 ——> 高级 ——> 环境变量 Python安装目录 D:\Program Files\Python39 winR打开运行,输入cmd打开命令窗口 python -V...
使用.Net nanoFramework 驱动ESP32的OLED显示屏
本文介绍如何使用.Net nanoFramework 驱动ESP32的OLED显示屏。我们将会从最基础的部分开始,逐步深入,让你能够理解并实现整个过程。无论你是初学者还是有一定经验的开发者,这篇文章都会对你有所帮助。 1. 硬件准备 1.1 ESP32开发板 这里我们…...
0基础学习VR全景平台篇第134篇:720VR全景,云台调整节点
相机、云台和脚架全套设备组装完成后需要进行调校才能开始拍摄。这一节,我们将主要介绍云台调整的两个内容:对中心靶、调三点一线。(后附调校原理) 云台部件名称 一、调节准备 (一)对于安装好的云台 1.检…...
扫地机器人地图与用户终端的同步
以下内容为本人的学习笔记,如需要转载,请声明原文链接 微信公众号「ENG八戒」https://mp.weixin.qq.com/s/APaJheSbgTW3jNssWsp5Ng 地图数据来源于机器人算法模块,一般通过SLAM算法完成建图的过程。 建图过程中,基础数据涉及到各…...
使用机器学习进行语法错误检测/纠正
@francescofranco_39234 一、说明 一般的学习,特别是深度学习,促进了自然语言处理。各种模型使人们能够执行机器翻译、文本摘要和情感分析——仅举几个用例。今天,我们将研究另一个流行的用途:我们将使用Gramformer构建一个用于机器学习语法错误检测和纠正的管道。 阅读本文…...
真做视频网站/杭州网站搜索排名
一. Oracle 11g 默认审计说明 之前整理的一篇有关审计的说明: Oracle Audit 审计 说明 http://www.cndba.cn/Dave/article/1469 在Maclean 的blog上看到了2篇介绍Oracle 11g 默认审计的文章,原文链接如下: 11g默认审计选项 http://www.oracl…...
asp网站制作教程/willfast优化工具下载
来到这边,开始工作后,才发现.有的时候,心态变了.一切都会改变.以前总是把自己会的技术挂在嘴边.而这次,更多的是踏踏实实的做出来.没有什么可骄傲的.一切都没有. 中午在机房里呆了一中午,又一次熟悉了设备及常用命令.在听完郭老师的培训后,才发现,自己是那么的无知.知识的深度及…...
潍坊网站制作发/成都网站建设系统
这样的功能主要用在两表分别在不同的数据库上,在数据库层面不同步数据关联不了的情况。且目前,被关联的表公仅支持是key/value两列数据的情况。 1、数据准备 -- 主表 select * from x_student_scores insert into x_student_scores select 1,语文,1,98…...
如何建设网站主页和其他相关页面/seo关键词排名优化要多少钱
《HTML5介绍ppt课件.ppt》由会员分享,可在线阅读,更多相关《HTML5介绍ppt课件.ppt(20页珍藏版)》请在人人文库网上搜索。1、Welcome to HTML 5,什么是HTML5,HTML5的特点,HTML5的新特性,简介趋势,优势 var cxtc.getContext(2d); var grdcxt.createLinearG…...
aws的efs可以做网站的什么/博客营销案例
一、各种规则正则表达式 1、大于0的正整数 /^[1-9]\d*$/2、是否是数字和一个英文,分开 const idContent this.form.activityId.replace(/[^\d,]/g, ) this.form.activityId idContent.replace(new RegExp(,, gm), ,)3、只能是一个英文,分开 const idContent this.adddata.…...
wordpress 联系地图/百度搜索高级搜索技巧
最近博客更新的少了,相对而言,我在自己的个人公众号里还是挺活跃的,大家可以扫描旁边的二维码,或者微信搜索公众号:“编程一生”加关注。 在分布式的年代,一个应用需要部署到多台服务器上。那么要查看日志文…...