RabbitMQ从入门到实战-知识详情总结

一、简介

RabbitMQ 是一个基于 AMQP(Advanced Message Queuing Protocol,高级消息队列协议)的消息中间件,它用于异步通信、解耦系统,提高系统的可扩展性和可靠性。它广泛应用于微服务架构、分布式系统、异步处理等场景。


1.RabbitMQ 的特点

  1. 支持多种协议:支持 AMQP 0.9.1、AMQP 1.0、MQTT、STOMP 等。
  2. 高可用性:支持集群部署、镜像队列等机制,确保消息不丢失。
  3. 可靠性:通过持久化、ACK 机制、事务支持等方式保证消息可靠传输。
  4. 灵活的路由机制:通过交换机(Exchange)和绑定(Binding)策略,实现复杂的消息路由规则。
  5. 插件机制:提供插件扩展,如管理插件、消息追踪插件、WebSocket 支持等。
  6. 轻量级、易于部署:基于 Erlang 语言开发,占用资源较低,支持 Docker、Kubernetes 部署。

2.RabbitMQ 的优缺点

(1)优点:
  1. 支持多种消息模式(点对点、发布订阅、路由等)。
  2. 社区活跃,生态丰富,官方提供大量插件,支持多种语言客户端(Java、Python、Go 等)。
  3. 支持消息持久化,即使服务器重启,消息也不会丢失。
  4. 支持集群模式,可扩展性强,适用于分布式环境。
  5. 性能较好,适用于中小型业务场景。
(2)缺点:
  1. 吞吐量较低:相比 Kafka,RabbitMQ 的吞吐量较低,不适合超大规模数据流处理。
  2. 管理运维复杂:需要手动管理队列、交换机、绑定等,运维成本相对较高。
  3. Erlang 生态较小,部分企业的运维人员对 Erlang 语言不熟悉,可能影响问题排查。
  4. 资源占用较大:在高并发场景下,RabbitMQ 的内存占用较多,可能影响性能。

二、核心组件

1. 生产者(Producer)

  • 负责发送消息到 RabbitMQ,消息最终会进入队列(Queue)。
  • 生产者不会直接把消息发到队列,而是先发送到交换机(Exchange)

2. 交换机(Exchange)

  • 负责接收生产者发送的消息,并根据绑定规则决定消息流向。
  • 交换机类型:
    • Direct(直连交换机):消息按照指定的 routing key 发送到匹配的队列。
    • Fanout(扇形交换机):消息广播到所有绑定的队列,不考虑 routing key
    • Topic(主题交换机):按模式匹配 routing key,适用于模糊匹配的场景(如 logs.*)。
    • Headers(头交换机):根据消息的 Header 属性进行路由。

3. 队列(Queue)

  • 存储消息,等待消费者(Consumer)消费。
  • 可配置是否持久化(避免 RabbitMQ 重启后消息丢失)。
  • 可设置死信队列(DLX),当消息超时未消费或被拒绝时,进入死信队列做后续处理。

4. 绑定(Binding)

  • 连接交换机和队列的桥梁,决定消息如何流转。
  • 绑定通常通过 routing key 进行匹配,或基于 headers 进行匹配。

5. 消费者(Consumer)

  • 从队列中获取消息并进行处理。
  • 支持手动 ACK 机制(保证消息消费后才被确认,避免丢失)。

6. 虚拟主机(Virtual Host,VHost)

  • RabbitMQ 服务器的逻辑隔离机制,不同 VHost 之间的消息互不影响。
  • 每个 VHost 维护自己的队列、交换机和权限控制。

7. RabbitMQ 连接(Connection)和信道(Channel)

  • Connection:TCP 连接,生产者和消费者都需要建立连接。
  • Channel:RabbitMQ 的逻辑连接,多个 Channel 共享一个 Connection,减少 TCP 连接开销。

三、交换机类型

RabbitMQ 中的交换机(Exchange)是消息路由的核心,决定了消息的流向。根据不同的路由策略,RabbitMQ 提供了几种交换机类型:

1. Direct Exchange(直连交换机)

  • 特点:消息通过交换机按 routing key 路由到匹配的队列。
  • 适用场景:精确匹配的消息路由。

在 Direct Exchange 中,生产者发送消息时指定 routing key,只有与队列绑定时的 routing key 完全匹配的队列会接收到消息。


