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

【MQ篇】RabbitMQ之消费失败重试!

在这里插入图片描述

目录

    • 引言:消息不丢是底线,失败了优雅重试是修养!
    • 消费失败了,为啥不能老是原地复活?🤔
    • 智能重试策略一:本地重试(Spring Retry 的魔法)🏠✨
    • 智能重试策略二:失败策略 - 重试耗尽后“送去劳改”还是“发配边疆”?📚👨‍🔧
    • 策略三:经纪人端延迟重试(DLX + TTL 深度剖析)📦⏳📬
    • 总结:如何构建可靠的消息消费策略?✨🛡️

🌟我的其他文章也讲解的比较有趣😁,如果喜欢博主的讲解方式,可以多多支持一下,感谢🤗!

🌟了解 MQ 请看 : 【MQ篇】初识MQ!

其他优质专栏: 【🎇SpringBoot】【🎉多线程】【🎨Redis】【✨设计模式专栏(已完结)】…等

如果喜欢作者的讲解方式,可以点赞收藏加关注,你的支持就是我的动力
✨更多文章请看个人主页: 码熔burning

引言:消息不丢是底线,失败了优雅重试是修养!

各位技术界的同仁们,大家好!👋 咱们之前聊了 RabbitMQ 消息不丢的“三板斧”(生产者确认、持久化、消费者确认),构建了一个消息安全的“铜墙铁壁”。但是,消息安全送达了,不代表它就能被“快乐地”消费掉!有时候,消费者在处理消息时会遇到各种“糟心事儿”——数据库崩了、下游服务挂了、消息格式错了… 一言不合就抛异常!⛈️

了解RabbitMQ消息不丢的“三板斧”请看:
【MQ篇】RabbitMQ的生产者消息确认实战!
【MQ篇】RabbitMQ之消息持久化!
【MQ篇】RabbitMQ的消费者确认机制实战!

这时候问题就来了:如果消费者处理失败了,这条消息该咋办?直接丢了?那业务就断了!重新发给它?如果消费者一直“病着”,或者消息本身就有问题,那就会出现很多可怕场景:
image-20210718172746378

瞧瞧这图,消息在队列里和消费者之间“魔鬼”般地反复横跳,处理次数飙升,MQ 压力山大,系统岌岌可危!😱 这就是简单地把失败消息 requeue=true(重新入队)可能带来的灾难——无限循环重试,尤其对那种永远无法处理成功的 “毒药消息”(Poison Message) ☠️✉️ 来说,简直是噩梦!

所以,今天咱们就来聊聊,如何优雅地处理消费失败,搭建一套智能的消费失败重试机制,让消息处理变得更健壮!

消费失败了,为啥不能老是原地复活?🤔

简单地 requeue 会导致无限循环。这就像一个得了感冒的快递员(消费者)反复去送一个“烫手”的包裹(有问题的消息)。每次去都被烫一下,回来,再被派去,再被烫… 结果就是快递员累垮了(消费者资源耗尽或阻塞),包裹也没送出去,其他等着送的包裹也耽误了。

咱们需要根据失败的原因和预期,采取不同的重试策略:

  • 临时性错误 (Transient Errors): 比如网络短暂抖动、数据库连接瞬断。这种错误等一会儿可能就恢复了,适合快速重试。
  • 永久性错误 (Permanent Errors): 比如消息格式错误、业务数据非法。这种错误无论重试多少次都不会成功。
  • 外部依赖错误: 比如调用第三方服务失败。这种错误可能需要等待较长时间让依赖恢复。

所以,咱们需要更精细的控制手段!

智能重试策略一:本地重试(Spring Retry 的魔法)🏠✨

“本地重试”顾名思义,就是消费者收到消息后,在把处理结果告诉 RabbitMQ 之前,先在自己家里(应用程序内部)多努力几次。💪 就像快递员拿到包裹后,发现地址有点模糊,他不会立刻把包裹退回邮局,而是在附近多转几圈,多问问人,尝试自己解决。

Spring Boot 集成了强大的 Spring Retry 框架,让实现本地重试变得异常简单!你只需要在配置文件里动动手指头:

application.yml 中为 Simple 监听容器开启 Retry:

