RabbitMQ 消息队列:从入门到Spring Boot实战

RabbitMQ 作为一款开源的、基于 AMQP(Advanced Message Queuing Protocol)协议实现的消息代理,凭借其强大的功能、灵活的路由机制以及出色的性能,在业界得到了广泛的应用。无论是处理高并发订单、异步通知、日志收集还是系统解耦,RabbitMQ 都能发挥其独特的作用。

1. RabbitMQ 核心概念

RabbitMQ 的强大功能离不开其背后一系列精心设计的核心概念。理解这些概念是掌握 RabbitMQ 的基础,也是构建高效、可靠消息系统的关键。

1.1 生产者 (Producer) 与消费者 (Consumer)

在消息队列的世界里,生产者和消费者是两个最基本的角色,它们共同构成了消息流转的起点和终点。

  • 生产者 (Producer):生产者是消息的创建者和发送者。它负责将业务数据封装成消息,并将其发送到 RabbitMQ 消息代理(Broker)。生产者通常不关心消息最终会被哪个消费者处理,它只负责将消息可靠地投递到 RabbitMQ。

  • 消费者 (Consumer):消费者是消息的接收者和处理者。它从 RabbitMQ 的队列中获取消息,并根据业务逻辑对消息进行相应的处理。一个队列可以有多个消费者,它们之间可以形成竞争关系(竞争消费者模式),也可以形成广播关系(发布/订阅模式)。

消息在生产者和消费者之间流转,实现了业务逻辑的解耦。生产者无需知道消费者是谁,消费者也无需知道消息来自哪个生产者,它们之间通过 RabbitMQ 这一中间件进行通信,大大提高了系统的灵活性和可维护性。

1.2 消息队列 (Queue)

消息队列是 RabbitMQ 的核心组件之一,它是消息的“家”,所有发送到 RabbitMQ 的消息都会被存储在队列中,直到被消费者取走。队列在 RabbitMQ 中扮演着重要的角色,它确保了消息的持久化存储和有序传输。

消息队列具有以下几个重要属性:

  • 持久性 (Durability):当队列被声明为持久化时,即使 RabbitMQ 服务重启,该队列以及其中未被消费的消息也不会丢失。这对于确保消息的可靠性至关重要。在实际应用中,为了避免消息丢失,通常会将队列设置为持久化。

  • 自动删除 (Auto-delete):如果一个队列被设置为自动删除,那么当最后一个消费者断开连接后,该队列会自动被删除。这种队列适用于临时性的消息处理场景,例如某些一次性任务的队列。

  • 惰性 (Lazy):惰性队列是 RabbitMQ 3.6 版本引入的新特性。当队列被声明为惰性时,消息会尽可能地写入磁盘,而不是一直保存在内存中。这有助于减少内存消耗,尤其是在处理大量消息堆积的场景下。但是,从磁盘读取消息会带来一定的性能开销。

  • 排他性 (Exclusive):排他队列是与连接绑定的。当声明排他队列的连接断开时,该队列会自动被删除。排他队列只能被声明它的连接访问,其他连接无法访问。这种队列通常用于客户端独占的临时队列。

1.3 交换机 (Exchange)

交换机是 RabbitMQ 消息路由的核心组件。生产者发送消息时,并不是直接将消息发送到队列,而是发送到交换机。交换机根据其类型和路由规则,将消息路由到一个或多个队列中,或者直接丢弃消息。交换机是消息路由的“交通枢纽”。