2. Fanout Exchange(扇形交换机)

  • 特点:不关心 routing key,将消息广播到所有绑定的队列。
  • 适用场景:需要将消息发送给多个消费者或多个队列的场景(例如广播通知)。

Fanout Exchange 将消息发送到所有绑定的队列,完全不考虑 routing key


3. Topic Exchange(主题交换机)

  • 特点:使用 routing key 的模式匹配功能,支持通配符(*#)来路由消息。
  • 适用场景:根据模式灵活路由消息,比如日志系统中的级别过滤(如 logs.infologs.error)。

Topic Exchange 根据绑定的 routing key 模式进行匹配,* 匹配一个词,# 匹配多个词。比如 logs.info 匹配所有包含 logs.info 的消息。


4. Headers Exchange(头交换机)

  • 特点:通过匹配消息的 Header 信息进行路由,而不是 routing key
  • 适用场景:需要通过多个属性来过滤消息的情况,比如消息头包含多个属性,灵活的路由机制。

Headers Exchange 根据消息的 header 信息来路由,常用于需要灵活多维度匹配的场景。


总结

  • Direct Exchange:用于精确匹配的场景。
  • Fanout Exchange:用于广播场景,所有绑定的队列都会接收到消息。
  • Topic Exchange:用于模式匹配,灵活路由。
  • Headers Exchange:通过 header 属性进行路由,灵活多维度匹配。

四、创建方式

在 RabbitMQ 中,队列(Queue)、交换机(Exchange)、和绑定(Binding)是消息传递的核心元素。下面我会介绍它们在 Spring Boot 中的创建方式,包括使用 配置类注解方式

1.配置类方式

在配置类中,可以通过 @Bean 注解来声明队列、交换机和绑定关系。

@Configuration
public class RabbitConfig {// 创建队列@Beanpublic Queue queue() {return new Queue("myQueue", true);}// 创建 Direct 交换机@Beanpublic DirectExchange directExchange() {return new DirectExchange("directExchange");}// 创建绑定@Beanpublic Binding binding(Queue queue, DirectExchange exchange) {return BindingBuilder.bind(queue).to(exchange).with("myRoutingKey");}
}
  • 队列(Queue):可以使用 @Bean 在配置类中声明。
  • 交换机(Exchange):同样使用 @Bean 创建,通常使用 DirectExchangeFanoutExchangeTopicExchange 等不同类型。
  • 绑定(Binding):队列和交换机的绑定通过 BindingBuilder.bind(queue).to(exchange).with(routingKey) 来配置。

2.注解方式

在 Spring Boot 中,@RabbitListener 注解可以用来监听 RabbitMQ 队列,并且可以通过 @QueueBinding 直接在注解中完成 队列、交换机、绑定 的声明(如果不存在就会创建)。

@RabbitListener(bindings = @QueueBinding(value = @Queue(name = "coupon.order.queue1", durable = "true"), // 队列exchange = @Exchange(name = "coupon.order.exchange", type = ExchangeTypes.DIRECT, durable = "true"), // 交换机key = {"coupon"} // 绑定的 routing key
))
public void receiveMessage(String message) {System.out.println("Received: " + message);
}

注意:RabbitMQ 默认发送和接收的消息是 字节数组(byte[]),在 Spring Boot 中,默认是字符串,但如果发送的是对象,需要手动配置 JSON 序列化 才能正确转换。需要在生产者和消费者都添加Bean注解:

@Beanpublic MessageConverter jacksonMessageConvertor() {return new Jackson2JsonMessageConverter();}

五、生产者确认机制

RabbitMQ 生产者确认机制(Publisher Confirms)用于确保消息从生产者 正确投递到交换机,并且 从交换机正确路由到队列,防止消息丢失。


1. 生产者确认机制的三种模式

首先配置.yml文件或.properties文件:

spring:rabbitmq:# 生产者网络连接失败重试设置,不建议使用template:retry:enabled: true        # 开启消费者重试(抛出异常时触发)initial-interval: 400ms  # 首次重试间隔multiplier: 1        # 间隔倍数(1表示固定间隔)max-attempts: 3      # 最大重试次数(含首次消费)# 生产者确认机制(可靠性保障关键配置)publisher-confirm-type: none  # 开启消息确认publisher-returns: correlated     # 开启消息路由失败通知

(1)ConfirmCallback(消息到达交换机,Exchange)

作用: 确保消息 成功到达交换机(Exchange)。

如果消息 到达交换机,返回 ack=true;如果 未到达交换机(如交换机不存在),返回 ack=false 并提供 cause 错误信息。

实现方式:

rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {if (ack) {System.out.println("消息成功到达交换机!");} else {System.out.println("消息未到达交换机,原因:" + cause);}
});