# application.yml
spring:rabbitmq:listener:simple: # 或者 container type: direct 等,取决于你用的容器类型retry:enabled: true # ⭐ 开启消费者失败重试!魔法生效!🧙‍♂️initial-interval: 1000ms # ⭐ 初次失败后等待 1 秒重试multiplier: 2 # ⭐ 下次等待时长 = multiplier * 上次等待时长。这是指数退避!⏳max-attempts: 3 # ⭐ 最多重试 3 次 (包括第一次处理失败)stateless: true # ⭐ 无状态重试。通常用这个,除非涉及到跨方法调用的复杂事务

这配置简直是傻瓜式!😅

  • enabled: true:一键开启重试功能!
  • initial-interval & multiplier:配置重试的等待时间策略。上面的例子就是经典的指数退避:第一次失败等 1 秒,第二次等 1 * 2 = 2 秒,第三次等 2 * 2 = 4 秒…(直到 max-attempts 或达到 Spring Retry 默认的最大等待时间)。这样可以避免失败后立即重试给依赖服务造成过大压力。你也可以设置 multiplier: 1 实现固定间隔重试。
  • max-attempts:设置最大重试次数(注意,这个次数是总尝试次数,包括第一次失败)。比如设为 3,意味着“1次初次尝试 + 最多 2 次重试”。

本地重试的结果:

  • 如果在 max-attempts 内,消费者成功处理了消息:Spring Retry 拦截器会认为处理成功,消息监听容器最终会向 RabbitMQ 发送 ACK,消息从队列中删除。🥳
  • 如果在 max-attempts 后,消费者依然失败:Spring Retry 拦截器会放弃重试,并向上抛出一个 AmqpRejectAndDontRequeueException 异常(或者其他表示重试耗尽的异常)。这时候,消息监听容器的错误处理器就会介入,根据默认策略或者你的自定义配置来处理这条消息。在默认情况下,消息会被丢弃。这是因为 Spring AMQP 默认的错误处理器 RejectAndDontRequeueRecoverer 会对异常消息执行 Reject 并设置 requeue=false,通常这会导致消息被 RabbitMQ 丢弃(如果没有配置 DLX)。

本地重试的优缺点:

  • 优点: 配置简单,对于瞬时错误处理效率高,不增加 RabbitMQ broker 的负担。
  • 缺点: 重试期间阻塞消费者线程 ⏸️;如果消费者应用在重试过程中崩溃,正在重试的消息会丢失 👻;不适合长时间等待的场景。

智能重试策略二:失败策略 - 重试耗尽后“送去劳改”还是“发配边疆”?📚👨‍🔧

本地重试次数用完了,消息还是处理不了,怎么办?难道就这么丢弃了?对于重要消息来说,这绝对不能接受!😠 这时候就需要一个“善后”策略,或者叫“消息恢复”策略,由 Spring AMQP 的 MessageRecoverer 接口来定义。

Spring AMQP 提供的几种 MessageRecoverer 实现:

  1. RejectAndDontRequeueRecoverer默认选项。 重试耗尽后,直接对消息执行 reject 并 requeue=false。如前所述,这通常意味着消息被 RabbitMQ 丢弃(如果没有 DLX)。简单粗暴,但可能丢消息。🚮
  2. ImmediateRequeueMessageRecoverer:重试耗尽后,发送 NACK 并 requeue=true请注意! 这又回到了最开始的问题,可能导致无限重试!😨 除了非常特殊的场景,慎用!🔄
  3. RepublishMessageRecoverer推荐的优雅方案。 重试耗尽后,将失败消息重新发布到指定的交换机!🚀 这就像把那些怎么都送不到收件人手里的疑难包裹,统一退回到一个“问题包裹处理中心”。

首先,定义一个专门处理错误消息的交换机和队列(通常用 Direct 交换机,绑定一个队列):

// RabbitConfig.java 或单独的 ErrorMessageConfig.java 文件
@Bean
public DirectExchange errorMessageExchange(){System.out.println("🛠️ 定义错误消息交换机: error.direct");return new DirectExchange("error.direct");
}
@Bean
public Queue errorQueue(){System.out.println("🛠️ 定义错误消息队列: error.queue");return new Queue("error.queue", true); // 队列通常要持久化 ✅
}
@Bean
public Binding errorBinding(Queue errorQueue, DirectExchange errorMessageExchange){System.out.println("🛠️ 绑定错误队列到错误交换机,路由键为 'error'");return BindingBuilder.bind(errorQueue).to(errorMessageExchange).with("error"); //
}

然后,定义一个 RepublishMessageRecoverer Bean,告诉它把失败消息发到哪个交换机和使用哪个路由键:

// RabbitConfig.java 或单独的 ErrorMessageConfig.java 文件
import org.springframework.amqp.rabbit.retry.MessageRecoverer; //
import org.springframework.amqp.rabbit.retry.RepublishMessageRecoverer; //
import org.springframework.amqp.rabbit.core.RabbitTemplate; //@Bean
public MessageRecoverer republishMessageRecoverer(RabbitTemplate rabbitTemplate){System.out.println("🛠️ 配置 RepublishMessageRecoverer,将失败消息发送到 error.direct 交换机,路由键 'error'");// 参数1: rabbitTemplate 用于发送消息// 参数2: 目标交换机名称// 参数3: 目标路由键return new RepublishMessageRecoverer(rabbitTemplate, "error.direct", "error"); //
}

最后,你需要将这个 republishMessageRecoverer Bean 配置到你的消费者容器中,通常是通过设置 SimpleRabbitListenerContainerFactorysetMessageRecoverer 方法,或者像 Spring Boot 默认配置那样,只要容器中有 MessageRecoverer 类型的 Bean,它会自动关联上去。

// 在你的 SimpleRabbitListenerContainerFactory 配置中(如果有的话)
// factory.setMessageRecoverer(republishMessageRecoverer);// 或者如果你没有自定义 SimpleRabbitListenerContainerFactory,并且 republishMessageRecoverer Bean 在 Spring Context 中
// Spring Boot 的 auto-configuration 会尝试自动关联 MessageRecoverer Bean

通过这种方式,那些经过本地重试依然失败的消息,就不会被简单丢弃或无限循环,而是被整整齐齐地发送到 error.queue 里。你可以有专门的服务去监听这个队列,对这些异常消息进行统一分析、人工处理或报警,极大地提高了系统的可运维性!报警!🚨🚨🚨

策略三:经纪人端延迟重试(DLX + TTL 深度剖析)📦⏳📬

咱们之前说了,本地重试是在消费者内部挣扎,而经纪人端重试是把消息“踢”回给 RabbitMQ,让它帮忙等待一段时间再派送。DLX + TTL 就是实现这种“踢回去,等会儿再发”逻辑的“官方推荐”组合拳!👊💥

想象一下,你的消息处理失败了,就像一个包裹送到了,但收件人现在不在家。快递员(消费者)不能一直拿着包裹(阻塞),也不能直接丢弃(丢消息)。他最好的办法是把包裹带回快递站,告诉调度中心:“这个包裹现在送不了,麻烦你过 N 分钟再重新派送一次吧!” DLX + TTL 在 RabbitMQ 中扮演的就是“调度中心”和“临时存放区”的角色。

核心思想:利用“死信”和“过期”的特性,让消息“曲线救国”回到原队列!

要玩转 DLX + TTL,我们需要在 RabbitMQ 里配置几个关键的“地点”和“规则”:

  1. 业务队列 (Original Queue): 这是你的消费者正常监听的队列。当消费者处理失败并决定进行延迟重试时,它会把消息变成“死信”,然后这个业务队列要被配置成,把它的死信发送到一个指定的 DLX。

    • 关键配置: 在队列的 arguments 里设置 x-dead-letter-exchange,指向你的死信交换机。
  2. 死信交换机 (Dead Letter Exchange - DLX): 这是一个普通的交换机,但它很特殊,因为它专门接收来自那些配置了 x-dead-letter-exchange 的队列的“死信”。

    • 关键作用: 接收死信,并根据死信的路由键将它们路由出去。注意,死信的路由键默认是原消息的路由键,你也可以通过队列的 arguments 里的 x-dead-letter-routing-key 来指定死信的路由键。
  3. 重试队列 (Retry Queue): 这是一个关键的“临时存放区”。它不直接给消费者监听,它的作用就是“关禁闭”——让消息在里面等待一段时间。

    • 关键配置 1: 设置 x-message-ttl,指定消息在这个队列里的存活时间(毫秒)。消息超过这个时间就会变成新的死信。🕰️
    • 关键配置 2: 设置 x-dead-letter-exchange指向原来的业务交换机! 🤯 这是最骚的操作!这样重试队列里的消息过期后,会变成死信,然后被发送回原来的业务交换机。
    • 关键绑定: 这个重试队列需要绑定死信交换机 (DLX)。绑定时使用的路由键,要和从业务队列出来的死信的路由键匹配(默认就是原消息路由键)。
  4. 业务交换机 (Original Exchange): 你的生产者发送消息的目标。重试队列里“刑满释放”的消息(过期死信),会回到这里,然后根据消息的原路由键再次被路由到业务队列。