RabbitMQ 提供了四种主要的交换机类型,每种类型都有其独特的路由行为:

  • Direct Exchange (直连交换机):直连交换机是最简单的交换机类型。它根据消息的 routing key 进行精确匹配。当消息的 routing key 与队列绑定的 binding key 完全一致时,消息才会被路由到该队列。直连交换机适用于点对点通信或需要精确路由的场景。

  • Topic Exchange (主题交换机):主题交换机在直连交换机的基础上增加了模式匹配的功能。它允许 binding key 使用通配符进行模糊匹配。其中,* 匹配一个单词,# 匹配零个或多个单词。主题交换机非常灵活,适用于日志系统、事件分发等需要根据不同主题订阅消息的场景。

  • Headers Exchange (首部交换机):首部交换机不依赖 routing key 进行路由,而是根据消息的 headers 属性进行匹配。生产者在发送消息时,可以在消息的 headers 中添加自定义属性。队列在绑定到首部交换机时,可以指定一组 headers 属性和匹配规则(例如 allany),只有当消息的 headers 满足匹配规则时,消息才会被路由到该队列。首部交换机提供了更灵活的路由方式,适用于复杂的业务场景。

  • Fanout Exchange (扇形交换机):扇形交换机是最“粗暴”的交换机类型。它会将接收到的所有消息广播到所有绑定到它的队列中,完全忽略消息的 routing key。扇形交换机适用于发布/订阅模式,例如广播通知、日志分发等场景。

1.4 绑定 (Binding)

绑定是连接交换机和队列的桥梁。它告诉交换机,当满足特定条件时,应该将消息路由到哪个队列。绑定关系是 RabbitMQ 路由机制的核心,它定义了消息从交换机到队列的路径。

在创建绑定时,通常会指定一个 binding keybinding key 的作用取决于交换机的类型:

  • 对于 Direct Exchange,binding key 必须与消息的 routing key 完全匹配。
  • 对于 Topic Exchange,binding key 可以包含通配符,与消息的 routing key 进行模式匹配。
  • 对于 Fanout Exchange,binding key 会被忽略,因为所有消息都会被广播。
  • 对于 Headers Exchange,binding key 不起作用,路由基于消息的 headers

1.5 消息确认 (Acknowledgement)

消息确认是 RabbitMQ 确保消息可靠性的重要机制。当消费者从队列中获取消息并成功处理后,会向 RabbitMQ 发送一个确认(ACK)信号。RabbitMQ 收到确认信号后,才会将该消息从队列中彻底删除。如果在消费者处理消息过程中发生异常或消费者崩溃,未发送确认信号,RabbitMQ 会认为消息未被成功处理,会将消息重新投递给其他消费者(或同一消费者),从而保证消息不会丢失。

消息确认分为两种模式:

  • 自动确认 (Auto Acknowledge):在这种模式下,RabbitMQ 会在消息发送给消费者后立即将其从队列中删除,而不管消费者是否真正处理了消息。这种模式简单方便,但存在消息丢失的风险,不适用于对消息可靠性要求高的场景。

  • 手动确认 (Manual Acknowledge):在这种模式下,消费者在处理完消息后,需要显式地向 RabbitMQ 发送确认信号。手动确认提供了更高的消息可靠性,因为只有当消费者确认消息已成功处理后,消息才会被删除。手动确认又分为肯定确认(basicAck)、否定确认(basicNack)和拒绝消息(basicReject)等操作,可以根据业务需求灵活控制消息的处理状态。

2. RabbitMQ 常用工作模式

RabbitMQ 提供了多种灵活的工作模式,以适应不同的消息通信需求。

2.1 简单模式 (Simple Mode)

简单模式,顾名思义,是最基础的消息通信模式,也被称为“Hello World”模式。它由一个生产者、一个队列和一个消费者组成。

  • 特点:生产者将消息发送到队列,消费者从队列中获取消息并处理。消息按照先进先出(FIFO)的顺序进行处理。这种模式实现了最简单的点对点通信。
  • 应用场景:适用于简单的消息传递,例如发送一次性通知、处理单个任务等。

2.2 工作队列模式 (Work Queues Mode)

