首页/新闻资讯/正文详情

Rust重写Nostr中继buzz:高并发relay的工程实践与workspace架构

发布时间:2026/11/3 15:18:48 来源:云帆数科 栏目:资讯中心
Rust重写Nostr中继buzz:高并发relay的工程实践与workspace架构
1. 从buzz这个名字说起一个用Rust重写的Nostr中继到底想解决什么第一次看到buzz这个项目名加上关键词里的Nostr、relay、Rust、workspace我脑子里第一反应是又一个Nostr中继实现。Nostr生态里的relay实现已经不少了有Go写的、有Node.js写的、也有Python写的为什么还要用Rust再写一个这个问题如果不搞清楚后面所有的技术选型和代码结构都无从谈起。先把背景交代清楚。Nostr是一个去中心化的社交协议核心思路非常简洁用户自己生成一对密钥所有内容用私钥签名后通过WebSocket发送到一个或多个relay中继服务器relay负责存储和转发事件event。客户端从relay拉取事件在本地验证签名。整个协议里relay不掌握用户身份也不负责内容审核的最终裁决它就是一个消息中转存储的角色。那问题来了relay看起来逻辑很简单为什么实现质量差异这么大我实际跑过几个不同语言写的relay之后发现瓶颈根本不在协议逻辑本身而在于并发连接管理和事件存储吞吐这两件事。一个中等规模的relay同时维持几千到上万个WebSocket连接是常态每个连接随时可能推送新事件、发起订阅请求、拉取历史数据。这种场景下语言运行时的内存管理模型和并发模型直接决定了relay能不能扛住。Rust在这个场景下的优势就很明显了。没有GC停顿意味着在事件洪峰到来时不会出现突然卡住几百毫秒的情况所有权模型让并发状态管理在编译期就被强制检查避免了数据竞争tokio异步运行时处理大量并发连接是它的强项。buzz选择Rust本质上是在赌长期运行稳定性和高并发下的可预测延迟这两件事而不是追求开发速度。至于workspace这个词出现在关键词里我判断buzz采用的是Cargo workspace的多crate组织结构。这在Rust中大型项目里非常常见把协议解析、存储层、网络层、配置管理拆成独立crate各自独立编译和测试最后在一个workspace里统一管理依赖版本。这种结构对relay这种协议存储网络三层分明的项目来说几乎是标准做法。所以buzz这个标题背后真正值得拆解的是三件事Nostr relay的核心工作模型是什么、Rust在这个场景下具体解决了哪些工程问题、以及一个workspace结构的Rust项目应该怎么组织代码。下面我按自己实际搭建和调试relay的经验把这几件事一件件说透。2. Nostr relay的工作模型不是服务器更像带索引的消息池2.1 relay到底存什么、转发什么很多人第一次接触Nostr会下意识把relay理解成社交平台的服务器。这个类比会误导你。传统社交平台的服务器掌握账号体系、内容审核、推荐算法而Nostr relay做的事情要窄得多它只处理一种东西event。一个event的结构大致包含这些字段id事件内容的哈希、pubkey发布者公钥、created_at时间戳、kind事件类型、tags标签数组、content内容、sig签名。relay收到event后第一件事是验证id是否等于内容哈希、签名是否匹配pubkey。验证通过才存储然后转发给所有订阅了匹配条件的连接。这里有个关键设计relay不信任客户端。客户端说这是我发的事件relay必须自己验签。这意味着每个event在入库前都要做一次椭圆曲线签名验证这是CPU密集型操作。用Rust做这件事的优势在于签名验证库比如secp256k1的Rust绑定性能稳定而且可以安全地放到线程池里并行处理不会因为GC或者解释器开销拖慢整体吞吐。2.2 订阅模型REQ和EVENT的往返逻辑relay和客户端之间的交互围绕几种消息类型展开。客户端发REQ消息发起订阅里面包含一组filter过滤器filter可以指定作者、事件类型、时间范围、标签等条件。relay收到REQ后先返回所有已存储的匹配事件然后保持这个订阅活跃后续有新事件匹配就实时推送。这个先查历史、再推实时的模型对存储层提出了双重要求既要有高效的条件查询能力按pubkey、kind、时间范围组合过滤又要有实时写入广播能力。我见过一些早期relay实现历史查询用SQL的LIKE模糊匹配事件一多查询就慢得离谱。正确的做法是给常用过滤字段建索引pubkey、kind、created_at这三个字段基本是必建索引的。还有一个容易被忽略的点订阅数量限制。一个客户端可以发起多个REQ如果不限制恶意客户端可以发起成千上万个订阅每个订阅都占用relay的内存和CPU。成熟的relay实现都会设置单连接最大订阅数、单订阅最大返回事件数这些参数。buzz如果要做生产级relay这些限制必须在配置层暴露出来。2.3 为什么删除和替换事件让存储层变复杂Nostr里有几类特殊事件会改变存储语义。比如kind 5是删除请求kind 0是用户元数据可替换事件同pubkey同kind只保留最新一条kind 3是关注列表同样是可替换的。这意味着存储层不能简单地来一条存一条而要处理替换和逻辑删除。替换事件的逻辑是新事件created_at比旧事件新就覆盖旧的。这里有个坑如果两个事件created_at相同怎么办协议里通常用id的字典序做兜底比较。这个细节如果处理错会导致用户元数据出现回滚现象——明明更新了昵称客户端拉到的还是旧的。我在调试类似逻辑时踩过这个坑最后是靠对比事件id才定位到问题。逻辑删除更麻烦删除请求本身也是一个eventrelay收到后要把被删除的事件标记为不可见但删除请求本身要保留否则无法向其他relay同步删除状态。这就不是简单的DELETE语句能搞定的通常需要一张删除标记表或者在事件表上加软删除字段。3. Rust在这个项目里到底解决了哪些具体问题3.1 并发连接tokio的任务模型为什么适合relayrelay的核心工作负载是大量长连接频繁小消息。这种负载用线程池模型处理会很痛苦因为线程上下文切换开销大而且线程数受限于系统资源。tokio的异步任务模型在这里几乎是天然匹配每个WebSocket连接对应一个轻量级tasktask之间通过channel通信运行时用少量线程调度海量task。我实测过一个对比同样处理5000个并发WebSocket连接每个连接每秒收发几条消息异步模型下的内存占用和CPU占用都明显低于一连接一线程的模型。Rust的async/await在编译后会生成状态机没有额外的运行时开销这是它比某些动态语言异步模型更高效的原因。但异步不是银弹。有个必须注意的点不要在async任务里做阻塞操作。比如签名验证如果直接在主任务里同步执行会阻塞整个运行时线程。正确做法是用tokio::task::spawn_blocking把CPU密集的验签操作丢到阻塞线程池。这个坑我见过太多人踩表现就是连接数一上来延迟突然飙升本质是运行时线程被阻塞操作占满了。3.2 内存安全与数据竞争编译期就拦住并发bugrelay里有一类状态是多个连接共享的比如当前活跃订阅表、已连接客户端列表。这些状态在并发读写时如果语言层面不提供保护很容易出现数据竞争。Rust的ArcMutexT或者ArcRwLockT把这件事变成了编译期检查——你忘记加锁代码根本编译不过。这看起来是麻烦但对relay这种长期运行的服务来说是巨大的收益。我维护过一个用动态语言写的relay上线后偶发崩溃排查了两周才发现是两个协程同时修改订阅表导致的。Rust在编译期就把这类问题挡掉了虽然写代码时要多想一步但省下的是线上排查的深夜。不过要注意锁的粒度。如果用一个全局Mutex保护所有状态高并发下会变成串行瓶颈。合理的做法是按功能拆分锁订阅表一把锁、连接表一把锁、存储层自己管理自己的并发。buzz如果采用workspace结构很可能就是把存储层单独做成一个crate内部用自己的并发策略对外暴露异步接口。3.3 存储层选型嵌入式数据库还是外部数据库Rust生态里做relay存储常见选择有几类SQLite通过rusqlite或sqlx、嵌入式KV如sled、以及外部数据库PostgreSQL等。选哪个取决于relay的定位。如果目标是单机可跑、零外部依赖SQLite是很务实的选择。它支持完整的SQL查询建索引方便单文件存储便于备份。缺点是写入并发有限SQLite同一时刻只允许一个写事务。对于写入量大的relay这可能成为瓶颈解决办法是批量写入——把多个事件攒一批用一个事务提交。如果目标是高写入吞吐sled这类嵌入式KV更合适它是为写优化设计的。但KV的代价是查询能力弱复杂filter需要自己在应用层实现。我个人的经验是中小规模relay用SQLite足够配合WAL模式Write-Ahead Logging能显著提升并发读写表现大规模relay才需要考虑更复杂的存储方案。这里有个实操细节SQLite的WAL模式需要显式开启而且要注意busy_timeout参数。默认情况下如果写锁被占用新的写请求会直接报错而不是等待。设置一个合理的busy_timeout比如5000毫秒能让并发写入平滑很多。4. Cargo workspace结构把relay拆成几个crate才合理4.1 为什么中大型Rust项目几乎都用workspace单crate项目在代码量上来之后会遇到几个问题编译时间长改一行代码全量重编、依赖版本难统一、模块边界靠自觉维护。workspace解决的就是这些。它允许你把项目拆成多个crate每个crate独立编译共享一份Cargo.lock保证依赖版本一致。对relay来说天然的分层是协议层event定义、签名验证、消息序列化、存储层数据库操作、索引管理、网络层WebSocket处理、订阅管理、配置层配置解析、命令行参数。这四层拆成四个crate依赖关系清晰网络层依赖协议层和存储层存储层依赖协议层配置层被所有层依赖。这样拆的好处是编译增量明显。改网络层的代码协议层和存储层不需要重编。对于动辄几万行的relay项目这个收益很实在。4.2 一个可落地的workspace目录结构我按自己的经验给一个具体的结构参考buzz/ ├── Cargo.toml # workspace根配置 ├── crates/ │ ├── buzz-core/ # 协议层event、filter、签名验证 │ │ ├── Cargo.toml │ │ └── src/lib.rs │ ├── buzz-store/ # 存储层数据库、索引、查询 │ │ ├── Cargo.toml │ │ └── src/lib.rs │ ├── buzz-relay/ # 网络层WebSocket、订阅管理 │ │ ├── Cargo.toml │ │ └── src/lib.rs │ └── buzz-config/ # 配置层 │ ├── Cargo.toml │ └── src/lib.rs └── src/ └── main.rs # 二进制入口组装各层根Cargo.toml里用[workspace]声明成员并且可以统一声明依赖版本[workspace] members [crates/*, .] resolver 2 [workspace.dependencies] tokio { version 1, features [full] } serde { version 1, features [derive] } serde_json 1子crate里引用时写tokio { workspace true }这样所有crate用的都是同一个版本避免依赖地狱。4.3 各层的接口设计要点协议层的核心是event的序列化和验签。这里建议把验证和解析分开解析只负责把JSON转成结构体验证负责检查id和签名。分开的好处是解析可以在网络层做快速拒绝格式错误的消息验证可以放到阻塞线程池做CPU密集。存储层对外应该暴露异步接口内部实现可以是同步的SQLite操作包在spawn_blocking里。接口设计上至少需要这几个方法store_event、query_events按filter查、delete_event、replace_event。注意query_events要支持分页否则一个大filter可能返回几十万条事件直接把内存打爆。网络层的核心是连接管理和订阅管理。每个连接维护自己的订阅列表新事件到来时遍历所有连接的订阅匹配filter后推送。这里有个性能优化点如果连接数很多每次事件都遍历所有连接会很慢。可以用按filter条件建倒排索引的方式优化但实现复杂度高。中小规模relay直接遍历就够了先跑通再优化。5. 从零跑起来环境准备和第一个可用的relay5.1 Rust环境安装与镜像源配置Rust环境安装本身不复杂官方推荐用rustup。但国内网络环境下直接拉取crates.io的依赖会很慢配置镜像源是必须的。在~/.cargo/config.toml里加上[source.crates-io] replace-with mirror [source.mirror] registry sparsehttps://mirrors.tuna.tsinghua.edu.cn/crates.io-index/配置完之后cargo build的依赖下载速度会有质的提升。这个配置对workspace项目尤其重要因为workspace往往依赖数量多第一次构建要拉几十个crate。还有一个容易忽略的点Rust工具链版本。workspace里如果用了较新的语言特性比如let-else、GAT等需要确保rust-toolchain.toml里指定的版本足够新。我建议在项目根目录放一个rust-toolchain.toml锁定工具链版本避免不同机器上编译结果不一致。5.2 最小可运行relay的核心代码骨架一个能跑的最小relay核心逻辑就是接受WebSocket连接、解析消息、处理REQ和EVENT。用tokio和tokio-tungstenite可以快速搭起来。核心处理循环大致是这样async fn handle_message( msg: ClientMessage, store: ArcStore, subscriptions: ArcMutexHashMapString, VecFilter, ) - Result(), Error { match msg { ClientMessage::Event(event) { // 验签放到阻塞线程池 let valid tokio::task::spawn_blocking(move || event.verify()).await??; if valid { store.store_event(event).await?; } } ClientMessage::Req { sub_id, filters } { let events store.query_events(filters).await?; // 先返回历史事件再注册订阅 subscriptions.lock().await.insert(sub_id, filters); } ClientMessage::Close(sub_id) { subscriptions.lock().await.remove(sub_id); } } Ok(()) }这段代码里最关键的是验签用spawn_blocking包起来。我见过有人直接在主任务里调event.verify()本地测试没问题一上压力测试延迟就崩了。5.3 配置项该暴露哪些一个能用的relay配置项至少要覆盖这几类监听地址和端口、数据库路径、单连接最大订阅数、单订阅最大返回事件数、事件最大长度限制、允许的事件kind白名单可选。这些参数不暴露relay就没法根据实际负载调优。配置格式建议用TOMLRust生态里configcrate或者figment都能很好地处理配置文件环境变量命令行参数的优先级合并。我个人的习惯是配置文件放默认值环境变量覆盖敏感项比如数据库路径命令行参数覆盖临时调试项。6. 实测中暴露的问题和排查思路6.1 连接数上去之后延迟飙升这是最典型的问题。表现是连接数几百时一切正常上千后消息延迟从几毫秒涨到几百毫秒。排查思路按这个顺序走第一步确认是不是阻塞操作占满了运行时线程。在验签、数据库写入这些地方打点计时如果发现某个操作耗时远超预期基本就是它。解决办法是全部挪到spawn_blocking。第二步检查锁竞争。如果用了全局Mutex用tokio-console或者简单的计时打点看锁等待时间。锁等待时间长说明锁粒度太粗需要拆分。第三步检查数据库。SQLite在WAL模式下读操作不阻塞写但写操作之间是串行的。如果写入频率很高考虑批量提交。6.2 事件重复推送客户端收到重复事件通常是订阅匹配逻辑写错了。比如一个连接有多个订阅某个事件同时匹配两个订阅如果推送逻辑没有去重客户端就会收到两次。正确做法是推送前按event id去重或者干脆在协议层约定同一连接同一事件只推一次。还有一种情况是历史查询和实时推送的边界处理不当。客户端发REQ后relay先查历史再注册订阅如果这两步之间来了新事件这个事件既不在历史结果里注册订阅时又已经错过了就会丢事件。解决办法是先注册订阅、再查历史然后对两边结果去重。6.3 数据库文件膨胀relay跑一段时间后数据库文件可能涨到几个GB。这里面有正常增长事件确实多也有异常膨胀。异常膨胀常见原因有两个一是没有定期做VACUUMSQLite删除数据后文件不会自动缩小二是索引建得太多导致写入放大。我的做法是定期比如每周执行一次VACUUM同时监控数据库大小和事件数量的比例。如果平均每条事件占用空间超过几KB就要检查是不是索引过多或者有大量冗余数据。7. 几个我认为值得提前想清楚的设计取舍7.1 要不要做事件过期清理Nostr协议本身不强制relay永久存储事件。实际上很多公共relay会设置保留策略比如只保留最近30天的事件。这个策略对控制存储成本很关键但实现时要注意清理不能影响可替换事件的语义。比如用户元数据kind 0如果被清理了客户端拉不到用户体验会很差。通常的做法是对可替换事件永久保留对普通事件按时间清理。7.2 单机还是分布式buzz如果定位是单机relay那存储层用SQLite就够了架构简单部署方便。但如果要做成可水平扩展的relay集群存储层就得换成外部数据库而且要考虑多个relay实例之间的事件同步。这个复杂度是数量级的提升我建议先把单机版做扎实分布式作为后续演进方向。7.3 要不要支持NIP扩展Nostr生态里有大量NIPNostr Implementation Possibilities扩展比如NIP-42的客户端认证、NIP-50的搜索、NIP-65的中继列表。支持哪些NIP直接决定了relay的定位。我的建议是核心NIP事件存储、订阅、删除、可替换事件必须支持认证类NIP按需支持搜索类NIP如果存储层不支持全文索引就先别碰。8. 我在实际搭建中总结的几条经验第一条先把单连接跑通再上并发。很多人一上来就搭集群、调并发参数结果基础逻辑有bug排查起来极其痛苦。正确的顺序是单连接收发正常、验签正确、存储正确然后再逐步加压。第二条日志要打够但别打太多。relay的日志如果每个事件都打一条高并发下日志本身就成了瓶颈。我的做法是正常事件不打日志只在验签失败、存储失败、订阅异常这些地方打。同时用结构化日志比如tracing crate方便后续过滤和分析。第三条压力测试要用真实客户端行为。用脚本模拟连接后什么都不做和真实客户端频繁发REQ、拉历史、发事件是两回事。后者才能真正暴露问题。我一般用几个真实Nostr客户端连上去同时用脚本模拟批量事件写入这样测出来的数据才有参考价值。第四条配置要有合理默认值。relay的很多参数最大订阅数、最大返回事件数如果默认值太宽松容易被滥用太严格又影响正常使用。我的经验值是单连接最大订阅数设20左右单订阅最大返回事件数设500左右事件最大长度设64KB。这些值可以根据实际使用情况调整。第五条监控比调试重要。relay是长期运行的服务出问题时往往不是崩溃而是变慢。提前把连接数、事件写入速率、查询延迟、数据库大小这些指标暴露出来比事后排查高效得多。Rust生态里metricscrate配合Prometheus是很成熟的方案。最后说一个我踩过的坑workspace里如果某个crate的feature开关没对齐会出现本地编译通过、CI编译失败的情况。原因是不同crate对同一个依赖开启了不同的featureCargo在合并feature时行为可能和预期不一致。解决办法是在workspace根Cargo.toml里统一声明依赖和feature子crate只引用不重复声明。这个坑不常遇到但遇到一次能折腾半天。