整个“生死轮回”流程细讲:

  1. 生产者发送消息到 业务交换机,使用 业务路由键
  2. 消息被路由到 业务队列
  3. 消费者从 业务队列 取出消息,处理失败。
  4. 消费者发送 NACK 并设置 requeue=false
  5. 消息变成死信。因为 业务队列 配置了 x-dead-letter-exchange 指向 DLX,消息被发送到 DLX。💀➡️
  6. DLX 收到死信,使用死信的路由键(默认是原业务路由键)查找绑定。
  7. 找到了 重试队列DLX 的绑定(绑定键是业务路由键)。消息被路由到 重试队列。📬
  8. 消息进入 重试队列,开始计时 TTL。消息在队列里“沉睡”。⏳
  9. TTL 时间到!消息在 重试队列 里过期,再次变成死信。
  10. 因为 重试队列 配置了 x-dead-letter-exchange 指向 业务交换机,消息被发送回 业务交换机。♻️
  11. 业务交换机 收到消息(此时消息会带有一些死信头信息),再次根据消息的原路由键路由。
  12. 消息再次被路由回 业务队列。🎉
  13. 消费者从 业务队列 再次收到消息,进行重试处理。这次收到的消息 Redelivered 标志通常为 true

这个流程可以循环多次,通过设置多个重试队列,每个队列绑定到前一个队列的 DLX,并设置不同的 TTL 和指向下一个队列的 DLX,可以实现多级、阶梯式的延迟重试!比如:失败 -> 等 1 分钟 -> 失败 -> 等 5 分钟 -> 失败 -> 等 30 分钟 -> 最终失败进入人工处理队列。🛣️⏱️

代码怎么配置这些“地点”和“规则”?

这主要是在你的 Spring Boot 项目的 RabbitConfig.java 里定义 Bean 时,通过队列的 arguments 参数来设置。