(2)ReturnCallback(消息未投递到队列,Queue)

作用: 确保消息 正确路由到队列。

如果消息没有匹配任何队列(比如 routingKey 错误),触发 ReturnCallback

实现方式:

rabbitTemplate.setMandatory(true); // 必须设置为 true,否则不会触发回调rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {System.out.println("消息未投递到队列!");System.out.println("交换机:" + exchange);System.out.println("路由键:" + routingKey);System.out.println("错误代码:" + replyCode + ",错误信息:" + replyText);
});

(3)事务模式(不推荐)

作用: 生产者使用事务 channel.txCommit() 进行消息投递,失败时回滚 channel.txRollback()。但性能较低,不推荐使用。

 以上三种方法都能确认生产者的消息成功送达,但会带来系统额外的性能开销,因此都不推荐使用。非要使用,RabbitMQ 官方建议使用 ConfirmCallback。 

六、消费者确认机制

RabbitMQ 的消费者确认机制默认为none,这种模式下,RabbitMQ 不会要求消费者确认消息,消息会在消费者接收到时直接从队列中删除。 消费者确认机制(Consumer Acknowledgements)用于确保消息被正确消费,避免消息丢失或重复消费。主要有两种模式:

  1. 自动确认(autoAck = true)(不推荐,可能丢失消息)
  2. 手动确认(autoAck = false)(推荐,确保消息被正确处理)

1. 自动确认模式(Auto ACK)

默认情况下,RabbitMQ 采用自动确认,即消费者 收到消息后立即确认,无论业务逻辑是否执行成功,RabbitMQ 都会从队列中删除该消息。

代码示例:
@RabbitListener(queues = "testQueue")
public void receiveMessage(String message) {System.out.println("收到消息:" + message);int result = 1 / 0;  // 业务代码出错
}

消息 刚到达消费者 就被 RabbitMQ 删除,即使消费者 还没处理,也不会重新投递,导致消息丢失

2. 手动确认模式(Manual ACK)

手动确认允许消费者 成功处理消息后再手动确认,并可选择拒绝或重新入队,保证消息不会丢失。