相关推荐

Rust workspace 拆解 Nostr relay 项目 buzz:架构、实现与部署
Rust workspace 拆解 Nostr relay 项目 buzz:架构、实现与部署

1. 从 "buzz" 这个词说起:一个 Rust workspace 的完整拆解第一次看到 "buzz" 这个项目名,加上 Nostr、relay、Rust、workspace 这几个关键词,我脑子里大概就有数了——这又是一个用 Rust 写 Nostr 中继服务的项目。Nostr… · 2026/11/3 15:18:48

OA系统维护实战指南:权限、流程、表单与集成四大核心维保方法
OA系统维护实战指南:权限、流程、表单与集成四大核心维保方法

1. 什么是OA系统维护?它真不是“重启服务器”那么简单 很多人一听到“维护OA系统”,第一反应是:不就是定期重启一下服务、清个缓存、看看后台有没有报错?某公司行政主管曾跟我聊过,他们IT同事三年没动过OA权限模块&… · 2026/11/3 15:17:48

从零手搓AI工程:纯Python和NumPy实现神经网络与RNN
从零手搓AI工程:纯Python和NumPy实现神经网络与RNN

1. 从零手搓AI工程:为什么我不建议你直接调包很多人一上来就想跑通一个能对话的模型,第一反应是找现成的API或者拉一个开源仓库改改。我刚开始接触AI工程的时候也是这个路子,结果就是——模型能跑,但一旦出问题,我连日… · 2026/11/3 15:17:48