package com.example.rabbitmqconfirmdemo.config; // 使用你自己的包名// ... 必要的导入,包括 Queue, DirectExchange, Binding, BindingBuilder ...
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.amqp.core.*; // 导入这些类
import java.util.HashMap; // 引入 HashMap
import java.util.Map; // 引入 Map@Configuration
public class RabbitConfig {// 业务交换机 (同上)@Beanpublic TopicExchange myDurableExchange() {System.out.println("🛠️ 正在创建业务交换机: my.durable.exchange");return new TopicExchange("my.durable.exchange", true, false);}// ⭐ 定义死信交换机 (DLX) ⭐@Beanpublic DirectExchange dlxExchange() {System.out.println("🛠️ 正在创建死信交换机 (DLX): my.dlx.exchange");// DLX 通常用 Direct 类型,因为路由键不变或指定死信路由键return new DirectExchange("my.dlx.exchange", true, false); // 持久化}// ⭐ 定义业务队列,并配置其死信发往 DLX ⭐@Beanpublic Queue myDurableQueue() {System.out.println("🛠️ 正在创建持久化业务队列: my.durable.queue");Map<String, Object> args = new HashMap<>();// ⭐ 核心配置 1: 指定当前队列的死信发往哪个交换机 ⭐// 当消息被 NACK(requeue=false) 或在当前队列过期,会被发到这个交换机args.put("x-dead-letter-exchange", "my.dlx.exchange"); // 指向上面定义的 DLX// ⭐ 可选配置:指定死信的路由键,不指定则使用原消息的路由键 ⭐// args.put("x-dead-letter-routing-key", "some-dlx-routing-key");// ⭐ 可选配置:给业务队列的消息设置 TTL (如果在队列里长时间没被消费也会死信) ⭐// args.put("x-message-ttl", 300000); // 比如 5 分钟,防止消息永远积压在业务队列// 队列本身也要持久化return new Queue("my.durable.queue", true, false, false, args);}// ⭐ 定义重试队列 (这是第一个延迟等待层级) ⭐@Beanpublic Queue retryQueue() {System.out.println("🛠️ 正在创建重试队列 (延迟 60秒): my.retry.queue");Map<String, Object> args = new HashMap<>();// ⭐ 核心配置 2: 消息在这个队列里等待多久变成死信(毫秒)⭐args.put("x-message-ttl", 60000); // 例子:等待 60 秒// ⭐ 核心配置 3: 消息过期变成死信后,发回哪个交换机?指向业务交换机! ⭐args.put("x-dead-letter-exchange", "my.durable.exchange"); // 死信发回业务交换机// ⭐ 核心配置 4: 消息过期变成死信后,使用哪个路由键发回?通常用原路由键 ⭐args.put("x-dead-letter-routing-key", "my.routing.key"); // 指定死信发回业务交换机时的路由键// 队列本身也要持久化return new Queue("my.retry.queue", true, false, false, args);}// ⭐ 定义重试队列绑定到 DLX ⭐@Beanpublic Binding retryBinding(Queue retryQueue, DirectExchange dlxExchange) {System.out.println("🛠️ 将重试队列绑定到 DLX");// 绑定键要和从业务队列出来到 DLX 的死信路由键匹配 (默认是原路由键 "my.routing.key")return BindingBuilder.bind(retryQueue).to(dlxExchange).with("my.routing.key"); //}// ⭐ 可选:定义最终失败队列 (Final DLQ) ⭐// 用于接收从重试队列出来,但没有被正确路由(比如业务交换机或队列被删了)// 或者你设计了多层重试,这是最后一层重试队列的死信目的地@Beanpublic Queue finalDlxQueue() {System.out.println("🛠️ 正在创建最终死信队列: my.final.dlq.queue");return new Queue("my.final.dlq.queue", true); // 持久化}// ⭐ 可选:定义一个交换机用于接收所有最终的死信 (如果需要统一处理) ⭐@Beanpublic DirectExchange finalDlxExchange() {System.out.println("🛠️ 正在创建最终死信交换机: my.final.dlx.exchange");return new DirectExchange("my.final.dlx.exchange", true, false);}// ⭐ 可选:将最终失败队列绑定到最终死信交换机 ⭐@Beanpublic Binding finalDlxBinding(Queue finalDlxQueue, DirectExchange finalDlxExchange) {System.out.println("🛠️ 将最终死信队列绑定到最终死信交换机");return BindingBuilder.bind(finalDlxQueue).to(finalDlxExchange).with("final.dlq.key"); // 使用一个特定的路由键}// ⭐ 如果需要多层延迟重试,就需要定义更多重试队列和绑定 ⭐// retryQueue2 (TTL 5分钟), retryQueue3 (TTL 30分钟) ...// retryQueue -> DLX (my.dlx.exchange) -> retryQueue2// retryQueue2 -> DLX (my.dlx.exchange) -> retryQueue3// retryQueue3 -> DLX (my.dlx.exchange) -> my.durable.exchange (或指向最终死信交换机)// 业务队列绑定到业务交换机 (同上)@Beanpublic Binding binding(Queue myDurableQueue, TopicExchange myDurableExchange) {return BindingBuilder.bind(myDurableQueue).to(myDurableExchange).with("my.routing.key");}// ... RabbitTemplate 和 ListenerContainer 配置 (同上,确保 ListenerContainer 模式是 MANUAL) ...
}

消费者代码中的关键:

在你的 ChannelAwareMessageListener 实现中,当处理失败并决定进行延迟重试时,一定要发送 NACK 并设置 requeue=false

// ManualAckMessageListener.java (onMessage 方法中的失败处理部分)// ... catch (Exception e) { ...
try {// ⭐ 核心:手动发送 NACK,并将 requeue 设为 FALSE! ⭐// 这样消息就会变成死信发往业务队列配置的 DLX!channel.basicNack(deliveryTag, false, false); // 拒绝当前消息,不批量,不重新入队 (进入DLX)System.err.println("💔 消息处理失败,手动发送 NACK 并发送到 DLX!Delivery Tag: " + deliveryTag);
} catch (Exception nackException) {System.err.println("🔥 发送 NACK 时也发生异常! Delivery Tag: " + deliveryTag + ", 错误: " + nackException.getMessage());// 极少发生,需重点关注
}
// ...

这样配置并配合消费者端的 basicNack(false, false) 操作,你就搭建起了一个完整的 DLX + TTL 延迟重试机制!🥳

优点总结:

  • 实现了消息处理失败后的延迟重试,不会立即给消费者和依赖服务造成压力。
  • 重试等待期间不阻塞消费者线程。
  • 即使消费者应用崩溃,消息也安全地待在 RabbitMQ 的重试队列里等待。
  • 通过配置多层重试队列,可以实现灵活的阶梯式延迟重试策略。🛣️⏱️
  • 最终失败的消息可以被导入专门的 DLQ,方便统一管理和处理。📂

注意事项:

  • DLX + TTL 机制依赖于 RabbitMQ broker 的稳定运行和正确配置。
  • 如果你的业务逻辑需要根据重试次数采取不同策略(比如重试 3 次失败后换个处理逻辑),你需要在消息的 Header 里记录重试次数,并在消费者端读取和判断。RabbitMQ 在死信过程中会添加一些 Header 信息,比如 x-death 头部,包含了消息的死信原因、次数、队列等信息,你可以利用它来判断重试次数。
  • 确保重试队列的 TTL 和业务处理时间、外部依赖恢复时间相匹配。

把 DLX + TTL 玩明白了,你的 RabbitMQ 消息可靠性就又上了一个大台阶!它和本地重试(Spring Retry)常常是好搭档,一个负责内部快速消化,一个负责外部排队等待,强强联合,构建最稳固的消息处理防线!🛡️💪

总结:如何构建可靠的消息消费策略?✨🛡️

结合咱们的讨论,一套可靠的 RabbitMQ 消息处理策略,就像给你的消息穿上多层“保险服”:

  1. 生产者确认机制: 确保消息安全送达 RabbitMQ 的 Exchange。✅📬
  2. 持久化功能: 确保 Exchange、Queue 和消息本身在 RabbitMQ 重启或崩溃时不会丢失。🏛️💾✉️
  3. 消费者确认机制: 确保消息被消费者成功处理后才从队列删除。对于需要精细控制重试行为的复杂场景,强烈推荐使用 AcknowledgeMode.MANUAL 手动确认模式!✍️🔒(对于 auto 模式,它在方法不抛异常时自动 ACK,抛异常时自动 NACK。对于简单的场景够用,但如果你需要灵活控制 NACK(true)/NACK(false) 或结合 MessageRecoverer,手动模式是必选项)。
  4. 消费者失败重试机制: 这是处理“非成功”消息的核心!
    • 可以开启 本地重试 (Spring Retry),在消费者内部进行几次快速重试。🏠🔄
    • 重试多次或遇到特定错误后,通过发送 NACK 并 requeue=false 将消息送入 DLX + TTL 流程,实现延迟重试。📦⏳
    • 可以配置 MessageRecoverer (特别是 RepublishMessageRecoverer),将那些重试耗尽依然失败的消息隔离到专门的异常队列进行处理。📚➡️📂

通过合理组合这些机制,你就可以构建出一个既高效又可靠的 RabbitMQ 消息消费系统,从容应对各种“幺蛾子”般的消费失败场景,让你的应用更加健壮!🏆

希望这篇文章对你有所启发!快去试试搭建你的智能重试策略吧!Go~Go~Go!🚀😄

相关文章:

  • Python3:Jupyter Notebook 安装和配置
  • 基于大模型的急性化脓性阑尾炎全程诊疗预测与方案研究
  • 物联网相关
  • Golang | 搜索表达式
  • 巧记英语四级单词 Unit6-上【晓艳老师版】
  • Dijkstra 算法代码步骤[leetcode.743网络延迟时间]
  • Milvus如何实现关键词过滤和向量检索的混合检索
  • 信竞中的数学(一):质数
  • 典籍查询界面增加我的收藏查询功能
  • 极狐GitLab 议题权重有什么作用?
  • 【漫话机器学习系列】227.信息检索与数据挖掘中的常用加权技术(TF-IDF)
  • 论文检索相关网站
  • 制作一款打飞机游戏26:精灵编辑器
  • 【2025 最新前沿 MCP 教程 05】为 MCP 设置开发环境
  • 《AI大模型应知应会100篇》第37篇:Agent框架入门:让AI具备自主行动能力
  • 非结构化数据解析
  • ESP32开发入门(四):ESP32-s3多串口开发实践
  • Linux进程详细解析
  • Day14(链表)——LeetCode234.回文链表141.环形链表
  • MySQL:13.用户管理
  • 恒瑞医药赴港上市获证监会备案,拟发行不超8.15亿股
  • 劳动最光荣!2426人受到表彰
  • 俄联邦安全局:俄军高级官员汽车爆炸案嫌疑人已被捕
  • 靳燕出任中央戏剧学院党委副书记,原任中戏院长助理
  • 乌称泽连斯基与特朗普进行简短会谈
  • 精准滴灌“种企业”,苏南强县常熟新的进阶密码