RabbitMQ(七)ACK 消息确认机制

目录

    • 一、简介
      • 1.1 背景
      • 1.2 定义
      • 1.3 如何查看确认/未确认的消息数?
    • 二、消息确认机制的分类
      • 2.1 消息发送确认
        • 1)ConfirmCallback方法
        • 2)ReturnCallback方法
        • 3)代码实现方式一:统一配置
          • a.配置类
          • a.生产者
          • c.消费者
          • d.测试结果
        • 4)代码实现方式二:单独配置
      • 2.2 消息接收确认
        • 1)basicAck() 方法
        • 2)basicReject() 方法
        • 3)basicNack() 方法
        • 4)代码实现
          • a.配置方式一:代码配置
          • b.配置方式二:配置文件
          • c.生产者
          • d.消费者
          • e.测试结果

在这里插入图片描述

当我们在项目中引入了新的中间件之后,数据的风险性就要多一层考虑。那么,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 消息确认机制分为两大类:

  1. 消息发送确认,又分为:
    • 生产者到交换机的确认
    • 交换机到队列的确认
  2. 消息接收确认

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) 默认是自动确认的。自动确认会 在消息发送给消费者后立即确认,但 存在丢失消息的可能。如果消费端消费逻辑抛出了异常,假如我们使用了事务的回滚,也只是保证了数据的一致性,消息还是丢失了。也就是消费端没有处理成功这条消息,那么就相当于丢失了消息。

消息的确认模式有三种:

  1. AcknowledgeMode.NONE:自动确认。(默认)
  2. AcknowledgeMode.AUTO:根据情况确认。
  3. AcknowledgeMode.MANUAL:手动确认。(推荐)

消费者收到消息后,手动调用 Channel 的 basicAck()/basicReject()/basicNack() 方法后,RabbitMQ 收到消息后,才认为本次投递完成。

  1. basicAck():用于确认当前消息。
  2. basicReject():用于拒绝当前消息,可以自定义是否重回队列。
  3. 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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/592269.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

TypeScript中的Declare关键字的作用

详细介绍TypeScript中的“declare”关键字的作用&#xff0c;这样你对*.d.ts文件就不陌生了。 当你在TypeScript项目中打开 *.d.ts 声明文件时&#xff0c;你可能会看到 declare 。你知道 declare 是做什么的吗?如果你不知道&#xff0c;读完这篇文章&#xff0c;也许你就会知…

安装VS Code到AWS EC2 Linux 2

文章目录 小结问题及解决参考 小结 本文记录了安装VS Code到AWS EC2 Linux 2。 问题及解决 安装VS Code到AWS EC2 Linux 2采取了以下步骤&#xff1a; 更新VS Code的YUM源&#xff1a; [ec2-userip-100-92-28-119 ~]$ sudo rpm --import https://packages.microsoft.com/k…

LanChatRoom局域网聊天室

CPP已经结课&#xff0c;我提交的项目是Qt的入门项目&#xff0c;局域网聊天室LanChatRoom。 这个代码重构了很多遍。第一遍是照着明哥推荐到书&#xff0c;把代码抄了一遍。 但抄下来之后&#xff0c;各种问题&#xff0c;而且是清朝老代码。抄了一遍之后&#xff0c;对代码的…

python慕课版课后题答案,python慕课作业答案

这篇文章主要介绍了中国大学慕课python测验答案&#xff0c;具有一定借鉴价值&#xff0c;需要的朋友可以参考下。希望大家阅读完这篇文章后大有收获&#xff0c;下面让小编带着大家一起了解一下。 Flag 8月29日前学习完MOOC_Python课程&#xff0c;以下各测试题均来自于Python…

深度学习|10.2 边缘检测示例 10.3 更多边缘检测

文章目录 如何在编程中实现卷积运算使用卷积实现边缘检测结果矩阵的元素正负性质的意义水平分类器如何构造卷积运算使用的矩阵 原矩阵通过一个过滤器&#xff08;filter&#xff09;/核心&#xff08;kernel&#xff09;来生成一个新的矩阵。 如何在编程中实现卷积运算 使用卷积…

Kafka安全认证机制详解之SASL_PLAIN

一、概述 官方文档&#xff1a; https://kafka.apache.org/documentation/#security 在官方文档中&#xff0c;kafka有五种加密认证方式&#xff0c;分别如下&#xff1a; SSL&#xff1a;用于测试环境SASL/GSSAPI (Kerberos) &#xff1a;使用kerberos认证&#xff0c;密码是…

Redis:原理+项目实战——Redis实战3(Redis缓存最佳实践(问题解析+高级实现))

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位大四、研0学生&#xff0c;正在努力准备大四暑假的实习 &#x1f30c;上期文章&#xff1a;Redis&#xff1a;原理项目实战——Redis实战2&#xff08;Redis实现短信登录&#xff08;原理剖析代码优化&#xff09;&#x…

【计算机毕业设计】SSM游戏点评网站

项目介绍 本项目分为前后台&#xff0c;前台为普通用户登录&#xff0c;后台为管理员登录&#xff1b; 管理员角色包含以下功能&#xff1a; 管理员登录,管理员管理,网站用户管理,游戏资讯管理,游戏类型管理,城市信息管理,竞技场管理,游戏信息管理,游戏评价信息管理等功能。…