代码示例:
@RabbitListener(queues = "testQueue", ackMode = "MANUAL")
public void receiveMessage(Message message, Channel channel) throws IOException {long deliveryTag = message.getMessageProperties().getDeliveryTag();try {System.out.println("收到消息:" + new String(message.getBody()));// 业务处理逻辑processMessage(new String(message.getBody()));// 处理成功,手动确认channel.basicAck(deliveryTag, false);} catch (Exception e) {System.out.println("消息处理失败:" + e.getMessage());// 处理失败,拒绝消息并重新入队channel.basicNack(deliveryTag, false, true);}
}

手动确认的三种方式如下:

方法作用说明
channel.basicAck(deliveryTag, false);确认消息处理成功后,通知 RabbitMQ 删除消息
channel.basicNack(deliveryTag, false, true);拒绝消息,并重新入队处理失败时,消息会重新回到队列
channel.basicReject(deliveryTag, false);拒绝消息,不重新入队直接丢弃消息

七、消息持久化

在 RabbitMQ 中,消息持久化是为了确保消息在 RabbitMQ 服务器崩溃重启 后,仍然能够被恢复。持久化可以保证即使 RabbitMQ 意外停止,消息不会丢失。

  • 临时消息(non-persistent): 默认情况下,消息在内存中存储,RabbitMQ 重启后会丢失。
  • 持久化消息(persistent): 消息存储在磁盘上,RabbitMQ 重启后可以恢复。

RabbitMQ 消息持久化涉及两个方面:

  1. 队列持久化
  2. 消息持久化
  3. 路由器持久化

1.设置队列和交换机持久化 

在声明队列和交换机时,需要设置队列的 durable 属性为 true,表示队列是持久化的。

@Beanpublic DirectExchange dlxExchange() {return new DirectExchange("dlx.exchange", true, false);}@Beanpublic Queue dlxQueue() {return new Queue("dlx.queue", true);}

2.设置消息持久化

为了使消息持久化,需要将消息的 deliveryMode 设置为 2(持久化)。如果是通过 Spring AMQP 发送消息,可以通过 MessagePostProcessor 来设置消息持久化。

通过 Spring AMQP 设置消息持久化:
@Autowired
private RabbitTemplate rabbitTemplate;public void sendMessage(String message) {MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {@Overridepublic Message postProcessMessage(Message message) throws AmqpException {// 设置消息持久化message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);return message;}};rabbitTemplate.convertAndSend("myExchange", "myRoutingKey", message, messagePostProcessor);
}

八、延迟队列的实现

RabbitMQ 中实现 延迟队列(Delay Queue)通常有两种方式,分别是通过 插件 和 死信队列+TTL(Time-To-Live) 实现。延迟队列允许消息在特定的时间延迟后再被消费,通常用于实现任务调度、定时任务等功能。

(1)不用插件:

通过死信队列+TTL实现的。首先,我们将消息放到延迟队列中,将TTL设置为延迟时间,消息在TTL过期之后会被转发到死信交换机,再路由到死信队列,接着消费者到死信队列中消费消息,从而达到延迟消费的作用。

通过配置类创建死信交换机和死信队列,设置绑定和TTL 

@Configuration
public class DlxConfig {// 死信交换机@Beanpublic DirectExchange dlxExchange() {return new DirectExchange("dlx.exchange", true, false);}// 死信队列@Beanpublic Queue dlxQueue() {return new Queue("dlx.queue", true);}// 死信队列绑定@Beanpublic Binding dlxBinding() {return BindingBuilder.bind(dlxQueue()).to(dlxExchange()).with("dlx");}// 延迟交换机和队列@Beanpublic DirectExchange delayExchange() {return new DirectExchange("delay.exchange", true, false);}// 延迟队列@Beanpublic Queue delayQueue() {return QueueBuilder.durable("delay.queue").deadLetterExchange("dlx.exchange")  // 绑定死信交换机.deadLetterRoutingKey("dlx")         // 设置死信路由键.ttl(10 * 1000)                      // 设置消息TTL为10秒.build();}// 延迟队列和交换机绑定@Beanpublic Binding delayBinding() {return BindingBuilder.bind(delayQueue()).to(delayExchange()).with("delay.routing.key");}
}

 测试类,发送消息到延迟队列:

@Test
public void test4() {LocalTime now = LocalTime.now(); // 获取当前时间DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss"); // 格式化String msg = "hello, amqp";// 通过交换机发送消息rabbitTemplate.convertAndSend("delay.exchange", "delay.routing.key", msg);System.out.println("发送时间为:" + now.format(formatter));
}

 消费者,在死信队列中消费:

@RabbitListener(queues = "dlx.queue")
public void DelayedMessage(String msg) {LocalTime now = LocalTime.now(); // 获取当前时间DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss"); // 格式化System.out.println("收到延迟消息时间为:" + now.format(formatter));
}

(2)使用插件:rabbitmq_delayed_message_exchange

消息发送到延迟交换机时,携带X-delay头(延迟时间),插件将消息暂存在内部数据库中,不会立即路由到队列,到期后将消息路由到目标队列; 

直接在消费者的监视器注解上配置信息:

// 使用插件的 延迟队列声明@RabbitListener(bindings = @QueueBinding(value = @Queue(name = "delay.queue", durable = "true"),exchange = @Exchange(name = "delay.exchange", delayed = "true"),key = "delay"))public void DelayMessage(String msg) {LocalTime now = LocalTime.now(); // 获取当前时间DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss"); // 格式化System.out.println("收到延迟消息时间为:" + now.format(formatter));}

 测试类,设置TTL:

// 测试有延迟插件的 延迟队列@Testpublic void test5() {LocalTime now = LocalTime.now(); // 获取当前时间DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss"); // 格式化String msg = "hello, amqp";rabbitTemplate.convertAndSend("delay.exchange", "delay", msg, new MessagePostProcessor() {@Overridepublic Message postProcessMessage(Message message) throws AmqpException {message.getMessageProperties().setDelay(10 * 1000);   // 设置过期时间return message;}});System.out.println("第一次发送时间为:" + now.format(formatter));}

(3)使用插件和不使用插件的区别: 

  1. 消息存储位置:没有插件的需要额外创建一个队列用来存储消息,而有插件的将消息暂存在Mnesia数据库中,占用资源较低;
  2. 性能和误差:没有插件的需要轮询检查消息TTL(默认是每秒检查一次),而有插件的使用 Erlang Timer 模块 或 时间轮算法管理延迟时间,性能比较好,延迟误差较小;
  3. 消息阻塞:没有插件的队列只会检查对头消息是否过期,会导致后续消息被阻塞;而有插件的对每个消息的延迟独立计算,到期后立即触发路由;

九、消息重复消费的解决

消息重复消费是一个常见的 RabbitMQ 消息队列问题,它可能会影响系统的准确性和性能。为了解决消息重复消费的问题,可以采取以下几种策略:

1)开启消费者确认机制(手动ack或自动ack确保每次消费者成功处理完消息后,发送一个信号给RabbitMQ, 如果消费者处理失败,重新排队未确认的消息;

@RabbitListener(queues = "order.payment.queue", ackMode = "MANUAL")
public void handlePaymentMessage(Message message, Channel channel) {String orderId = new String(message.getBody());try {// 处理订单支付orderService.processPayment(orderId);// 确认消息已成功消费channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);} catch (Exception e) {// 处理失败,拒绝消息,并不重新入队channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);}
}

(2)核心思想是保证幂等性:无论消息被消费多少次,结果与一次消费相同:

1.设置唯一标识(消息ID),将消息ID放到缓存中,每次消费前根据消息ID是否已存在来判定消息是否处理过;

@Autowired
private StringRedisTemplate redisTemplate;@RabbitListener(queues = "order.payment.queue")
public void handlePaymentMessage(String orderId) {String messageId = "payment:" + orderId;if (redisTemplate.opsForSet().isMember("processedMessages", messageId)) {return;  // 如果消息已经处理过,跳过}// 处理订单支付orderService.processPayment(orderId);// 将消息ID存入 Redis,防止重复消费redisTemplate.opsForSet().add("processedMessages", messageId);
}

2.利用数据库的唯一索引、主键的约束防止消息重复消费; 

    在消费者代码中,如果订单已存在(即订单ID已经在数据库中),则跳过该消息,从而避免重复消费:

    @RabbitListener(queues = "order.payment.queue")
    public void handlePaymentMessage(String orderId) {try {// 检查订单是否已支付(通过唯一索引判断是否重复消费)if (orderService.isOrderPaid(orderId)) {return;  // 如果订单已经支付,则跳过该消息}// 处理订单支付orderService.processPayment(orderId);} catch (Exception e) {// 处理异常e.printStackTrace();}
    }
    

    orderService.isOrderPaid(orderId) 方法中,我们可以查询数据库,检查订单是否已处理:

    public boolean isOrderPaid(String orderId) {// 通过唯一索引查询订单是否存在,如果存在则返回 true,表示已支付return jdbcTemplate.queryForObject("SELECT COUNT(1) FROM order_payment WHERE order_id = ?", Integer.class, orderId) > 0;
    }
    

    3.使用乐观锁,通过版本号或条件判断是否重复消费;

    十、异常消息的处理

    异常消息的处理是消息队列中一个重要的问题,尤其是在 RabbitMQ 中。消息处理过程中,可能会遇到各种异常情况,例如消费者处理消息时发生错误、消息无法被消费等。为了提高系统的可靠性和容错能力,RabbitMQ 提供了多种机制来处理异常消息。

    配置.yml文件或者.properties文件:

    spring:rabbitmq:listener:simple:prefetch: 1  # 每个消费者最大未确认消息数(设为1实现能者多劳模式)acknowledge-mode: auto  # 自动ACK(高风险!建议改为 manual 手动确认,避免消息丢失)retry:enabled: true          # 是否开启发送失败重试(仅针对网络波动场景,不解决业务异常)multiplier: 1          # 重试间隔时间倍数(此处为固定间隔)max-attempts: 3        # 最大重试次数(含首次发送)initial-interval: 200ms # 首次重试间隔

    创建异常交换机和异常队列:

    /*** 定义异常交换机和异常队列* 将异常的消息重试多次失败后,统一放在这个错误队列中,人工处理*/@Configuration
    @ConditionalOnProperty(prefix = "spring.rabbitmq.listener.simple.retry", name = "enabled", havingValue = "true")   // 当这个属性存在且为true,配置类才生效
    public class ErrorConfig {@Beanpublic DirectExchange errorExchange() {return new DirectExchange("error.exchange", true, false);}@Beanpublic Queue errorQueue() {return new Queue("error.queue", true);}@Beanpublic Binding errorBinding() {return BindingBuilder.bind(errorQueue()).to(errorExchange()).with("error");}// 消息异常重试n此后,移到该异常交换机@Beanpublic MessageRecoverer messageRecoverer(RabbitTemplate rabbitTemplate) {return new RepublishMessageRecoverer(rabbitTemplate, "error.exchange", "error");}
    }

    总结

    RabbitMQ 作为轻量级的消息队列,适用于中小型业务场景,具备高可用性、可靠性和灵活的消息路由机制。然而,它的吞吐量不及 Kafka,且运维成本较高。对于需要事务保障、低延迟、灵活消息分发的业务,RabbitMQ 是一个不错的选择。

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

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

    相关文章

    嵌入式八股RTOS与Linux---前言篇

    前言 Linux与RTOS是校招八股的时候很喜欢考察的知识,在这里并没有把两个操作系统完全的独立开去讲,放在一起对比或许可能加深印象。我们讲Linux的内核有五部分组成:进程调度、内存管理、文件系统、网络接口、进程间通信,所以我也将从这五方面出发 中断管理去对比和RTOS的不同。…

    centos 8安装及相关操作

    安装centos 8 在VMware workstation中安装 UEFI对比BIOS有更快的启动速度、支持更大容量硬盘及 GPT 分区、图形化操作界面更友好、安全性更高、对新操作系统支持更好、硬件兼容性不断增强以及扩展性更好等。 按回车确定 重置root管理员密码 这样进入到紧急救援模式 mount -o r…

    2025最新版Windows通过GoLand远程连接Linux构建Go项目保姆级教学

    以Ubuntu24.04和GoLand2024.1.6为例子,演示如何在Windows上通过GoLand远程连接Linux进行Go编程。 通过go version指令可以发现当前Ubuntu系统没有安装go。 go version 通过指令安装go,其他系统可以通过wget安装,要指定安装的具体go版本&…

    多元时间序列预测的范式革命:从数据异质性到基准重构

    本推文介绍了一篇来自中国科学院计算技术研究所等机构的论文《Exploring Progress in Multivariate Time Series Forecasting: Comprehensive Benchmarking and Heterogeneity Analysis》,发表在《IEEE Transactions on Intelligent Transportation Systems》。论文…

    开源PACS(dcm4che-arc-light)部署教程,源码方式

    目录 文件清单下载地址安装概述OpenLDAP、Apache Directory StudioWildflydcm4che 安装部署MySQL源码编译dcm4cheedcm4chee-arc-light OpenLDAP安装ApacheDirectoryStudio安装配置WildFly服务器 部署完成 文件清单 下载地址 Apache directory studio - linkOpenLDAP - linkdcm…

    PySide(PyQt),使用types.MethodType动态定义事件

    以PySide(PyQt)的图片项为例,比如一个视窗的场景底图是一个QGraphicsPixmapItem,需要修改它的鼠标滚轮事件,以实现鼠标滚轮缩放显示的功能。为了达到这个目的,可以重新定义一个QGraphicsPixmapItem类,并重写它的wheelE…

    深度学习 Deep Learning 第1章 深度学习简介

    第1章 深度学习简介 概述 本章介绍人工智能(AI)和深度学习领域,讨论其历史发展、关键概念和应用。解释深度学习如何从早期的AI和机器学习方法演变而来,以及如何有效解决之前方法无法应对的挑战。 关键概念 1. 人工智能的演变 …

    简述下npm,cnpm,yarn和pnpm的区别,以及跟在后面的-g,--save, --save-dev代表着什么

    文章目录 前言一、npm,cnpm,yarn和pnpm的基本介绍和特点1.npm (Node Package Manager)2. Yarn3. cnpm (China npm)4. pnpm 二、简述npm和pnpm 的存储方式和依赖数1.存储方式2.依赖树 三、两者依赖树的差异导致结果的对比四、简单说说-g,--sav…

    vue3系列:vite+vue3怎么配置通过ip和端口打开浏览器

    目录 1.前言 2.修改前的 3.修改后的 4.效果 5.其他 1.前言 想要使用IP端口号的方式访问页面,结果无法访问 查了些资料,原来是vite.config.js需要加一些配置才能让他通过IP访问,默认的只能localhost:端口号访问 2.修改前的 使用vue3默认…

    使用yolov8+flask实现精美登录界面+图片视频摄像头检测系统

    这个是使用flask实现好看登录界面和友好的检测界面实现yolov8推理和展示,代码仅仅有2个html文件和一个python文件,真正做到了用最简洁的代码实现复杂功能。 测试通过环境: windows x64 anaconda3python3.8 ultralytics8.3.81 flask1.1.2…

    突破连接边界!O9201PM Wi-Fi 6 + 蓝牙 5.4 模块重新定义笔记本无线体验

    在当今数字化时代,笔记本电脑已成为人们工作、学习和娱乐的必备工具。而无线连接技术,作为笔记本电脑与外界交互的关键桥梁,其性能的优劣直接关乎用户体验的好坏。当下,笔记本电脑无线连接领域存在诸多痛点,严重影响着…

    2025 香港 Web3 嘉年华:全球 Web3 生态的年度盛会

    自 2023 年首届香港 Web3 嘉年华成功举办以来,这一盛会已成为全球 Web3 领域规模最大、影响力最深远的行业活动之一。2025 年 4 月 6 日至 9 日,第三届香港 Web3 嘉年华将在香港盛大举行。本届活动由万向区块链实验室与 HashKey Group 联合主办、W3ME 承…

    Windows11 新机开荒(二)电脑优化设置

    目录 前言: 一、注册微软账号绑定权益 二、此电脑 桌面图标 三、系统分盘及默认存储位置更改 3.1 系统分盘 3.2 默认存储位置更改 四、精简任务栏 总结: 前言: 本文承接上一篇 新机开荒(一) 上一篇文章地址&…

    [C++面试] 标准容器面试点

    一、入门 1、vector和list的区别 [C面试] vector 面试点总结 vector 是动态数组,它将元素存储在连续的内存空间中。支持随机访问,即可以通过下标快速访问任意位置的元素,时间复杂度为 O(1),准确点是均摊O(1)。但在中间或开头插…

    蓝桥杯每日一题

    丢失的雨伞 题目思路代码演示 题目 今天晚上本来想练习一下前缀和与差分 结果给我搜出来这题(几乎没啥关系),我看半天有点思路但又下不了手哈哈,难受一批 在图书馆直接红温了 题目链接 思路 题目要求找到两个不重叠的区间&…

    校园安全用电怎么保障?防触电装置来帮您

    引言 随着教育设施的不断升级和校园用电需求的日益增长,校园电力系统的安全性和可靠性成为了学校管理的重要课题。三相智能安全配电装置作为一种电力管理设备,其在校园中的应用不仅能够提高电力系统的安全性,还能有效保障师生的用电安全&am…

    Matlab 汽车二自由度转弯模型

    1、内容简介 Matlab 187-汽车二自由度转弯模型 可以交流、咨询、答疑 2、内容说明 略 摘 要 本文前一部分提出了侧偏角和横摆角速度作为参数。描述了车辆运动的运动状态,其中文中使用的参考模型是二自由度汽车模型。汽车速度被认为是建立基于H.B.Pacejka的轮胎模…

    OpenCV计算摄影学(20)非真实感渲染之增强图像的细节函数detailEnhance()

    操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 此滤波器增强特定图像的细节。 cv::detailEnhance用于增强图像的细节,通过结合空间域和频率域的处理,提升图像中特定细节…

    Java面试八股—Redis篇

    一、Redis的使用场景 (一)缓存 1.Redis使用场景缓存 场景:缓存热点数据(如用户信息、商品详情),减少数据库访问压力,提升响应速度。 2.缓存穿透 正常的访问是:根据ID查询文章&…

    2025-03-17 Unity 网络基础1——网络基本概念

    文章目录 1 网络1.1 局域网1.2 以太网1.3 城域网1.4 广域网1.5 互联网(因特网)1.6 万维网1.7 小结 2 IP 地址2.1 IP 地址2.2 端口号2.3 Mac 地址2.4 小结 3 客户端与服务端3.1 客户端3.2 服务端3.3 网络游戏中的客户端与服务端 1 网络 ​ 在没有网络之前…