工作队列模式(也称为任务队列或竞争消费者模式)旨在解决单个消费者处理消息速度慢的问题。在这种模式下,一个生产者将消息发送到一个队列,但有多个消费者同时监听该队列,共同竞争消息。

  • 特点
    • 负载均衡:RabbitMQ 默认采用轮询(Round-robin)的方式将消息分发给不同的消费者,从而实现消息的负载均衡。
    • 消息确认:为了确保消息不丢失,消费者在处理完消息后需要发送确认信号。如果消费者在处理过程中崩溃,未确认的消息会被重新投递给其他消费者。
  • 应用场景:适用于处理耗时任务的场景,例如图片处理、视频转码、邮件发送等。通过增加消费者数量,可以提高系统的吞吐量和处理能力。

2.3 发布/订阅模式 (Publish/Subscribe Mode)

发布/订阅模式(Pub/Sub)实现了消息的广播。在这种模式下,消息不再直接发送到队列,而是发送到 Fanout Exchange。所有绑定到该交换机的队列都会收到消息,然后这些队列再将消息分发给各自的消费者。

  • 特点
    • 广播:一个消息可以被多个消费者同时接收和处理。
    • 解耦:生产者和消费者之间完全解耦,生产者无需知道有多少个消费者,消费者也无需知道消息来自哪个生产者。
  • 应用场景:适用于需要将同一消息发送给多个订阅者的场景,例如日志系统(所有日志都发送给所有日志处理服务)、事件通知(用户注册成功后通知所有相关服务)等。

2.4 路由模式 (Routing Mode)

路由模式允许消息根据 routing key 进行有选择地路由。在这种模式下,消息发送到 Direct Exchange,队列在绑定到交换机时会指定一个 binding key。只有当消息的 routing key 与队列的 binding key 完全匹配时,消息才会被路由到该队列。

  • 特点
    • 选择性接收:消费者可以根据自己感兴趣的 routing key 来接收消息。
    • 灵活路由:通过定义不同的 routing key,可以实现消息的精细化路由。
  • 应用场景:适用于需要根据消息的特定属性进行分类处理的场景,例如日志系统(根据日志级别路由到不同的处理服务,如 error 级别的日志发送到错误处理服务,info 级别的日志发送到信息记录服务)。

2.5 主题模式 (Topics Mode)