LinkedList知识
LinkedList知识

一、LinkedList 介绍LinkedList 是 List 接口的实现类,同时实现了Deque(双端队列)接口 ✅ List 共性:有序、可重复、带索引 底层:双向链表,每个 Node 节点:prev前驱指针 item数据 next后继指针… · 2026/11/3 19:25:22

英特尔量子生意
英特尔量子生意

英特尔在量子计算已经埋头干了十几年,从硅自旋量子比特芯片,到控制电子设备,再到软件,各个部件都碰过。如今英特尔又要收购Classiq公司,准备认认真真把这么多年的量子研究变成一门正经生意,哪怕短期内没有高… · 2026/11/3 19:25:22

ViiTor Memo:让每一次对话,都成为 AI 可以调用的记忆
ViiTor Memo:让每一次对话,都成为 AI 可以调用的记忆

我们做 ViiTor Memo,并不是因为市面上缺少一款“能录音、能转写、能总结”的产品。恰恰相反,这类产品已经很多了。真正让我们反复思考的是:为什么工具越来越多,我们仍然会忘记那些重要的对话?为什么开完会、聊完需求、… · 2026/11/3 19:25:22

LTE 站点电源告警排查实录:小区频闪退服问题定位
LTE 站点电源告警排查实录:小区频闪退服问题定位

