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

【RabbitMQ】发布确认机制的具体实现

文章目录

    • 模式介绍
    • 建立连接
    • 单独确认
      • 代码实现逻辑
      • 运行结果
    • 批量确认
      • 代码实现逻辑
      • 运行结果
    • 异步确认
      • 实现逻辑介绍
      • 代码实现逻辑
      • 运行结果
    • 三种策略对比以及完整代码

模式介绍

作为消息中间件,都会面临消息丢失的问题,消息丢失大概分为三种情况:

  1. 生产者问题:因为应用程序故障,网络抖动等各种原因,生产者没有成功向 broker 发送消息
  2. 消息中间件自身问题:生产者成功发送给了 Broker,但是 Broker 没有把消息保存好,导致消息丢失
  3. 消费者问题:Broker 发送消息到消费者,消费者在消费消息时,因为没有处理好,导致 broker 将消费失败的消息从列表中删除了

image.png

  • RabbitMQ 也对上述问题给出了相应的解决方案。
    • 问题二可以通过持久化机制
    • 问题三可以采用消息应答机制
    • 问题一可以采用发布确认机制

发布确认属于 RabbitMQ 的七大工作模式之一

生产者将信道设置成 confirm(确认)模式

  • 一旦信道进入 confirm 模式,所有在该信道上面发布的消息都会被指派一个唯一的 ID(从 1 开始)
    • 同一个 channel 下,序号不可能重复
  • 一旦消息被投递到所有匹配的对类之后,brocker 就会发送一个确认(ACK)给生产者(包含消息的唯一 ID
    • 这就使得生产者知道消息已经正确到达目的的队列了
  • 如果消息和对类是可持久化的,那么确认消息会在将消息写入磁盘之后发出
    • broker 回传给生产者的确认消息中 deliveryTag 包含了确认消息的序号
    • 此外 broker 也可以设置 channel.basicAck 方法中的 multiple 参数,表示到这个序号之前的所有消息都已经得到了处理
      image.png

发送确认机制最大的好处在于它是异步的,生产者可以同时发布消息和等待信道返回确认消息

  1. 当消息最终得到确认之后,生产者可以通过回调方法来处理该确认消息
  2. 如果 RabbitMQ 因为自身内部错误导致消息丢失,就会发送一条 nack(Basic.Nack) 命令,生产者同样可以在回调方法中处理该 nack 命令

使用发布确认机制,必须要信道设置成 confirm (确认) 模式

  • 发布确认是 AMQP 0.9.1 协议的扩展,默认情况下它不会被启用
  • 生产者通过 channel.confirmSelect() 将信道设置为 confirm 模式
Channel channel = connection.createChannel();
channel.confirmSelect();

发布确认有三种策略,接下来我们来介绍这三种策略

ProducerBrockerConsumer 都有可能丢失消息

  • 发布确认是来解决生产者 Producer 消息丢失的问题
  • 生产者可以在发送消息的同时,等待返回确认消息

建立连接

因为每一个策略都需要重复建立链接这一步骤,所以我们将其提出来,单独作为一个方法,需要的时候直接调用即可

  • 之后就不用重复写这一部分代码了
  • 在类里面,main 方法外面,使用一个静态方法
public class PublisherConfirms {  static Connection createConnection() throws Exception {  ConnectionFactory connectionFactory = new ConnectionFactory();  connectionFactory.setHost(Constants.HOST);  connectionFactory.setPort(Constants.PORT);  connectionFactory.setUsername(Constants.USER_NAME);  connectionFactory.setPassword(Constants.PASSWORD);  connectionFactory.setVirtualHost(Constants.VIRTUAL_HOST);  return connectionFactory.newConnection();  }  public static void main(String[] args) {  }  }

单独确认

Publishing Messages Individually

代码实现逻辑

    /**  * 单独确认  */  private static void publishingMessagesIndividually() throws Exception {  // 1. 创建连接  // 我们将连接的建立写在 try 里面,这样就不用再去关闭了  try(Connection connection = createConnection()) {  // 2. 开启信道  Channel channel = connection.createChannel();  // 3. 设置信道为 confirms 模式  channel.confirmSelect();  // 4. 声明队列(交换机就使用内置的,就不再声明了)  // 队列对象、是否持久化、是否独占、是否自动删除、传递参数  channel.queueDeclare(Constants.PUBLISHER_CONFIRMS_QUEUE1, true, false, false,null);  // 5. 发送消息,并等待确认  // 这里我们需要可以再创建一个 MESSAGE_COUNT 全局变量,来指定消息的数量  long start = System.currentTimeMillis();  for (int i = 0; i < MESSAGE_COUNT; i++) {  String msg = "hello publisher confirms" + i;  // 信道的发送  // 交换机的名称(我们使用的是内置交换机,也就是空的)、routingKey、  channel.basicPublish("", Constants.PUBLISHER_CONFIRMS_QUEUE1, null, msg.getBytes());  // 等待确认(等待确认消息,只要消息被确认,这个方法就会被返回)  // 有 waitForConfirms() 和 waitForConfirmsOrDie() 随便用哪个  // 如果超时过期,则抛出 TimeoutException。如果任何消息被 nack(丢失),waitForConfirmsOrDie 则抛出 Exception  channel.waitForConfirmsOrDie(5000);  }  long end = System.currentTimeMillis();  // 这里注意是 printf            System.out.printf("单独确认==>消息条数: %d, 耗时: %d ms \n", MESSAGE_COUNT, end-start);  }  }

运行结果

单独确认==>消息条数: 200, 耗时: 5793 ms 
  • 可以发现,耗时较长

观察上面代码,会发现这种策略是每发送一条消息后就调用 channel.waitForConfirmsOrDie() 方法, 之后等待服务器的确认

  • 这其实是一种串行同步等待的方式
  • 尤其对于持久化的消息来说,需要等待消息确认存储在硬盘之后才会返回 (调用 Linux 内核中的 fsync 方法)

但是消息确认机制是支持异步的,可以一边发送消息,一边等待消息确认。由此进行了改进,我们看另外两种策略

  • Publishing Messages in Batches(批量确认):每发送一批消息之后,调用 channel.waitForConfirms 方法,等待服务器的确认返回
  • Handling Publisher Confirms Asynchronously(异步确认):提供一个回调方法,服务端确认了一条或者多条消息后,客户端会对这个方法进行处理

批量确认

Publishing Messages in Batches

代码实现逻辑

/**  * 批量确认  */  
private static void publishingMessagesInBatches() throws Exception {  // 1. 建立连接  try(Connection connection = createConnection()){  // 2. 开启信道  Channel channel = connection.createChannel();  // 3. 设置信道为 confirm 模式  channel.confirmSelect();  // 4. 声明队列  channel.queueDeclare(Constants.PUBLISHER_CONFIRMS_QUEUE2, true, false, false, null);  // 5. 发送消息,并进行确认  // 设置批量处理的大小和计数器  long start = System.currentTimeMillis();  int batchSize = 100;  int outstandingMessageCount = 0;  for (int i = 0; i < MESSAGE_COUNT; i++) {  String msg = "hello publisher confirms" + i;  channel.basicPublish("", Constants.PUBLISHER_CONFIRMS_QUEUE2, null, msg.getBytes());  outstandingMessageCount++;  // 当计数器的大小达到了设置的批量处理大小,就进行确认  if(outstandingMessageCount == batchSize) {  channel.waitForConfirmsOrDie(5000);  // 消息确认后,计数器要清零  outstandingMessageCount = 0;  }  // 当计数器大小 < 100 的时候,由于没有达到批量发送的标准,所以单独再进行发送  if (outstandingMessageCount > 0) {  channel.waitForConfirmsOrDie(5000);  }  }  long end = System.currentTimeMillis();  System.out.printf("批量确认==>消息条数: %d, 耗时: %d ms", MESSAGE_COUNT, end-start);  }  
}

运行结果

批量确认==>消息条数: 200, 耗时: 128 ms 

异步确认

Handling Publisher Confirms Asynchronously

实现逻辑介绍

异步 confirm 方法的编程实现最为复杂

  • Channel 接口提供了一个方法 addConfirmListener()
  • 这个方法可以添加 ConfirmListener 回调接口

ConfirmListener 接口中包含两个方法:

  • handleAck(long deliveryTag, boolean multiple),处理 RabbitMQ 发送给生产者的 ack
    • deliveryTag 表示发送消息的序号
    • multiple 表示是否批量确认
  • handleNack(long deliveryTag, boolean multiple),处理 RabbitMQ 发送给生产者的 nack
    image.png|413

我们需要为每一个 Channel 维护一个已发送消息的序号集合

  • 当收到 RabbitMQconfirm 回调时,从集合中删除对应的消息
  • Channel 开启 confirm 模式后,channel 上发送消息都会附带一个从 1 开始递增的 deliveryTag 序号
  • 我们可以使用 SortedSet 的有序性来维护这个已发消息的集合
    1. 当收到 ack 时,从序列中删除该消息的序号。如果为批量确认消息,表示小于当前序号 deliveryTag 的消息都收到了,则清楚对应集合
    2. 当收到 nack 时,处理逻辑类似,不过需要结合具体业务情况,进行消息重发等操作

代码实现逻辑

/**  * 异步确认  */  
private static void handlingPublisherConfirmsAsynchronously() throws Exception {  // 1. 建立连接  try(Connection connection = createConnection()) {  // 2. 开启信道  Channel channel = connection.createChannel();  // 3. 设置信道为 confirm 模式  channel.confirmSelect();  // 4. 声明队列  channel.queueDeclare(Constants.PUBLISHER_CONFIRMS_QUEUE3, true, false, false, null);  // 5. 监听 confirm        long start = System.currentTimeMillis();  // 创建一个集合,用来存放未确认的消息(的id)  SortedSet<Long> confirmSeqNo = Collections.synchronizedSortedSet(new TreeSet<>());  channel.addConfirmListener(new ConfirmListener() {  @Override  public void handleAck(long deliveryTag, boolean multiple) throws IOException {  // 如果是批量确认,就要将集合中 <= deliveryTag 的 id 都给清除掉  if(multiple) {  // headSet(n)方法返回当前集合中小于 n 的集合  // 先获取到这部分 id,然后一起 clear 清除掉即可  confirmSeqNo.headSet(deliveryTag + 1).clear();  }else {  // 单独确认,只需要移除当前这个 id 即可  confirmSeqNo.remove(deliveryTag);  }  }  @Override  public void handleNack(long deliveryTag, boolean multiple) throws IOException {  // 和 ack 处理模式基本是相似的,只是多了一步重发处理  if(multiple) {  confirmSeqNo.headSet(deliveryTag + 1).clear();  }else {  confirmSeqNo.remove(deliveryTag);  }  // 业务需要根据实际场景进行处理,比如重发,此处代码省略  }  });  // 6. 发送消息  for (int i = 0; i < MESSAGE_COUNT; i++) {  String msg = "hello publisher confirms" + i;  long seqNo = channel.getNextPublishSeqNo(); // 拿到消息的序号  channel.basicPublish("", Constants.PUBLISHER_CONFIRMS_QUEUE3, null, msg.getBytes());  confirmSeqNo.add(seqNo); // 将消息的序号加入集合中  }  // 确认消息已处理完  while (!confirmSeqNo.isEmpty()) {  // 没有处理完,就休眠一段时间后再确认一下,看是否处理完  Thread.sleep(10);  }  long end = System.currentTimeMillis();  System.out.printf("异步确认==>消息条数: %d, 耗时: %d ms", MESSAGE_COUNT, end-start);  }  
}

运行结果

单独确认==>消息条数: 200, 耗时: 93 ms 

三种策略对比以及完整代码

package rabbitmq.publisher.confirms;  import com.rabbitmq.client.Channel;  
import com.rabbitmq.client.ConfirmListener;  
import com.rabbitmq.client.Connection;  
import com.rabbitmq.client.ConnectionFactory;  
import rabbitmq.constant.Constants;  import java.io.IOException;  
import java.util.Collections;  
import java.util.SortedSet;  
import java.util.TreeSet;  public class PublisherConfirms {  private static final Integer MESSAGE_COUNT = 200;  static Connection createConnection() throws Exception {  ConnectionFactory connectionFactory = new ConnectionFactory();  connectionFactory.setHost(Constants.HOST);  connectionFactory.setPort(Constants.PORT);  connectionFactory.setUsername(Constants.USER_NAME);  connectionFactory.setPassword(Constants.PASSWORD);  connectionFactory.setVirtualHost(Constants.VIRTUAL_HOST);  return connectionFactory.newConnection();  }  public static void main(String[] args) throws Exception {  // Strategy #1: Publishing Messages Individually  // 单独确认  publishingMessagesIndividually();  // Strategy #2: Publishing Messages in Batches  // 批量确认  publishingMessagesInBatches();  // Strategy #3: Handling Publisher Confirms Asynchronously  // 异步确认  handlingPublisherConfirmsAsynchronously();  }  /**  * 单独确认  */  private static void publishingMessagesIndividually() throws Exception {  // 1. 创建连接  // 我们将连接的建立写在 try 里面,这样就不用再去关闭了  try(Connection connection = createConnection()) {  // 2. 开启信道  Channel channel = connection.createChannel();  // 3. 设置信道为 confirms 模式  channel.confirmSelect();  // 4. 声明队列(交换机就使用内置的,就不再声明了)  // 队列对象、是否持久化、是否独占、是否自动删除、传递参数  channel.queueDeclare(Constants.PUBLISHER_CONFIRMS_QUEUE1, true, false, false,null);  // 5. 发送消息,并等待确认  // 这里我们需要可以再创建一个 MESSAGE_COUNT 全局变量,来指定消息的数量  long start = System.currentTimeMillis();  for (int i = 0; i < MESSAGE_COUNT; i++) {  String msg = "hello publisher confirms" + i;  // 信道的发送  // 交换机的名称(我们使用的是内置交换机,也就是空的)、routingKey、  channel.basicPublish("", Constants.PUBLISHER_CONFIRMS_QUEUE1, null, msg.getBytes());  // 等待确认(等待确认消息,只要消息被确认,这个方法就会被返回)  // 有 waitForConfirms() 和 waitForConfirmsOrDie() 随便用哪个  // 如果超时过期,则抛出 TimeoutException。如果任何消息被 nack(丢失),waitForConfirmsOrDie 则抛出 Exception                channel.waitForConfirmsOrDie(5000);  }  long end = System.currentTimeMillis();  // 这里注意是 printf            System.out.printf("单独确认==>消息条数: %d, 耗时: %d ms \n", MESSAGE_COUNT, end-start);  }  }  /**  * 批量确认  */  private static void publishingMessagesInBatches() throws Exception {  // 1. 建立连接  try(Connection connection = createConnection()){  // 2. 开启信道  Channel channel = connection.createChannel();  // 3. 设置信道为 confirm 模式  channel.confirmSelect();  // 4. 声明队列  channel.queueDeclare(Constants.PUBLISHER_CONFIRMS_QUEUE2, true, false, false, null);  // 5. 发送消息,并进行确认  // 设置批量处理的大小和计数器  long start = System.currentTimeMillis();  int batchSize = 100;  int outstandingMessageCount = 0;  for (int i = 0; i < MESSAGE_COUNT; i++) {  String msg = "hello publisher confirms" + i;  channel.basicPublish("", Constants.PUBLISHER_CONFIRMS_QUEUE2, null, msg.getBytes());  outstandingMessageCount++;  // 当计数器的大小达到了设置的批量处理大小,就进行确认  if(outstandingMessageCount == batchSize) {  channel.waitForConfirmsOrDie(5000);  // 消息确认后,计数器要清零  outstandingMessageCount = 0;  }  // 当计数器大小 < 100 的时候,由于没有达到批量发送的标准,所以单独再进行发送  if (outstandingMessageCount > 0) {  channel.waitForConfirmsOrDie(5000);  }  }  long end = System.currentTimeMillis();  System.out.printf("批量确认==>消息条数: %d, 耗时: %d ms \n", MESSAGE_COUNT, end-start);  }  }  /**  * 异步确认  */  private static void handlingPublisherConfirmsAsynchronously() throws Exception {  // 1. 建立连接  try(Connection connection = createConnection()) {  // 2. 开启信道  Channel channel = connection.createChannel();  // 3. 设置信道为 confirm 模式  channel.confirmSelect();  // 4. 声明队列  channel.queueDeclare(Constants.PUBLISHER_CONFIRMS_QUEUE3, true, false, false, null);  // 5. 监听 confirm            long start = System.currentTimeMillis();  // 创建一个集合,用来存放未确认的消息(的id)  SortedSet<Long> confirmSeqNo = Collections.synchronizedSortedSet(new TreeSet<>());  channel.addConfirmListener(new ConfirmListener() {  @Override  public void handleAck(long deliveryTag, boolean multiple) throws IOException {  // 如果是批量确认,就要将集合中 <= deliveryTag 的 id 都给清除掉  if(multiple) {  // headSet(n)方法返回当前集合中小于 n 的集合  // 先获取到这部分 id,然后一起 clear 清除掉即可  confirmSeqNo.headSet(deliveryTag + 1).clear();  }else {  // 单独确认,只需要移除当前这个 id 即可  confirmSeqNo.remove(deliveryTag);  }  }  @Override  public void handleNack(long deliveryTag, boolean multiple) throws IOException {  // 和 ack 处理模式基本是相似的,只是多了一步重发处理  if(multiple) {  confirmSeqNo.headSet(deliveryTag + 1).clear();  }else {  confirmSeqNo.remove(deliveryTag);  }  // 业务需要根据实际场景进行处理,比如重发,此处代码省略  }  });  // 6. 发送消息  for (int i = 0; i < MESSAGE_COUNT; i++) {  String msg = "hello publisher confirms" + i;  long seqNo = channel.getNextPublishSeqNo(); // 拿到消息的序号  channel.basicPublish("", Constants.PUBLISHER_CONFIRMS_QUEUE3, null, msg.getBytes());  confirmSeqNo.add(seqNo); // 将消息的序号加入集合中  }  // 确认消息已处理完  while (!confirmSeqNo.isEmpty()) {  // 没有处理完,就休眠一段时间后再确认一下,看是否处理完  Thread.sleep(10);  }  long end = System.currentTimeMillis();  System.out.printf("异步确认==>消息条数: %d, 耗时: %d ms", MESSAGE_COUNT, end-start);  }  }  }
  • 消息条数越多,异步确认的优势越明显

相关文章:

【RabbitMQ】发布确认机制的具体实现

文章目录 模式介绍建立连接单独确认代码实现逻辑运行结果 批量确认代码实现逻辑运行结果 异步确认实现逻辑介绍代码实现逻辑运行结果 三种策略对比以及完整代码 模式介绍 作为消息中间件&#xff0c;都会面临消息丢失的问题&#xff0c;消息丢失大概分为三种情况&#xff1a; …...

RabbitMQ是什么?应用场景有哪些?

RabbitMQ 是一款开源的消息代理中间件,基于 AMQP(高级消息队列协议)实现,用于在分布式系统中进行异步通信和消息传递。它通过将消息的发送者和接收者解耦,提高了系统的可扩展性、可靠性和灵活性。 核心特点 多协议支持:不仅支持 AMQP,还兼容 STOMP、MQTT 等多种消息协议…...

数学实验(Matlab符号运算)

一、符号对象的建立 Matlab符号运算特点 计算以推理方式进行&#xff0c;因此不受计算误差积累所带来的困扰 符号计算指令的调用比较简单&#xff0c;与数学教科书上的公式相近 Matlab符号运算举例 符号对象与符号表达式 在进行符号运算时&#xff0c;必须先定义基本的符号…...

使用 hover-class 实现触摸态效果 - uni-app 教程

目录 一、什么是 hover-class 二、常用组件支持 hover-class 三、基本 效果说明&#xff1a; 四、配合 hover-start-time 和 hover-stay-time 五、注意事项 六、实践建议 在移动端开发中&#xff0c;良好的用户交互体验尤为重要&#xff0c;点击或长按某个按钮时&#x…...

# 深度剖析LLM的“大脑”:单层Transformer的思考模式探索

简单说一下哈 —— 咱们打算训练一个单层 Transformer 加上稀疏自编码器的小型百万参数大型语言模型&#xff08;LLM&#xff09;&#xff0c;然后去调试它的思考过程&#xff0c;看看这个 LLM 的思考和人类思考到底有多像。 LLMs 是怎么思考的呢&#xff1f; 开源 LLM 出现之后…...

Git仓库迁移

前言 前面我讲了GitLab搭建与使用(SSH和Docker)两种方式&#xff0c;那么就会延伸出来一个情况&#xff1a;Git仓库迁移虽然这种情况很少发生&#xff0c;但是我自己公司近期要把 阿里云迁移到华为云&#xff0c;那么放在上面的Git仓库也要全量迁移下面我就写了一个脚本演示&am…...

Windows避坑部署CosyVoice多语言大语言模型

#工作记录 前言 在实际部署与应用过程中&#xff0c;项目的运行环境适配性对其稳定性与功能性的发挥至关重要。CosyVoice 项目虽具备强大的语音处理能力&#xff0c;但受限于开发与测试环境的侧重方向&#xff0c;其对运行环境存在特定要求。 该项目在 Linux 和 Docker 生态…...

《实现模式》以Golang视角解读 价值观和原则 day 1

为什么阅读实现模式&#xff1f; 为什么阅读《实现模式》&#xff1f;Kent Beck 的《实现模式》其核心思想——编写清晰、易于理解且易于维护的代码&#xff0c;对于软件工程的新手而言&#xff0c;直接深入复杂的设计模式或架构理念可能会感到困惑。《实现模式》则弥合了设计…...

解决 PicGo 上传 GitHub图床及Marp中Github图片编译常见难题指南

[目录] 0.行文概述 1.PicGo图片上传失败 2.*关于在Vscode中Marp图片的编译问题* 3.总结与启示行文概述 写作本文的动机是本人看到了Awesome Marp&#xff0c;发现使用 Markdown \texttt{Markdown} Markdown做PPT若加持一些 CSS , JavaScript \texttt{CSS},\texttt{JavaScript} …...

LeetCode 820 单词的压缩编码题解

LeetCode 820 单词的压缩编码题解 题目描述 题目链接 给定一个单词列表&#xff0c;将其编码为一个索引字符串S&#xff0c;格式为"单词1#单词2#…"。要求当某个单词是另一个单词的后缀时&#xff0c;该单词可以被省略。求最终编码字符串的最小长度。 解题思路 逆…...

Windows软件插件-写wav

下载本插件 本插件&#xff0c;将PCM音频流写入WAV音频文件。或将PCM音频流压缩为ALAW格式&#xff0c;写入WAV文件。可以创作大文件&#xff08;超过4字节所能表示的大小&#xff09;。插件类型为DLL&#xff0c;可以在win32和MFC程序中使用。使用本插件创建的ALAW格式WAV音频…...

基于 Spring Boot 瑞吉外卖系统开发(十五)

基于 Spring Boot 瑞吉外卖系统开发&#xff08;十五&#xff09; 前台用户登录 在登录页面输入验证码&#xff0c;单击“登录”按钮&#xff0c;页面会携带输入的手机号和验证码向“/user/login”发起请求。 定义UserMapper接口 Mapper public interface UserMapper exte…...

【Linux高级IO】多路转接之epoll

多路复用之epoll 一&#xff0c;认识epoll二&#xff0c;epoll的相关接口1. epoll_create2. epoll_ctl3. epoll_wait 三&#xff0c;epoll的原理四&#xff0c;epoll的两种工作模式&#xff08;ET和LT&#xff09;1. 两种工作模式2. 对比ET和LT 五&#xff0c;总结 在了解到sel…...

Java 性能调优全解析:从设计模式到 JVM 的 7 大核心方向实践

引言 在高并发、低延迟的技术场景中&#xff0c;Java 性能优化需要系统化的方法论支撑。本文基于7 大核心优化方向&#xff08;复用优化、计算优化、结果集优化、资源冲突优化、算法优化、高效实现、JVM 优化&#xff09;&#xff0c;结合权威框架与真实案例&#xff0c;构建从…...

“海外滴滴”Uber的Arm迁移实录:重构大规模基础设施​

云工作负载在性价比上的自然演进路径&#xff1a; Intel ➜ AMD ➜ ARM 不信&#xff1f;来看看 Uber 的做法&#xff1a; 01/Arm架构&#xff1a;云计算新时代 2023 年 2 月&#xff0c;Uber 正式开启了一项战略性迁移&#xff1a;将从本地数据中心迁移至云端&#xff0c;…...

java加强 -File

File类的对象可以代表文件/文件夹&#xff0c;并可以调用其提供的方法对象文件进行操作。 File对象既可以代表文件&#xff0c;也可以代表文件夹。 创建File对象&#xff0c;获取某个文件的信息 语法&#xff1a; File 对象名 new File("需要访问文件的绝对路径&…...

SQL注入 ---04

1 简单的sql注入 要求&#xff1a; 要有sql注入&#xff1a; 1&#xff0c;变量 2&#xff0c;变量要带入数据库进行查询 3&#xff0c;没有对变量进行过滤或者过滤不严谨 mysql> select * from users where id2 limit 0,1; 当我的语句这样写时查寻到的结果 当我修改为&…...

MySQL知识点总结(持续更新)

聚合函数通常用于对数据进行统计和聚合操作。以下是一些常见数据库系统&#xff08;如 MySQL、PostgreSQL、Oracle、SQL Server 等&#xff09;中常用的聚合函数&#xff1a; 常见的数据库聚合函数&#xff1a; COUNT()&#xff1a;计算指定列中非空值的数量 SELECT COUNT(*) …...

数字信号处理-大实验1.1

MATLAB仿真实验目录 验证实验&#xff1a;常见离散信号产生和实现验证实验&#xff1a;离散系统的时域分析应用实验&#xff1a;语音信号的基音周期&#xff08;频率&#xff09;测定 目录 一、常见离散信号产生和实现 1.1 实验目的 1.2 实验要求与内容 1.3 实验…...

Qt操作SQLite数据库教程

Qt 中操作 SQLite 数据库的步骤如下&#xff1a; 1. 添加 SQLite 驱动并打开数据库 #include <QSqlDatabase> #include <QSqlError> #include <QSqlQuery>// 创建数据库连接 QSqlDatabase db QSqlDatabase::addDatabase("QSQLITE"); db.setData…...

【PSINS工具箱】基于工具箱的单独GNSS导航、单独INS导航、两者结合组合导航,三种导航的对比程序。附完整的代码

本文给出基于PSINS工具箱的单独GNSS导航、单独INS导航、两者结合组合导航(153EKF)的程序。并提供三者的轨迹对比、误差对比。 文章目录 运行结果MATLAB代码代码的简单介绍简介2. 平均绝对误差 (MAE)主要模块运行结果 三轴轨迹图: 各轴误差曲线: 命令行窗口的结果输出: …...

开发者的测试复盘:架构分层测试策略与工具链闭环设计实战

摘要‌ 针对测试复盘流于形式、覆盖率虚高等行业痛点&#xff0c;本文提出一套结合架构分层与工具链闭环的解决方案&#xff1a; ‌分层测试策略精准化‌&#xff1a;通过单元测试精准狙击核心逻辑、契约测试驱动接口稳定性、黄金链路固化端到端场景&#xff0c;实现缺陷拦截率…...

手写CString类

学习和理解字符串处理机制&#xff1a;手写 CString 类是深入学习字符串处理和内存管理的有效方式。通过实现构造函数、析构函数、赋值运算符等&#xff0c;能够理解字符串在内存中的存储方式、动态内存分配和释放的原理&#xff0c;以及如何处理字符串的复制、拼接、查找等操作…...

electron结合vue,直接访问静态文件如何跳转访问路径

在最外的app.vue或者index.vue的js模块编写 let refdade ref(1);//刷新&#xff0c;获得请求// 获取完整的查询字符串&#xff08;例如: "?dade/myms"&#xff09;const searchParams new URLSearchParams(window.location.search);// 获取 dade 参数的值&#xf…...

解读RTOS 第七篇 · 驱动框架与中间件集成

1. 引言 在面向生产环境的 RTOS 系统中,硬件驱动框架与中间件层是连接底层外设与上层应用的桥梁。一个模块化、可扩展的驱动框架能够简化外设管理,提升代码可维护性;而丰富的中间件生态则为网络通信、文件系统、图形界面、安全加密等功能提供开箱即用的支持。本章将从驱动模…...

Java GUI开发全攻略:Swing、JavaFX与AWT

Swing 界面开发 Swing 是 Java 中用于创建图形用户界面&#xff08;GUI&#xff09;的库。它提供了丰富的组件&#xff0c;如按钮、文本框、标签等。 import javax.swing.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener;public class SwingExa…...

Cursor 0.5版本发布,新功能介绍

Cursor,这款流行的AI编程平台,刚刚在其v0.50更新中推出了一系列新功能。 首先,请将您的Cursor IDE更新到最新版本。当您打开Cursor时,您应该会在屏幕左下方收到关于最新版本发布的通知。 更多上下文控制: 对上下文的精细可见性,以及最新模型的MAX模式。 聊天升级: 导出…...

Android学习总结之Glide自定义三级缓存(实战篇)

一、为什么需要三级缓存 内存缓存&#xff08;Memory Cache&#xff09; 内存缓存旨在快速显示刚浏览过的图片&#xff0c;例如在滑动列表时来回切换的图片。在 Glide 中&#xff0c;内存缓存使用 LruCache 算法&#xff08;最近最少使用&#xff09;&#xff0c;能自动清理长…...

Maven 下载安装与配置教程

## 1. Maven 简介 Maven 是一个项目管理和构建自动化工具&#xff0c;主要用于 Java 项目。Maven 可以帮助开发者管理项目的构建、报告和文档&#xff0c;简化项目依赖管理。 ## 2. 下载 Maven 1. 访问 Maven 官方网站 [https://maven.apache.org/download.cgi](https://maven.…...

一篇解决Redis:持久化机制

目录 认识持久化 持久化方案 RDB&#xff08;Redis DataBase&#xff09; 手动触发 自动触发 小结 AOF(Append-Only File) AOF缓冲区刷新机制 AOF重写机制 AOF重写流程 ​编辑 混合持久化 认识持久化 我们都知道Mysql有四大特征&#xff0c;原子性&#xff0c;持久…...

使用IDEA创建Maven版本的web项目以及lombok的使用

1.新建项目 2.修改pom.xml 3.修改项目结构 4.在main/java下面写一个Servlet测试一下 然后当前页面往下滑 -Dfile.encodingUTF-8编写一句输出语句&#xff0c;测试是否成功部署配置&#xff0c;并选择到正确的位置&#xff1a; 回车以后 再回到idea里面&#xff0c;发现控…...

2025年AI开发者在开发者占比?

AI开发者在全球开发者中的占比目前没有一个统一且精确的数值&#xff0c;但根据行业报告和调研数据&#xff0c;可以给出以下大致的范围和趋势分析&#xff1a; 1. 综合估算范围 全球范围&#xff1a;AI/ML&#xff08;机器学习&#xff09;开发者约占开发者总数的 5%-15%&…...

SpringBoot整合MQTT实战:基于EMQX构建高可靠物联网通信,从零到一实现设备云端双向对话

一、引言 随着物联网(IoT)技术的快速发展&#xff0c;MQTT(Message Queuing Telemetry Transport)协议因其轻量级、低功耗和高效的特点&#xff0c;已成为物联网设备通信的事实标准。本文将详细介绍如何使用SpringBoot框架整合MQTT协议&#xff0c;基于开源MQTT代理EMQX实现设…...

Windows更新暂停七天关键注册表

环境&#xff1a;windows10 工具&#xff1a;procmon 下载地址&#xff1a;https://learn.microsoft.com/zh-cn/sysinternals/downloads/procmon 监控截图&#xff1a; 界面截图&#xff1a; 注&#xff1a; 1.北京时间差8小时 2.至少是从第二天恢复&#xff0c;即至少暂停…...

小白学习java第18天(上):spring

Spring &#xff1a;是一个轻量级&#xff08;一个小依赖就可以实现还不是轻量级&#xff09;的控制反转&#xff08;IOC&#xff09;和面向切面编程&#xff08;AOP&#xff09;的框架&#xff01; 优点&#xff1a; 1.Spring 是一个开源免费的框架&#xff08;容器&#xf…...

用Array.from实现创建一个1-100的数组

一、代码实现 let arr Array.from({length: 100}, (_, i) > i 1); 二、代码分析 1、Array.from(arrayLike, mapFn) &#xff08;1&#xff09;arrayLike 类数组对象&#xff08;如 { length: 100 }&#xff09;本身没有索引属性&#xff08;如 0: undefined, 1: undefi…...

什么是物联网 IoT 平台?

目录 物联网IoT平台的定义 物联网 IoT 平台发展历程 物联网IoT平台数据的特征 物联网IoT平台的处理流程 专为物联网 IoT 平台处理而生的时序数据库 物联网 IoT 平台时序数据处理面临的挑战及解决方案 收益与价值 物联网 IoT 平台企业案例 至数摇光 x TDengine 华自科技…...

PostgREST:无需后端 快速构建RESTful API服务

在现代 Web 开发中&#xff0c;API 已成为连接前后端的核心桥梁&#xff0c;传统的做法是通过后端框架来构建API接口&#xff0c;然后由前后端人员进行联调。 PostgREST是基于无服务器的一种实现方案&#xff0c;允许开发者将PostgreSQL数据库直接暴露为RESTful API&#xff0…...

3天北京旅游规划

北京 第一天应该集中在故宫和市中心区域&#xff0c;比如天安门、人民广场。这样可以体验到北京的历史和政治文化。午餐推荐烤鸭&#xff0c;因为这可是北京的特色。下午可以安排南锣鼓巷&#xff0c;既有古色古香的胡同&#xff0c;又有丰富的美食选择。 第二天的话&#xff0…...

MySQL--day1--数据库概述

&#xff08;以下内容全部来自上述课程&#xff09; 概述 1. 为什么要用数据库 持久化&#xff1a;内存中的数据断电之后就不存在了&#xff0c;所以需要持久化–>需要相关介质。 其中的一个介质就是数据库&#xff1a;存储数据量大、存储数据类型多 2. 数据库与数据库…...

[思维模式-38]:看透事物的关系:什么是事物的关系?事物之间的关系的种类?什么是因果关系?如何通过数学的方式表达因果关系?

一、什么是事物的关系&#xff1f; 事物的关系是指不同事物之间存在的各种联系和相互作用&#xff0c;它反映了事物之间的相互依存、相互影响、相互制约等特性。以下从不同维度为你详细阐述&#xff1a; 1、关系的类型 因果关系 定义&#xff1a;一个事件&#xff08;原因&a…...

图像识别与 OCR 应用实践

图像识别是一种让计算机具备“看”与“理解”图像能力的人工智能技术&#xff0c;其目标是从图像或视频中提取有意义的信息&#xff0c;如物体、人物、场景或文字。在现实生活中&#xff0c;这项技术被广泛应用于面部识别、自动驾驶、安防监控、医疗诊断、图像搜索等多个领域。…...

深入理解卷积神经网络:从基础原理到实战应用

在人工智能领域&#xff0c;卷积神经网络&#xff08;Convolutional Neural Network&#xff0c;简称 CNN&#xff09;凭借其强大的图像识别、处理能力&#xff0c;成为深度学习中不可或缺的技术。无论是自动驾驶汽车识别道路标志&#xff0c;还是医学影像分析辅助疾病诊断&…...

51单片机——交通指示灯控制器设计

设计目标 1、设计一交通灯控制&#xff0c;控制东西方向的红、黄、绿灯和南北方向的红、黄、绿灯。 2、可手动控制和自动控制&#xff0c;设置两个输入控制开关。 手动/自动开关&#xff0c;通过P11的按键输入控制 3、手动&#xff1a;设置开关P11&#xff0c;两种情况&#x…...

vue2 头像上传+裁剪组件封装

背景&#xff1a;最近在进行公司业务开发时&#xff0c;遇到了头像上传限制尺寸的需求&#xff0c;即限制为一寸证件照&#xff08;宽295像素&#xff0c;高413像素&#xff09;。 用到的第三方库&#xff1a; "vue-cropper": "^0.5.5" 完整组件代码&…...

面向对象设计模式之代理模式详解

文章目录 面向对象设计模式之代理模式详解面向对象思想&#xff1a;现代软件开发的基石代理模式&#xff1a;巧妙的中间层设计JavaScript 语法点与代理模式的结合JavaScript 实现代理模式示例代理模式的应用场景 面向对象设计模式之代理模式详解 在现代软件开发的浩瀚领域中&a…...

Leetcode209做题笔记

力扣209 题目分析&#xff1a;想象一个窗口遍历着这个数组&#xff0c;不断扩大右边界&#xff0c;让r。往窗口中添加数字&#xff1a; 此时我们找到了这个窗口&#xff0c;它的和满足了大于等于target的条件&#xff0c;题目让我求最短的&#xff0c;那么我们就尝试来缩短它&…...

SVG 知识详解:从入门到精通

SVG 知识详解&#xff1a;从入门到精通 作为一名前端开发者&#xff0c;我经常会被SVG的魅力所折服。这种基于XML的矢量图形格式&#xff0c;不仅能完美适配各种屏幕分辨率&#xff0c;还能通过CSS和JavaScript进行灵活控制。今天&#xff0c;就让我们一起来深入探索SVG的世界…...

编译openssl源码

openssl版本 1.1.1c windows 安装环境 perl 先安装perl&#xff0c;生成makefile需要 https://strawberryperl.com/releases.html nasm nasm 也是生成makefile需要 https://www.nasm.us/ 安装完perl输入一下nasm&#xff0c;看看能不能找到&#xff0c;找不到的话需要配…...

土壤温湿盐分传感器用于节水农业灌溉引领者三针设计原理便于安装维护

土壤温度部分是由精密铂电阻和高精度变送器两部分组成。变送器部分由电源模块、温度传感模块、变送模块、温度补偿模块及数据处理模块等组成&#xff0c;彻底解决铂电阻因自身特点导入的测量误差&#xff0c;变送器内有零漂电路和温度补偿电路&#xff0c;对使用环境有较高的适…...