使用UDF扩展Spark SQL

Apache Spark是一个强大的分布式计算框架&#xff0c;Spark SQL是其一个核心模块&#xff0c;用于处理结构化数据。虽然Spark SQL内置了许多强大的函数和操作&#xff0c;但有时可能需要自定义函数来处理特定的数据需求。在Spark SQL中&#xff0c;可以使用UDF&#xff08;User…

视频剪辑技巧:制作视频画中画效果,背景图片的选取与运用

在视频剪辑中&#xff0c;画中画效果是一种常用的技巧&#xff0c;它可以在同一屏幕上展示两个或多个视频片段。这种效果在制作教程、比较两个物品或展示对话等场景中非常实用。现在一起来看看云炫AI智剪如何制作视频画中画效果&#xff0c;选取和运用背景图片的方法。 一起来看…

【Apache-2.0】springboot-openai-chatgpt超级AI大脑产品架构图

springboot-openai-chatgpt: 一个基于SpringCloud的Chatgpt机器人&#xff0c;已对接GPT-3.5、GPT-4.0、百度文心一言、stable diffusion AI绘图、Midjourney绘图。用户可以在界面上与聊天机器人进行对话&#xff0c;聊天机器人会根据用户的输入自动生成回复。同时也支持画图&a…

RT-Thread内核移植

目录 前言一、实验平台简介1.1 W601简介1.2 RT-Thread简介1.3 ENV简介 二、开发环境搭建2.1 MDK安装2.2 Git安装2.3 RT-Thread相关下载2.4 其他素材 三、移植RT-Thread四、ENV使用五、W601开发板下载验证5.1 使用串口下载软件5.2 ST-Link下载 前言 本文以正点原子W601开发板为例…

阿里云服务器端口PPTP 1723放行教程

阿里云服务器安装PPTP VPN需要先开通1723端口&#xff0c;阿里云服务器端口是在安全组中操作的&#xff0c;阿里云服务器网aliyunfuwuqi.com来详细说明阿里云服务器安全组开放PPTP VPN专用1723端口教程&#xff1a; 阿里云服务器放行1723端口教程 PPTP是点对点隧道协议&#…

STM32 学习(二)GPIO

目录 一、GPIO 简介 1.1 GPIO 基本结构 1.2 GPIO 位结构 1.3 GPIO 工作模式 二、GPIO 输出 三、GPIO 输入 1.1 传感器模块 1.2 开关 一、GPIO 简介 GPIO&#xff08;General Purpose Input Output&#xff09;即通用输入输出口。 1.1 GPIO 基本结构 如下图&#xff0…

C#使用 OpenHardwareMonitor获取CPU或显卡温度、使用率、时钟频率相关方式

C# 去获取电脑相关的基础信息&#xff0c;还是需要借助 外部的库&#xff0c;我这边尝试了自己去实现它 网上有一些信息&#xff0c;但不太完整&#xff0c;都比较零碎&#xff0c;这边尽量将代码完整的去展示出来 OpenHardwareMonitor获取CPU的温度和频率需要管理员权限 在没…

梯度下降法

前言&#xff1a;在均方差损失函数推导中&#xff0c;我使用到了梯度下降法来优化模型&#xff0c;即迭代优化线性模型中的和。现在进一步了解梯度下降法的含义以及具体用法。 一、梯度下降法(入门级理解&#xff09; 定义&#xff1a;梯度下降是一种用于最小化损失函数的优化…

Python爬取今日头条热门文章

前言 今日头条文章收益是没有任何门槛&#xff0c;只要是你发布文章&#xff0c;每篇文章的阅读量超过1000就能有收益&#xff0c;阅读量越多收益越高。于是乎我就有了个大胆的想法。何不利用Python爬虫&#xff0c;爬取热门文章&#xff0c;然后完成自动化发布文章呢&#xf…

专访 | STIF2023第四届国际科创节访第七在线CEO赵嘉程

12月15日&#xff0c;在STIF2023第四届国际科创节暨数服会上&#xff0c;第七在线获得年度数智化创新典范奖&#xff0c;第七在线CEO赵嘉程在颁奖典礼现场接受了媒体专访。 主持人&#xff1a;赵总&#xff0c;您好&#xff0c;欢迎您接受我们的专访&#xff0c;首先我们特别想…

杰发科技AC7840——Eclipse环境DMA注意事项

0.序 用 户 使 用 DMA 时 &#xff0c; 所 有 DMA 搬 运 的 SRAM 数 据 都 必 须 存 放 在 SRAM_U 区 (0x20000000~0x2000EFFF) 。 1. 修改办法 第一步&#xff1a; RAM定义 /* Specify the memory areas */ MEMORY {FLASH (rx) : ORIGIN 0x00000000, LENGT…

dyld: Library not loaded: /usr/lib/swift/libswiftCoreGraphics.dylib

更新Xcode14后低版本iPhone调试报错 dyld: Library not loaded: /usr/lib/swift/libswiftCoreGraphics.dylib Referenced from: /var/containers/Bundle/Application/…/….app/… Reason: image not found 这是缺少libswiftCoreGraphics库 直接导入libswiftCoreGraphics库即…