主题模式是路由模式的增强版,它允许 routing keybinding key 使用通配符进行模式匹配。消息发送到 Topic Exchange,队列在绑定到交换机时会指定一个包含通配符的 binding key

  • 特点
    • 更灵活的匹配* 匹配一个单词,# 匹配零个或多个单词。这使得消息路由更加灵活和强大。
    • 多条件订阅:消费者可以订阅符合特定模式的消息。
  • 应用场景:适用于需要根据多个条件进行消息过滤和订阅的场景,例如股票行情系统(用户可以订阅 *.stock.us 来获取所有美国股票信息,或者订阅 ibm.# 来获取所有 IBM 相关的消息)。

3. Spring Boot 整合 RabbitMQ 实战

Spring Boot 极大地简化了 Spring 应用的开发,包括与各种消息中间件的集成。通过 Spring AMQP 模块,我们可以非常方便地在 Spring Boot 项目中集成 RabbitMQ,实现消息的发送和接收。

3.1 环境准备

首先,我们需要创建一个 Spring Boot 项目,并在 pom.xml 中引入 RabbitMQ 相关的 Maven 依赖。此外,确保本地或远程环境中已经安装并运行了 RabbitMQ 服务。

3.1.1 Maven 依赖引入

pom.xml 文件中添加以下依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.0</version> <!-- 根据您的Spring Boot版本调整 --><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.example</groupId><artifactId>rabbitmq-spring-boot-demo</artifactId><version>0.0.1-SNAPSHOT</version><name>rabbitmq-spring-boot-demo</name><description>Demo project for Spring Boot and RabbitMQ</description><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin></plugins></build></project>

3.1.2 RabbitMQ 安装与配置

如果您还没有安装 RabbitMQ,可以通过 Docker 或直接下载安装包进行安装。这里以 Docker 为例,快速启动一个 RabbitMQ 实例:

docker run -it --rm --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:management

上述命令会启动一个 RabbitMQ 容器,并暴露 5672 端口(AMQP 协议端口)和 15672 端口(管理界面端口)。您可以通过浏览器访问 http://localhost:15672,使用默认的 guest/guest 账号登录管理界面。

3.2 配置文件

src/main/resources/application.yml (或 application.properties) 中配置 RabbitMQ 连接信息:

spring:rabbitmq:host: localhostport: 5672username: guestpassword: guestvirtual-host: /listener:simple:acknowledge-mode: manual # 消费者手动确认消息

这里我们配置了 RabbitMQ 的连接地址、端口、用户名、密码和虚拟主机。特别地,我们将消费者消息确认模式设置为 manual,以便在代码中手动确认消息,确保消息的可靠性。

3.3 生产者实现

生产者负责将消息发送到 RabbitMQ 的交换机。我们将演示如何使用 RabbitTemplate 发送消息到不同类型的交换机。

3.3.1 配置交换机和队列

为了方便管理和配置,我们可以在 Spring Boot 配置类中定义交换机、队列和绑定关系。这里以 Direct Exchange、Topic Exchange 和 Fanout Exchange 为例。

package com.example.rabbitmqspringbootdemo.config;import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class RabbitMQConfig {// Direct Exchange 相关配置public static final String DIRECT_EXCHANGE = "direct.exchange";public static final String DIRECT_QUEUE = "direct.queue";public static final String DIRECT_ROUTING_KEY = "direct.routing.key";@Beanpublic DirectExchange directExchange() {return new DirectExchange(DIRECT_EXCHANGE, true, false);}@Beanpublic Queue directQueue() {return new Queue(DIRECT_QUEUE, true);}@Beanpublic Binding directBinding(Queue directQueue, DirectExchange directExchange) {return BindingBuilder.bind(directQueue).to(directExchange).with(DIRECT_ROUTING_KEY);}// Topic Exchange 相关配置public static final String TOPIC_EXCHANGE = "topic.exchange";public static final String TOPIC_QUEUE_A = "topic.queue.a";public static final String TOPIC_QUEUE_B = "topic.queue.b";public static final String TOPIC_ROUTING_KEY_A = "topic.#.a"; // 匹配 topic.x.a, topic.y.z.apublic static final String TOPIC_ROUTING_KEY_B = "topic.b.*"; // 匹配 topic.b.x@Beanpublic TopicExchange topicExchange() {return new TopicExchange(TOPIC_EXCHANGE, true, false);}@Beanpublic Queue topicQueueA() {return new Queue(TOPIC_QUEUE_A, true);}@Beanpublic Queue topicQueueB() {return new Queue(TOPIC_QUEUE_B, true);}@Beanpublic Binding topicBindingA(Queue topicQueueA, TopicExchange topicExchange) {return BindingBuilder.bind(topicQueueA).to(topicExchange).with(TOPIC_ROUTING_KEY_A);}@Beanpublic Binding topicBindingB(Queue topicQueueB, TopicExchange topicExchange) {return BindingBuilder.bind(topicQueueB).to(topicExchange).with(TOPIC_ROUTING_KEY_B);}// Fanout Exchange 相关配置public static final String FANOUT_EXCHANGE = "fanout.exchange";public static final String FANOUT_QUEUE_1 = "fanout.queue.1";public static final String FANOUT_QUEUE_2 = "fanout.queue.2";@Beanpublic FanoutExchange fanoutExchange() {return new FanoutExchange(FANOUT_EXCHANGE, true, false);}@Beanpublic Queue fanoutQueue1() {return new Queue(FANOUT_QUEUE_1, true);}@Beanpublic Queue fanoutQueue2() {return new Queue(FANOUT_QUEUE_2, true);}@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);}
}

3.3.2 消息发送服务

创建一个生产者服务,使用 RabbitTemplate 发送消息。RabbitTemplate 是 Spring AMQP 提供的核心组件,用于发送和接收消息。

package com.example.rabbitmqspringbootdemo.producer;import com.example.rabbitmqspringbootdemo.config.RabbitMQConfig;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class MessageProducer {@Autowiredprivate RabbitTemplate rabbitTemplate;public void sendDirectMessage(String message) {rabbitTemplate.convertAndSend(RabbitMQConfig.DIRECT_EXCHANGE, RabbitMQConfig.DIRECT_ROUTING_KEY, message);System.out.println("Direct Message Sent: " + message);}public void sendTopicMessageA(String message) {rabbitTemplate.convertAndSend(RabbitMQConfig.TOPIC_EXCHANGE, "topic.order.a", message);System.out.println("Topic Message A Sent: " + message);}public void sendTopicMessageB(String message) {rabbitTemplate.convertAndSend(RabbitMQConfig.TOPIC_EXCHANGE, "topic.user.b.register", message);System.out.println("Topic Message B Sent: " + message);}public void sendFanoutMessage(String message) {rabbitTemplate.convertAndSend(RabbitMQConfig.FANOUT_EXCHANGE, "", message); // Fanout Exchange 忽略 routing keySystem.out.println("Fanout Message Sent: " + message);}
}

3.3.3 测试生产者

您可以通过一个简单的 Controller 来触发消息发送:

package com.example.rabbitmqspringbootdemo.controller;import com.example.rabbitmqspringbootdemo.producer.MessageProducer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;@RestController
public class TestController {@Autowiredprivate MessageProducer messageProducer;@GetMapping("/sendDirect")public String sendDirectMessage(@RequestParam String msg) {messageProducer.sendDirectMessage(msg);return "Direct message sent: " + msg;}@GetMapping("/sendTopicA")public String sendTopicMessageA(@RequestParam String msg) {messageProducer.sendTopicMessageA(msg);return "Topic message A sent: " + msg;}@GetMapping("/sendTopicB")public String sendTopicMessageB(@RequestParam String msg) {messageProducer.sendTopicMessageB(msg);return "Topic message B sent: " + msg;}@GetMapping("/sendFanout")public String sendFanoutMessage(@RequestParam String msg) {messageProducer.sendFanoutMessage(msg);return "Fanout message sent: " + msg;}
}

3.4 消费者实现

消费者通过 @RabbitListener 注解来监听指定队列的消息。我们将演示如何接收不同队列的消息,并进行手动确认。

package com.example.rabbitmqspringbootdemo.consumer;import com.example.rabbitmqspringbootdemo.config.RabbitMQConfig;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;import java.io.IOException;@Component
public class MessageConsumer {@RabbitListener(queues = RabbitMQConfig.DIRECT_QUEUE)public void receiveDirectMessage(String message, Channel channel, Message msg) throws IOException {try {System.out.println("Received Direct Message: " + message);// 模拟业务处理// int i = 1/0; // 模拟异常channel.basicAck(msg.getMessageProperties().getDeliveryTag(), false);System.out.println("Direct Message Acknowledged.");} catch (Exception e) {System.err.println("Error processing Direct Message: " + e.getMessage());// 拒绝消息,并重新入队channel.basicNack(msg.getMessageProperties().getDeliveryTag(), false, true);System.err.println("Direct Message Nacked and Requeued.");}}@RabbitListener(queues = RabbitMQConfig.TOPIC_QUEUE_A)public void receiveTopicMessageA(String message, Channel channel, Message msg) throws IOException {try {System.out.println("Received Topic Message A: " + message);channel.basicAck(msg.getMessageProperties().getDeliveryTag(), false);System.out.println("Topic Message Acknowledged.");} catch (Exception e) {System.err.println("Error processing Topic Message A: " + e.getMessage());channel.basicNack(msg.getMessageProperties().getDeliveryTag(), false, true);System.err.println("Topic Message Nacked and Requeued.");}}@RabbitListener(queues = RabbitMQConfig.TOPIC_QUEUE_B)public void receiveTopicMessageB(String message, Channel channel, Message msg) throws IOException {try {System.out.println("Received Topic Message B: " + message);channel.basicAck(msg.getMessageProperties().getDeliveryTag(), false);System.out.println("Topic Message B Acknowledged.");} catch (Exception e) {System.err.println("Error processing Topic Message B: " + e.getMessage());channel.basicNack(msg.getMessageProperties().getDeliveryTag(), false, true);System.err.println("Topic Message Nacked and Requeued.");}}@RabbitListener(queues = RabbitMQConfig.FANOUT_QUEUE_1)public void receiveFanoutMessage1(String message, Channel channel, Message msg) throws IOException {try {System.out.println("Received Fanout Message 1: " + message);channel.basicAck(msg.getMessageProperties().getDeliveryTag(), false);System.out.println("Fanout Message 1 Acknowledged.");} catch (Exception e) {System.err.println("Error processing Fanout Message 1: " + e.getMessage());channel.basicNack(msg.getMessageProperties().getDeliveryTag(), false, true);System.err.println("Fanout Message 1 Nacked and Requeued.");}}@RabbitListener(queues = RabbitMQConfig.FANOUT_QUEUE_2)public void receiveFanoutMessage2(String message, Channel channel, Message msg) throws IOException {try {System.out.println("Received Fanout Message 2: " + message);channel.basicAck(msg.getMessageProperties().getDeliveryTag(), false);System.out.println("Fanout Message 2 Acknowledged.");} catch (Exception e) {System.err.println("Error processing Fanout Message 2: " + e.getMessage());channel.basicNack(msg.getMessageProperties().getDeliveryTag(), false, true);System.err.println("Fanout Message 2 Nacked and Requeued.");}}
}

3.5 消息确认机制 (ACK)

在上述消费者代码中,我们通过 Channel 对象实现了消息的手动确认。这是确保消息可靠性的关键步骤。

  • channel.basicAck(deliveryTag, multiple):用于肯定确认消息。deliveryTag 是消息的唯一标识,multiple 参数表示是否批量确认。设置为 false 表示只确认当前消息。
  • channel.basicNack(deliveryTag, multiple, requeue):用于否定确认消息。requeue 参数表示是否将消息重新放回队列。设置为 true 表示重新入队,false 表示丢弃或发送到死信队列(如果配置了)。
  • channel.basicReject(deliveryTag, requeue):与 basicNack 类似,但不支持批量操作。

通过手动确认机制,我们可以根据业务处理结果灵活地控制消息的生命周期。例如,在业务处理成功后进行 basicAck,在处理失败时进行 basicNack 并选择是否重新入队,从而避免消息丢失或重复消费的问题。

4. 总结

RabbitMQ 作为一款成熟稳定的消息中间件,在分布式系统、微服务架构中扮演着举足轻重的角色。它不仅能够帮助我们实现系统解耦、异步通信,还能有效应对高并发、削峰填谷等挑战,提升系统的整体性能和稳定性。

在实际项目中,除了本文介绍的基本概念和工作模式,RabbitMQ 还提供了许多高级特性,例如死信队列(Dead Letter Exchange)、延迟队列、消息优先级等,这些特性可以帮助我们构建更加健壮和灵活的消息系统。

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

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

相关文章

代账行业数字化破局:从“知道”到“做到”,三步走稳赢!

认知&#xff01;降本&#xff01;增收&#xff01;数字化&#xff01;——这不仅是口号&#xff0c;更是代账行业在激烈竞争和时代变化中生存发展的关键。很多代账同行其实都明白趋势&#xff0c;也知道大概该怎么做。但问题卡在第一步&#xff1a;不知道怎么开始&#xff0c;…

Mac 电脑crontab执行定时任务【Python 实战】

1、crontab -e 编辑定时任务列表 crontab -e查看当前定时任务列表,长按 i 编辑,编辑完之后按 esc 退出编辑,然后输入:wq 保存并提出。 如下: (base) charles@zl ~ % crontab -e58 15 * * * /Library/Frameworks/Python.framework/Versions/3.8/bin/python3 /Users/charle…

go go go 出发咯 - go web开发入门系列(三) 项目基础框架搭建与解读

go go go 出发咯 - go web开发入门系列&#xff08;三&#xff09; 项目基础框架搭建与解读 往期回顾 go go go 出发咯 - go web开发入门系列&#xff08;一&#xff09; helloworldgo go go 出发咯 - go web开发入门系列&#xff08;二&#xff09; Gin 框架实战指南 前言 如…

【字节跳动】数据挖掘面试题0014:SQL中count(1), count(*), count(列)区别

文章大纲SQL 中 count(1)、count(*)、count(某列) 的区别一、核心定义与行为差异二、示例说明差异三、性能差异与优化四、适用场景建议五、面试应答要点六、索引扫描与全表扫描1. 索引扫描的触发条件2. 全表扫描的适用场景3. 常见面试问题点Q1&#xff1a;索引扫描一定比全表扫…

Linux面试问题-软件测试

1、你在上一家公司常用的Linux命令有哪些&#xff1f;答&#xff1a;使用vim/vi编辑文件&#xff0c;使用cat&#xff0c;more,less&#xff0c;head查看文件&#xff0c;使用grep过滤日志中的error,使用ps查看进程&#xff0c;使用top查看实时进程&#xff0c;netstat查看端口…

时序数据库的存储之道:从数据特性看技术要点

时序数据的独特挑战时序数据(Time-Series Data)是指按时间顺序记录的一系列数据点&#xff0c;在物联网、金融、工业监控等领域无处不在。与传统数据相比&#xff0c;时序数据具有几个鲜明特点&#xff1a;时间导向性&#xff1a;每个数据点都带有精确的时间戳高写入量&#xf…

【vim中替换】

vim中替换1 : s/在Vim中经常高频使用到的命令&#xff1a;1 : s/ &#xff1a;s 命令的基本语法是 :[range]s/{pattern}/{string}/[flags]&#xff0c;其中&#xff1a; • [range] 是可选的范围&#xff0c;用于指定替换的行范围。例如&#xff0c;% 表示全文&#xff0c;10,…

Qt实战:使用QSqlDatabase连接MySQL,并实现增删改查

文章目录一、创建数据表二、连接MySQL数据库三、封装成一个完整的轻量级 ORM 风格类四、实现派生具体模型类五、支持多线程连接池 ORM 事务封装一、创建数据表 数据库名&#xff1a; 我们先创建一个数据库&#xff0c;名字叫 game_db&#xff1a; CREATE DATABASE IF NOT E…

Python脚本保护工具库之pyarmor使用详解

概要 PyArmor是一个专门为Python代码提供加密保护的第三方库,旨在解决Python源代码易被反编译和泄露的安全问题。作为一种动态代码保护工具,PyArmor能够对Python脚本进行混淆和加密处理,有效防止源代码被恶意获取、分析或篡改。该库特别适用于商业软件开发、知识产权保护和…

仓颉编程语言:从入门到精通

为啥要瞅瞅仓颉这玩意儿&#xff1f; 有一说一&#xff0c;现在的编程语言多得跟米一样&#xff0c;对吧&#xff1f;那一门新语言想火&#xff0c;没点绝活儿肯定不行。仓颉&#xff08;Cangjie&#xff09;这哥们儿&#xff0c;是华为搞出来的新玩意儿&#xff0c;静态编译的…

线性探针是什么:是一种用于探测神经网络中特定特征的工具

线性探针是什么 线性探针是一种在机器学习和相关领域广泛应用的技术,用于评估预训练模型特征、检测数据中的特定序列等。在不同的应用场景下,线性探针有着不同的实现方式和作用: 评估预训练模型特征:在机器学习中,线性探针是一种评估预训练模型“特征迁移能力”的标准化方…

【论文阅读】Few-Shot PPG Signal Generation via Guided Diffusion Models

从少量样本数据选择到后处理的整体框架。首先,扩散模型在N样本数据集和指导下的训练。接着,模型生成一个增强的数据集,并进一步优化以提高保真度。最后,这些合成数据与少量样本训练数据集结合,用于基准模型的训练和评估。数据分布从最初的红色变为保真度增强的蓝色,这表明…

CentOS-7的“ifupdown“与Debian的“ifupdown“对比 笔记250706

CentOS-7的"ifupdown"与Debian的"ifupdown"对比 笔记250706 CentOS 7 和 Debian 的 ifupdown 工具名称相同&#xff0c;但在实现机制、配置文件语法和系统集成上存在显著差异。以下是核心对比分析&#xff1a; ⚙️ 一、核心差异概览 对比维度CentOS 7De…

架构如传承:技术长河中的可持续乐章

代码结构&#xff1a;协作基石 在软件开发的世界里&#xff0c;代码结构就如同建筑的框架&#xff0c;支撑着整个项目的运行。想象一下&#xff0c;你加入了一个新的开发团队&#xff0c;接手一个已经有一定规模的项目。当你打开代码库&#xff0c;看到的是一团乱麻般的代码&a…

Ubuntu22.04更新Openssh至9.9p2无法正常连接,报错解决

Ubuntu22.04更新Openssh至9.9p2无法正常连接&#xff0c;报错解决 1.报错信息如下所示ExecStart/usr/sbin/sshd -D $SSHD_OPTS (codeexited, status255/EXCEPTION)2.这通常说明 SSH 配置文件存在语法错误、缺失关键文件&#xff0c;或者端口被占用等问题。 3.检查配置文件是否有…

基于小程序的智能停车管理系统设计与开发

项目介绍 本课程演示的是一款基于小程序的智能停车管理系统设计与开发&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的 Java 学习者。 1.包含&#xff1a;项目源码、项目文档、数据库脚本、软件工具等所有资料 2.带你从零开始部署运行本套系统 3…

多模态大语言模型arxiv论文略读(155)

Panther: Illuminate the Sight of Multimodal LLMs with Instruction-Guided Visual Prompts ➡️ 论文标题&#xff1a;Panther: Illuminate the Sight of Multimodal LLMs with Instruction-Guided Visual Prompts ➡️ 论文作者&#xff1a;Honglin Li, Yuting Gao, Chengl…

SAP ERP与Oracle EBS对比,两个ERP系统有什么区别?

据统计&#xff0c;2024年中国ERP软件市场规模预计突破210亿元&#xff0c;其中SAP和Oracle占据第一梯队&#xff0c;共占国内ERP市场45%以上的份额&#xff0c;在高端市场尤其显著。SAP和Oracle作为ERP行业的两大巨头&#xff0c;具体有什么区别呢&#xff1f;SAP是什么&#…

网络安全之RCE分析与利用详情

Gogs背景介绍Gogs&#xff08;Go Git Service&#xff09;是一款用Go语言编写的轻量级、开源的Git仓库托管系统。它的设计目标是让搭建和维护Git服务变得简单、快速&#xff0c;同时提供类似GitHub的功能&#xff0c;但对资源消耗更少&#xff0c;适合个人或者小型团队使用&…

OpenCV图片操作100例:从入门到精通指南(2)

接上篇&#xff0c;本文将继续分享OpenCV实用技巧&#xff0c;涵盖图像处理、目标检测、3D视觉等进阶领域&#xff01;六、图像变换进阶17. 图像金字塔# 高斯金字塔下采样 smaller cv2.pyrDown(img)# 高斯金字塔上采样 larger cv2.pyrUp(img)用于多尺度图像处理&#xff0c;构…