Spring Boot集成Redis发布订阅实战:从原理到代码避坑指南

做 Spring Boot 项目的时候,Redis 的发布订阅功能我经常用来做轻量级的消息通知,比如订单状态变更广播、缓存失效通知、在线用户心跳提醒。Spring Boot 里默认的 Redis 客户端就是 Lettuce,集成发布订阅其实只需要几张配置类和监听器,但里面的序列化、频道模式、断线重连这些坑,文档不会给你写全。这篇文章就以一个可运行的 Spring Boot + Lettuce 发布订阅 Demo 为主线,把核心配置、代码实现、测试方法和排查思路一起梳理一遍。

如果你只是想快速跑通一个“生产者发消息、消费者收消息”的样例,百度一搜一大把。但真实项目里总会遇到“为什么别人能收到我收不到”“RedisTemplate 发出去以后 listener 拿到一堆二进制乱码”“服务重启以后消息丢了怎么办”之类的问题。这篇文章不会只给代码,还会把背后的原理和我实际踩过的坑一起讲透。

1. 项目整体思路与选型拆解

1.1 为什么用 Redis 发布订阅而不是消息队列

先要搞清楚一个核心问题:你需要的到底是“广播通知”还是“任务分发”。Redis 发布订阅,也就是 Pub/Sub,是一种很轻量的实时广播机制。一个消息发布到频道,所有订阅这个频道的客户端都会实时收到,消息不会存储在 Redis 里。它和消息队列最大的区别就是:不存在“消费者拉取”和“消息积压”的概念。

假设你现在要做一个订单系统,订单创建成功后需要通知积分服务、消息推送服务、库存服务。用 Pub/Sub 就很顺手:订单服务往orders.created频道发一条消息,所有监听这个频道的服务同时收到,各自做自己的事情。这个场景核心是“广播”,不是“谁抢到谁处理”。反过来,如果是一个邮件发送任务,希望多台机器消费同一个任务队列,一份任务只能被一台机器处理,那就不该用 Pub/Sub,应该用 Redis Stream 或者专业的消息队列。

我见过新手一上来就用发布订阅做异步任务队列,消息发出去没有消费者在线,任务直接就丢了。后来改成 Redis Stream 才解决问题。所以选型前先问自己一句:消息没了能不能接受?不能接受就换方案。

1.2 为什么 Spring Boot 默认选择 Lettuce

Spring Boot 的spring-boot-starter-data-redis默认集成的是 Lettuce 而不是 Jedis,这是很多人忽略的事实。Jedis 是阻塞式 IO,一个连接同时只能处理一个命令,多线程要使用连接池。Lettuce 基于 Netty,把连接复用做得很好,多个线程可以共享一个连接而不互相干扰,对异步操作、响应式编程支持也更友好。

对于发布订阅场景,Lettuce 的优势尤其明显。Redis 发布订阅要求订阅连接长期存在,专门负责监听频道消息。如果用 Jedis,你得单独维护一条长期存活的连接,并且不能在这个连接上执行普通命令,否则会破坏订阅状态。Lettuce 在底层已经把这层事情封装好了,Spring Data Redis 只需要通过RedisMessageListenerContainer去管理订阅生命周期即可。

我用过的项目中,Spring Boot 2.x 和 3.x 默认都是 Lettuce,不需要额外改依赖。如果你以前用过 Jedis,建议直接适应 Lettuce,Spring 官方围绕 Lettuce 做的集成最完整。

1.3 适合什么场景,不适合什么场景

适合用 Redis 发布订阅的场景,我梳理了几类:

  • 配置变更通知:比如后台改了开关配置,通过频道广播通知各服务刷新本地缓存。
  • 缓存失效通知:应用内存缓存过期后,广播给集群里的其他节点,避免脏数据。
  • 实时在线推送:聊天室、站内信、用户状态变更这类低延迟、可容忍丢失的场景。
  • 服务间轻量事件通知:订单创建、用户注册等事件,多个下游服务并行感知。