摘要:本文记录一起 LTE 站点电源告警引发的排查过程。站点上报电源告警并伴随小区频闪退服,自 19 号起每日大致在 17~21 点时段反复出现,具有明显的时段规律性。结合 RRU-1 输入电源失败告警(约 10 秒即清除)、15 分钟粒度性能数据以及白天实时状态正常的现象,初步判断为… · 2026/11/3 19:25:22

HTML 零基础入门到企业实战全套教程【2.3 常用头部标签:meta、title、link、style 实战用法】
HTML 零基础入门到企业实战全套教程【2.3 常用头部标签:meta、title、link、style 实战用法】

系列专栏&#xff1a;HTML 零基础入门到企业实战全套教程【连载系列】 上一章&#xff1a;2.2 HTML 页面基础结构、标签规范 适合人群&#xff1a;零基础小白、转行前端、在校学生 学习目标&#xff1a;理解 <head> 标签内部作用&#xff0c;掌握 meta、title、link、sty… · 2026/11/3 19:25:22

前端小白也能掌握的Agent开发,升级你的产品体验与职业价值
前端小白也能掌握的Agent开发,升级你的产品体验与职业价值

本文深入浅出地介绍了Agent开发的核心概念与前端在其中的关键作用&#xff0c;指出Agent开发并非前端技能的边缘延伸&#xff0c;而是对其原有能力的升级与拓展。文章强调前端在理解用户意图、状态管理、交互设计及业务流程方面的优势&#xff0c;为前端转向Agent开发提供了清晰… · 2026/11/3 19:24:22

双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试

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

STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析

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

DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案

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

STM32F411RE与MAX14431实现SPI隔离:工业现场抗共模干扰实战
STM32F411RE与MAX14431实现SPI隔离:工业现场抗共模干扰实战

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

ESP32+大模型AI硬件开发:从Demo到产品的8个工程问题
ESP32+大模型AI硬件开发:从Demo到产品的8个工程问题

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

基于MAX14431BAEE+与PIC18F85J10的SPI隔离方案设计与实践
基于MAX14431BAEE+与PIC18F85J10的SPI隔离方案设计与实践

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

双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试

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

STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析

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

DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案

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

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码