不适合的场景,也很明确:

  • 要求消息不丢失,必须有持久化。Pub/Sub 消息是“发了就没了”。
  • 要求消息能够回溯、重放。Pub/Sub 做不到。
  • 要求一条消息只被一个消费者消费,需要消费组。Pub/Sub 广播是发给所有人的。
  • 要求可靠的消息确认和重试。Pub/Sub 没有 ACK 机制,接收方处理失败也不知道。

这些边界想清楚,后面才不会在设计上踩大坑。

2. 快速搭建 Spring Boot 集成环境

2.1 依赖引入与版本建议

我用的是 Spring Boot 2.7.x,Redis 用的是本机或者 Docker 起的 6.x 以上。只需要引入一个 starter 就够:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>

如果你打算给 Lettuce 连接池设置参数,还得引入连接池依赖:

<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency>

Spring Boot 的自动配置会把 LettuceConnectionFactory、RedisTemplate、StringRedisTemplate 这些核心 Bean 都创建好,不需要手动注册。我们要做的只是在配置类里补充定制化的 Bean,比如消息监听容器。

2.2 配置文件:连接 Redis 与线程池参数

application.yml里基本的 Redis 配置如下:

spring: redis: host: 127.0.0.1 port: 6379 password: database: 0 timeout: 3000ms lettuce: pool: max-active: 16 max-idle: 8 min-idle: 2 max-wait: 3000ms

在发布订阅场景里,连接池的作用主要是给普通的 Redis 命令操作提供连接。订阅连接本身由RedisMessageListenerContainer管理,一般不会从连接池里占用一个连接长期不放。不过连接池参数还是建议设置,防止高并发下 RedisTemplate 操作连接不足。

有一个容易被忽视的配置是timeout。如果一个 Redis 服务端或网络环境不太好,订阅连接长时间空闲可能被中间层断开,timeout太短会加剧连接不稳定。我一般会把它设置为 3 到 5 秒,既要能快速暴露问题,也不要频繁超时。

2.3 定义频道常量与消息结构

频道名最好统一管理,不要到处写字符串。我习惯建一个常量类:

public final class RedisChannel { private RedisChannel() { } public static final String ORDER_CREATED = "orders.created"; public static final String ORDER_PAID = "orders.paid"; public static final String NOTICE = "notice"; }

如果一对一业务,写死字符串也能跑,但项目一旦有多个频道、多个模块,字符串散落在各处,第二天修改频道名时你会非常痛苦。

对于消息体,我建议频道里传输的永远是字符串,最好是 JSON 字符串。这样一是不受 Java 序列化影响,二是其他语言的服务也能消费。比如订单消息结构,可以直接定义一个 DTO:

public class OrderEvent { private String orderId; private String status; private Long timestamp; public OrderEvent() { } public OrderEvent(String orderId, String status) { this.orderId = orderId; this.status = status; this.timestamp = System.currentTimeMillis(); } // getter/setter 省略 }

通过 Jackson 手动序列化成 JSON 字符串再发出去,比直接塞 Java 对象靠谱得多。后面讲序列化坑的时候,你会明白为什么我这么坚持。

3. 核心功能实现:发布端与订阅端

3.1 消息监听容器与 MessageListener

Spring Data Redis 里专门有一个RedisMessageListenerContainer,负责维护订阅连接、把收到的消息分发给监听器。我们可以把它理解成一个“订阅管理器”:容器启动时去 Redis 执行 subscribe/psubscribe,消息到达后转交给对应的MessageListener。

实现一个监听器,最简单直接的方式是实现MessageListener接口:

@Component public class OrderMessageListener implements MessageListener { private static final Logger log = LoggerFactory.getLogger(OrderMessageListener.class); @Override public void onMessage(Message message, byte[] pattern) { String channel = new String(message.getChannel(), StandardCharsets.UTF_8); String body = new String(message.getBody(), StandardCharsets.UTF_8); log.info("收到频道 [{}] 的消息: {}", channel, body); // 这里可以直接根据频道名做不同处理 if (RedisChannel.ORDER_CREATED.equals(channel)) { handleOrderCreated(body); } } private void handleOrderCreated(String body) { // 解析 JSON,写业务逻辑 } }

然后在配置类里把这个监听器注册进容器:

@Configuration public class RedisPubSubConfig { @Bean public RedisMessageListenerContainer redisMessageListenerContainer( RedisConnectionFactory connectionFactory, OrderMessageListener orderMessageListener) { RedisMessageListenerContainer container = new RedisMessageListenerContainer(); container.setConnectionFactory(connectionFactory); // 监听精确频道 + 通配符频道 container.addMessageListener(orderMessageListener, Arrays.asList( new ChannelTopic(RedisChannel.ORDER_CREATED), new PatternTopic("orders.*") )); container.setRecoveryInterval(3000L); return container; } }

这里注意一个细节:addMessageListener的第二个参数可以传单个Topic,也可以传一个Topic列表。ChannelTopic表示精确频道,对应 Redis 命令SUBSCRIBE;PatternTopic表示频道模式,对应PSUBSCRIBE。如果你同时注册了orders.created精确频道和orders.*模式,发送到orders.created的消息会重复进入同一个监听器。我在编码时一般只选择一种方式,避免重复处理。

3.2 发布消息的两种方式与封装

发布消息最推荐的是StringRedisTemplate.convertAndSend(),因为消息体是 String,绕开了 RedisTemplate 默认序列化的坑。

@Service public class OrderMessagePublisher { private final StringRedisTemplate stringRedisTemplate; private final ObjectMapper objectMapper = new ObjectMapper(); public OrderMessagePublisher(StringRedisTemplate stringRedisTemplate) { this.stringRedisTemplate = stringRedisTemplate; } public void publishOrderCreated(String orderId) { OrderEvent event = new OrderEvent(orderId, "CREATED"); try { String json = objectMapper.writeValueAsString(event); stringRedisTemplate.convertAndSend(RedisChannel.ORDER_CREATED, json); } catch (JsonProcessingException e) { throw new RuntimeException("订单消息序列化失败", e); } } }

如果你一定要用RedisTemplate发送对象,RedisTemplate 的 ValueSerializer 必须和订阅端使用的序列化器对齐,否则监听器拿到的字节数组和预期不一致。同一个 Redis 连接工厂里,StringRedisTemplate和RedisTemplate是两套独立的 Bean,别以为它们底层序列化行为一样。

还有一个小技巧:发布消息时可以在 JSON 里带上一个全局唯一的消息 ID,下游收到后做幂等。发布订阅没有 ACK,收不到也没办法,但至少处理端不会因为网络重发或者其他原因重复执行业务逻辑。

3.3 MessageListenerAdapter 的用法与坑

网上很多教程会推荐MessageListenerAdapter,把监听器写成一个普通的 Bean,然后在配置类里适配:

@Bean MessageListenerAdapter orderListenerAdapter(OrderMessageListener listener) { MessageListenerAdapter adapter = new MessageListenerAdapter(listener); adapter.setSerializer(new StringRedisSerializer()); return adapter; }

这样监听器就不需要实现MessageListener接口,Spring 会通过反射调用默认名为handleMessage的方法。比如:

@Component public class OrderMessageListener { public void handleMessage(String message) { System.out.println(message); } }

这个写法很简洁,但坑在于:MessageListenerAdapter 默认会使用SerializingMessageConverter把消息体从字节数组反序列化成对象。如果你发布端用的是 StringRedisTemplate 发出的 JSON 字符串,而 MessageListenerAdapter 没有设置StringRedisSerializer,监听器方法里拿到的参数就是乱码,或者直接类型转换异常。所以如果使用这个模式,一定要记得调用adapter.setSerializer(new StringRedisSerializer())。

我个人更偏向直接实现MessageListener接口,少一层反射调用,也更透明。但对于那种只想监听某个固定频道、方法名固定的快速开发,MessageListenerAdapter 也够用。

4. 序列化、线程池与消息可靠性:容易被坑的细节

4.1 RedisTemplate 序列化器选择:JSON 还是 String

这是发布订阅功能里最经典的坑。Spring Boot 默认创建的RedisTemplate<String, Object>的 key 序列化器是JdkSerializationRedisSerializer,value 序列化器也是 JDK 序列化。这意味着用默认 RedisTemplate 的convertAndSend发送一个对象时,它会先经过 Java 序列化,带着一堆\xAC\xED\x00\x05的二进制头,再存进 Redis 的频道。

订阅端拿到Message.getBody()后直接转成 String 就是一堆乱码。要解决这个问题,有两条路可以走:

第一条路,也是最省心的,就是用StringRedisTemplate,它内部已经把 key 和 value 的序列化器都设成了StringRedisSerializer。我不需要特意去改任何 Bean 配置,直接把 JSON 字符串发出去,监听器拿到后就是 UTF-8 明文。

第二条路,自定义 RedisTemplate:

@Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); StringRedisSerializer stringSerializer = new StringRedisSerializer(); GenericJackson2JsonRedisSerializer jsonSerializer = new GenericJackson2JsonRedisSerializer(); template.setKeySerializer(stringSerializer); template.setHashKeySerializer(stringSerializer); template.setValueSerializer(jsonSerializer); template.setHashValueSerializer(jsonSerializer); template.afterPropertiesSet(); return template; }

如果消息体是包含时间类型字段的对象,Jackson 默认可能不支持LocalDateTime,需要在 ObjectMapper 中注册 JavaTimeModule。这也是一个隐藏坑。

4.2 消息监听容器的线程模型与并发控制

很多人以为 RedisMessageListenerContainer 只是简单地在某个连接上循环读消息,其实它的内部有独立的订阅任务和消息分发线程。发布订阅消息到达监听器后,如果onMessage方法执行很慢,后面的消息就会被阻塞。原因很简单:同一个连接上,Redis 服务端按顺序推送消息,监听器是串行消费的。

我遇到过一个实际案例:有人在监听器里直接调用了一个外部 HTTP 接口,这个接口最慢耗了 5 秒,结果频道上后续所有消息都排队,最夸张的时候延迟几分钟。解决方式其实很简单,把耗时逻辑丢到线程池异步执行:

private final ExecutorService bizExecutor = Executors.newFixedThreadPool(4); @Override public void onMessage(Message message, byte[] pattern) { String body = new String(message.getBody(), StandardCharsets.UTF_8); bizExecutor.execute(() -> { try { // 耗时的业务处理 } catch (Exception e) { log.error("处理消息异常", e); } }); }

如果你同时订阅了许多频道,并且消息量很大,还可以给容器设置专门的任务执行器:

container.setTaskExecutor(Executors.newFixedThreadPool(8));

但要注意,这并不会把一个频道内的消息并发消费,Redis 频道本身的推送顺序仍然由底层连接持有。它更多是让多个监听器之间的分发不互相阻塞。

4.3 订阅断线重连与监听器存活问题

Redis 服务端重启、网络闪断、防火墙空闲断开,都会让订阅连接失效。如果容器没有重连机制,监听器就悄无声息地“死掉”了。Spring Data Redis 的RedisMessageListenerContainer默认是有恢复机制的,它内部有一个recoveryInterval参数,默认是 5000 毫秒。容器会定期检查订阅状态,如果发现连接断开,会尝试重新注册订阅。

我在配置里一般会显式缩短恢复时间,让业务对断连更敏感:

container.setRecoveryInterval(3000L);

但这里有个边界大家要明白:重连可以恢复“之后”的新消息,重连期间发布出来的消息依然会丢。这是发布订阅的天然限制,不是代码能解决的。生产环境如果对丢失敏感,请用 Redis Stream 或者把关键事件同步落库后由定时任务补偿。

另一点,如果服务是多实例部署的,每个实例都有自己的监听器,广播消息会同时进入所有实例。这种情况下要提前想好“重复消费”的问题。要么接受广播的语义,让各实例自己判断;要么在业务上做幂等,比如根据消息 ID 去重。

5. 测试与故障排查实录

5.1 用 redis-cli 和代码发布消息验证

先把服务跑起来,然后在命令行连接 Redis,往频道发一条消息,观察应用日志:

redis-cli -h 127.0.0.1 -p 6379 PUBLISH orders.created "hello-123"

如果监听器正常,应用日志会打印“收到频道 [orders.created] 的消息: hello-123”。

也可以先单独在 redis-cli 里订阅频道,确认频道名没写错:

SUBSCRIBE orders.created

这条命令会进入阻塞监听模式,此时另开一个 redis-cli 发布消息,能看到订阅输出,说明 Redis 本身没问题,问题可能在应用端。

我建议在开发环境加一个临时接口或者启动时测试发布,确认整个链路可用。比如用一个CommandLineRunner:

@Component public class PubSubStartupTester implements CommandLineRunner { private final OrderMessagePublisher publisher; public PubSubStartupTester(OrderMessagePublisher publisher) { this.publisher = publisher; } @Override public void run(String... args) { publisher.publishOrderCreated("startup-test"); } }

跑完记得删掉,避免每次启动都发测试消息污染环境。

5.2 常见故障速查表:收不到消息、乱码、失去连接

我把实际开发中遇到过的典型问题整理成了一个表格,排查的时候对照着看就行:

现象可能原因解决办法
服务日志完全没输出频道名不一致,或者发布端和订阅端连的不是同一个 Redis database检查常量,用 redis-cli 手动 PUBLISH 验证
redis-cli 能收到,应用收不到容器 Bean 没有被扫描到,监听器没有注册成功查看启动日志中是否有 Subscribed 相关信息,确认配置类被加载
收到但内容是乱码发布端用了 RedisTemplate 默认 JDK 序列化,订阅端却按 String 解析统一使用 StringRedisTemplate 发送字符串
使用 MessageListenerAdapter 后参数类型转换异常没有设置 StringRedisSerializer调用adapter.setSerializer(new StringRedisSerializer())
通配符orders.*收不到经过orders.created发送的消息发布用的是 SUBSCRIBE 而不是 PSUBSCRIBE 的语义?不,发送端 PUBLISH 本身就可以被 PSUSCRIBE 收到;更常见是模式写错检查 PatternTopic 里的通配符,确认没有把*写成#
服务重启后消息丢失发布订阅本来就不持久化接受语义,或者改用 Redis Stream
监听器偶发不工作,Redis 连接日志有断开网络问题或者空闲连接被服务端关闭缩短recoveryInterval,检查 Redis timeout 配置
多实例都收到同一条消息这是广播特性下游业务做幂等,不要试图让多实例竞争消费

5.3 调试技巧:日志级别与监控频道

排查发布订阅问题,第一步是确认消息有没有到达应用这一层。给监听器加好日志是基础,但日志别只打印消息内容,把频道也带上。我之前就遇到过“明明收到了消息,但处理逻辑走了别的频道分支”,就是因为只打印了消息 body 没打印 channel。

第二,可以把 Spring Data Redis 的日志级别调成 DEBUG,观察订阅连接是否建立:

logging: level: org.springframework.data.redis.listener: DEBUG org.springframework.data.redis.connection.lettuce: DEBUG

第三,如果消息量不大,可以临时用一个“垃圾桶”监听器,订阅*模式,把所有发布消息都打出来,看看是不是消息确实能到订阅端。但要注意PSUBSCRIBE *在高频场景下压力很大,只适合临时排查。

6. 顺带聊聊进阶与替代方案

6.1 从 Pub/Sub 迁移到 Redis Stream 的取舍

如果你发现自己开始在 Pub/Sub 上做消费重试、消息持久化、消费组了,那就是它在越权。Redis Stream 才是 Redis 里更适合做消息队列的数据结构。Stream 的消息会持久化在 Redis 中,支持消费组,支持每条消息的 ACK,也支持按时间范围回溯。

两者的选择,不是谁更好,而是谁更匹配:

需求使用方式
实时广播给所有订阅者,消息可丢Redis Pub/Sub
每个消费者消费所有消息,但可以异步拉取历史Redis Stream + 独立消费者
多个消费者竞争消费同一批消息,每个消息只处理一次Redis Stream + Consumer Group
需要严格的顺序且不丢消息Redis Stream + 单消费者组,或专业 MQ

我在项目里常用一个组合:实时通知走 Pub/Sub,订单任务、积分类事件走 Redis Stream。两个都是 Redis 原生的,不用额外引入中间件,维护成本很低。

6.2 生产中值得增加的改进点

第一个改进是统一封装消息模型。不要直接暴露convertAndSend(channel, message)给业务方,而是封装一个Publisher,内部处理 JSON 序列化、频道命名、消息 ID 生成。这样以后如果换 Stream,业务方不需要感知。

第二个改进是增加简单的监控。比如在监听器里记录每秒处理消息数、最近一条消息到达时间。如果一段时间没有消息进来,可以认为是订阅连接异常,配合告警提前发现。这种方式比等服务完全挂掉再排查高效得多。

第三个改进是消息处理的异常隔离。监听器里永远要有一个统一的 try-catch,否则某个消息反序列化失败会直接中断整个监听流程。我在生产代码里习惯把所有失败的原始消息写到一个专门的“死信频道”或者 Redis List 里,然后由定时任务扫描重新处理。虽然发布订阅本身没有 ACK,但业务层面的兜底可以自己设计。

第四个改进是网络连接参数的调优。高并发场景下,Lettuce 的连接池和超时时间要结合实际压测调整,不要照抄网上的配置。尤其是订阅连接不通过连接池,但普通操作 latency 如果太高,也会间接影响整个 Redis 客户端的稳定性。

最后再分享一个小体会:发布订阅功能看起来简单,但真正碰过线上问题之后,会发现它和消息队列完全是两种思维模式。Simple 从来不代表没边界,理解了它“实时、广播、不持久”这三个特点,你自然就明白什么时候用对了,什么时候该换方案。我自己在多个项目里用这个套路做订单事件广播、配置刷新通知,跑得很稳,希望这份经验对你也有用。

相关新闻

LLM Agent驱动的Git原生代码审查CLI工具

LLM Agent驱动的Git原生代码审查CLI工具

1. 项目概述&#xff1a;这不是又一个代码审查工具&#xff0c;而是一次开发工作流的底层重定义“open-code-review”这个名称乍看平平无奇&#xff0c;甚至有点像某个被遗忘在 GitHub 某个角落的冷门仓库名。但如果你最近刷过技术社区、看过几篇关于 LLM Agent 的实操笔记&…

2026/10/19 16:41:27 阅读更多 →
从零实现生成对抗网络:GAN原理、PyTorch代码与训练避坑指南

从零实现生成对抗网络:GAN原理、PyTorch代码与训练避坑指南

聊生成对抗网络&#xff08;GAN&#xff09;之前&#xff0c;我真不建议一上来就背公式。G1 在我这里指的是第一代教学实战项目&#xff0c;核心就一句话&#xff1a;让生成器和判别器两个网络互相较劲&#xff0c;最后逼出以假乱真的数据生成能力。这个系列我已经带过不少同学…

2026/10/19 16:41:27 阅读更多 →
YOLOv8自助售货机商品检测:从数据集到部署全流程实战

YOLOv8自助售货机商品检测:从数据集到部署全流程实战

1. 自助售货机商品检测&#xff1a;毕设到底在做什么自助售货机商品检测这个课题&#xff0c;说白了一件事&#xff1a;让摄像头代替人的眼睛&#xff0c;识别出货架上的商品是什么、是否齐全、还有没有货。听起来不复杂&#xff0c;放在毕业设计里却是个特别完整的项目——有数…

2026/10/19 16:41:27 阅读更多 →

最新新闻

【信息科学与工程学】【数据中心】 第十一篇 人工智能智算中心整体设计101 智算中心解决方案 第一部分1.1 智算中心ICT基础设施的定义与边界

【信息科学与工程学】【数据中心】 第十一篇 人工智能智算中心整体设计101 智算中心解决方案 第一部分1.1 智算中心ICT基础设施的定义与边界

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 22:46:04 阅读更多 →
open-code-review:CLI+LLM+Git驱动的开源代码审查新范式

open-code-review:CLI+LLM+Git驱动的开源代码审查新范式

1. 什么是 open-code-review&#xff1a;一个被严重低估的开发者协作新范式open-code-review 不是某个具体软件的名字&#xff0c;也不是某家公司的私有产品&#xff0c;而是一类正在快速成型的技术实践范式——它指代的是基于开源理念、命令行界面&#xff08;CLI&#xff09;…

2026/10/19 22:40:54 阅读更多 →
二轮平衡车全套源码:卡尔曼滤波与PID三环控制实战

二轮平衡车全套源码:卡尔曼滤波与PID三环控制实战

简介&#xff1a;这是一套面向电子爱好者与嵌入式初学者的二轮平衡车DIY完整资料包&#xff0c;涵盖从硬件搭建到软件调试的全链路内容。资源定位为STM32平台平衡车的开源学习套件&#xff0c;包含互补滤波、卡尔曼滤波、DMP三种姿态解算方案的源码&#xff0c;三套工程均编译通…

2026/10/19 22:39:52 阅读更多 →
加扰与解扰:原理、区别及在高速链路中的应用

加扰与解扰:原理、区别及在高速链路中的应用

简介&#xff1a;本资源面向数字通信、FPGA开发与信号处理方向的学习者&#xff0c;围绕加扰与解扰这一核心环节&#xff0c;提供基于VHDL的完整工程实现与验证流程。加扰通过可逆数学变换打散连续相同符号&#xff0c;降低信道自相关与互相关影响&#xff0c;广泛应用于数字电…

2026/10/19 22:35:37 阅读更多 →
数据范围决定算法选型:从O(n log n)到O(n)

数据范围决定算法选型:从O(n log n)到O(n)

我去年处理过一个挺有意思的活儿&#xff1a;某内部工具需要在上亿条日志里按时间窗口做聚合统计&#xff0c;数据的时间戳全部落在最近三个月内。一开始我按教科书思路上了红黑树、跳表这类“高级”结构&#xff0c;结果在真实数据上跑得还不如后来换上的一个普通数组快。原因…

2026/10/19 22:32:32 阅读更多 →
Open Code Review:面向可复现与可审计的代码审查新范式

Open Code Review:面向可复现与可审计的代码审查新范式

1. “open-code-review”不是工具名&#xff0c;而是一类新型代码审查范式的代号你搜“open-code-review”&#xff0c;大概率会撞上一堆零散的 GitHub 仓库、CLI 工具片段、LLM 集成脚本&#xff0c;甚至有人把它当成某个具体开源项目的名称——但其实它根本不是。它是一个正在…

2026/10/19 22:32:32 阅读更多 →

日新闻

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

简介&#xff1a;本资源为电源技术方向的学术研究文档&#xff0c;聚焦介质阻挡放电型臭氧发生器电源的频率跟踪型PWM控制策略&#xff0c;面向电力电子、高压电源及臭氧应用领域的科研人员与工程技术人员。针对负载呈容性、等效电容随外加电压升高而增大、谐振频率随之漂移的难…

2026/10/19 0:00:47 阅读更多 →
Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 0:00:47 阅读更多 →
HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

【免费下载链接】HundredCV-Chat 项目地址&#xff1a; https://ai.gitcode.com/hf_mirrors/Jax-dan/HundredCV-Chat 点击查看 免费下载 HundredCV-Chat 是一个由 100 位青年简历数据开发而来的中文多轮对话数据集&#xff0c;共包含 24,750 组日常闲聊对话和 4,871 个话题标签…

2026/10/19 0:00:47 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/19 10:02:04 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/19 17:32:41 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/19 1:30:52 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 2:54:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 9:10:22 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →