news 2026/9/11 3:14:32

Redis数据安全加固实战:访问控制、持久化与分布式锁

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Redis数据安全加固实战:访问控制、持久化与分布式锁

凌晨两点半,监控面板上Redis主节点的CPU使用率突然拉满,慢查询数量肉眼可见地往上跳。登录服务器一看,公网安全组里竟然挂着6379的放行规则,一台没有任何认证配置的Redis正被全网扫描工具轮番爆破。虽然最后没造成数据清空,但那一整晚排查的过程让我意识到一个扎心的事实:绝大多数团队对Redis数据安全的理解,还停留在“不崩就行”的层面。

Redis数据安全性这句话,落到工程实践里至少可以拆成四件事:防非法访问、防数据丢失、防运行阻塞、防脏读错写。这四件事分别对应网络层防护、持久化策略、应用层治理和分布式一致性。这篇文章我就按这几个维度,把我在生产环境里踩过的坑、排查过的事故、以及最终沉淀下来的一套可落地的加固方案,完整梳理一遍。无论你把Redis当缓存用、当计数器用,还是当分布式锁的底层存储,这篇文章都值得你花十分钟过一遍。

1. 先搞清楚Redis数据安全威胁模型的四个维度

很多人一提数据安全,第一反应就是“加个密码”。实际上Redis这类内存型数据库的安全问题,远比一条requirepass配置复杂得多。我在评估一个Redis实例的安全性时,通常按下面这张表来做威胁建模。

威胁类型实际路径后果示例
未授权访问端口暴露公网、空密码、protected-mode关闭数据被遍历、被篡改、被植入定时任务
弱口令爆破Redis认证协议简单,高频尝试AUTH实例被接管,数据被FLUSHALL清空
危险命令滥用KEYS、FLUSHALL、DEBUG等命令可执行阻塞主线程、数据批量删除、实例挂死
进程崩溃与宕机RDB/AOF配置不当、服务器断电丢失数分钟甚至全部数据
主从复制错乱failover后旧主重新加入、复制积压区溢出新主数据被旧主覆盖,数据回退
应用层逻辑隐患Big Key、热Key、序列化类型错乱慢查询拖垮实例、incr命令报错、缓存雪崩
分布式一致性问题锁超时失效、主从切换锁丢失并发重复执行、超卖、重复下单

这张表里的每一项,我都见过真实的生产事故。比较讽刺的是,最容易被团队忽视的恰恰是后半部分——很多人都认真配过bind和requirepass,但很少有人认真回答“Redis所在服务器突然断电,你会丢多少数据”“主从切换的瞬间,分布式锁还能不能保证互斥”这两个问题。

Redis的安全范围为什么和传统数据库不一样?关键在于它的定位。传统MySQL是唯一数据源,天然会有人认真对待备份和权限;Redis在很多团队眼里就是“加速层”,丢了可以回源数据库重建。可一旦Redis里存了库存、订单号、验证码、分布式锁、接口幂等标记这类有业务语义的数据,它就的的确确是业务系统的一部分了。此时再拿“缓存随便丢”的心态去运维,迟早会出事。

所以这篇文章后续所有内容,都围绕着上面表格里的四类风险展开:访问控制怎么收紧,持久化怎么保证不丢,应用怎么写才不把Redis搞挂,分布式场景怎么避免错写和重复执行。

2. 最容易出事的三个环节:未授权访问、弱口令、危险命令

这三个问题通常一起出现。早期Redis版本默认配置只监听127.0.0.1,这个默认值其实是相对安全的;真正出事的基本都是人为改配置改出来的:bind 0.0.0.0、protected-mode no、不设密码,三件套齐活之后,只要安全组或防火墙不小心放行了6379,你的Redis就等于裸奔在公网。

2.1 未授权访问为什么这么普遍

我接手过的一个项目,内网Redis居然能被开发机直接无密码连接,登录进去发现里面存了大量用户Session和验证码。查下来原因很典型:当初搭建时图省事,复制了一份网上流传的“通用配置”,里面直接写死了bind 0.0.0.0protected-mode no,然后一直没人改。

更隐蔽的情况是bind配置本身没错,但被部署在Docker容器里,端口通过-p 6379:6379直接映射到了宿主机公网地址。容器内部的bind 127.0.0.1只约束了容器网络栈,宿主机端口转发根本不受这个限制。这种“容器隔离错觉”让很多团队的Redis明明看着配置没问题,实际上公网一抓一个准。

检查方法很简单,在Redis服务器本地执行:

redis-cli -h 127.0.0.1 -p 6379 ping

如果能直接返回PONG而没提示NOAUTH,同时客户端连接来源又不限于127.0.0.1,那基本可以判定当前实例处于未授权访问状态。

2.2 弱口令和爆破:Redis认证机制的局限性

Redis的认证早期只有一层密码,也就是requirepass。它的问题在于:密码没有强度校验机制,而且Redis协议简单,攻击者可以用极低的成本暴力尝试。某些扫描工具甚至专门维护了一份“Redis常见弱密码表”,从redis、123456到passw0rd,挨个试。

还有一种思路是直接限制来源。生产环境的Redis只应该被特定应用服务器访问,安全组/防火墙层面做来源IP白名单,往往比一个强密码更有效。密码再强,也怕客户端连接串泄露;来源IP限制住,即使连接串泄露,攻击者也无法从外部触达。

2.3 危险命令:比删库更常见的是“手滑”和“爆破后的最后一击”

在Redis里,KEYS *FLUSHALLFLUSHDBCONFIG SETDEBUG这类命令的破坏力,很多人没有概念。

  • KEYS *在key数量达到百万级别时会阻塞主线程数秒甚至更久。
  • FLUSHALL一条命令清空所有数据,却不会像MySQL那样弹出任何确认。
  • CONFIG SET甚至可以在运行期动态修改配置,直接把保护机制关掉。

我见过不止一次线上事故,是因为同事在测试环境连错了Redis地址,一个FLUSHALL把预发环境的缓存全部清掉,接着流量全部打到数据库,把库打出了慢查询告警。

防御上比较通用的是两步:第一,在配置文件中用rename-command把危险命令改名或禁用;第二,Redis 6.0之后的ACL功能能做到按用户授权,比如给业务应用只开读写权限,不开CONFIGFLUSHALL等管理命令。

2.4 网络层安全:别把安全寄托在Redis自身

我在实际加固时会遵循一个原则:Redis自身的安全配置是纵深防御的一层,但绝不能是唯一一层。哪怕Redis配置得再严,操作系统层面的防火墙、云平台的安全组也要配合收敛端口暴露面。

落地的检查步骤大概是:

  1. 先看监听地址:ss -lntp | grep 6379,确认只监听了内网或回环地址。
  2. 确认云安全组和主机防火墙的入方向规则,没有放行0.0.0.0/0到6379。
  3. 在配置文件中开启protected-mode yes
  4. 设置强密码或启用ACL,禁止空密码启动。
  5. rename-command禁用KEYSFLUSHALLCONFIGDEBUG等管理类命令。
  6. 定期用扫描工具做一次端口自检,模拟公网视角看6379是否可达。

这套流程做完,未授权访问这个维度的风险基本就能堵住。

3. 数据不丢比想象中难:RDB、AOF与主从架构的数据可靠性边界

很多团队对Redis持久化的理解就是“开了AOF,应该不会丢数据了吧”。实际排查过事故之后,你会发现这句话离真相还差得很远。

3.1 RDB快照的丢失窗口

RDB是Redis默认的持久化方式,通过saveBGSAVE生成某个时间点的全量快照。它的最大特点是恢复快、文件紧凑,但缺点也明显:在两次快照之间的数据,一旦进程异常退出,就全部丢失。

默认配置是save 900 1save 300 10save 60 10000,意思是“900秒内有1次写入”“300秒内有10次写入”“60秒内有1万次写入”三个条件任一满足就触发快照。注意这个条件组合意味着低流量时段,理论上可能15分钟才打一次快照,这期间的数据丢失窗口就是15分钟。

3.2 AOF三种刷盘策略的取舍评估

AOF通过追加日志的方式记录每次写操作,重启时重放日志来恢复数据。它的关键参数是appendfsync,有三个取值:

appendfsync配置刷盘时机丢失窗口性能影响
always每条写命令都调用fsync最多一条命令最高,吞吐量明显下降
everysec每秒调用一次fsync最多1秒数据中等,生产环境常用
no交给操作系统决定不确定,可能较多最好,但安全性最低

生产环境我通常选everysec,在性能和安全性之间找一个平衡点。Redis官方文档的表述也是这样一个默认取向。

但这里要特别提醒一件事:AOF的“最多丢1秒”是建立在操作系统正常运行的前提下的。服务器物理宕机、内核panic、云主机被强制重启这类场景,数据丢失可能比预期更多。因为everysec策略下,Redis子进程独立执行fsync,如果操作系统还没来得及把缓冲落盘就断电,缓冲里积压的那一小段时间数据就没了。

3.3 从库强一致保护:min-replicas机制

主从架构下,很多人以为“有从库就等于数据安全”。但从库同步是有延迟的,主库会无脑接受写入并返回成功,从库还没跟上。如果此时主库宕机触发哨兵切换,新主库可能缺失了最后几秒的写入数据。更要命的是,如果旧主库恢复后重新加回集群,它发现自己落后了新主,会主动清空本地数据去追随新主,此时如果它的数据比新主还新,反而会把新主覆盖?不会,Redis的复制逻辑里旧主会被清空并复制新主数据,这会造成一个“回退”——原本已写入的数据被抹掉。

针对这种场景,Redis提供了min-replicas-to-writemin-replicas-max-lag两个参数,含义是:当健康的从库数量低于阈值,或从库的复制延迟超过了max-lag秒,主库就拒绝写入。这样可以在极端情况下放弃可用性,保数据一致性。

min-replicas-to-write 1 min-replicas-max-lag 10

这个配置的含义是,至少要有1个从库的延迟在10秒以内,主库才接受写入。如果所有从库都掉队了,主库会直接返回错误,而不是继续无脑写。代价是短暂的服务不可用,但换来的是一次灾难恢复后数据不会无故回退。

3.4 文件损坏与备份恢复演练

Redis的数据文件在极端情况下也会损坏,最常见的场景是AOF文件末尾写了半条命令就断电,此时Redis启动会拒绝加载AOF。处理办法是使用官方自带的修复工具:

redis-check-aof --fix appendonly.aof

它会扫描文件,截断异常尾部,尽可能保留可解析的数据。对RDB文件,可以使用redis-check-rdb来校验完整性。

但说实话,工具修复只是最后一道防线,更重要的习惯是定期做数据备份和恢复演练。我在团队里推行的做法是:

  1. 每天凌晨在从库上执行BGSAVE,把生成的dump.rdb同步到对象存储或异地服务器。
  2. 每月做一次恢复演练,从备份文件启动一个全新实例,让QA在实例上跑核心链路用例。
  3. 演练结果记录在案,一旦发现问题及时调整备份策略。

只有真正演练过,你才知道备份文件能不能用、恢复流程需要多长时间。别等到生产宕机了,才第一次尝试恢复数据。

4. 应用层常被忽视的隐性数据事故:Big Key、热Key与序列化异常

如果说前面两层是“数据库安全”的常规议题,那应用层和Redis交互时的安全治理,才是很多团队现有的真实短板。这类事故不涉及攻击者,纯粹是代码写法或使用姿势不对,却一样会导致数据错乱、服务雪崩。

4.1 Big Key阻塞主线程

Redis是单线程处理命令的,所有读写在同一个线程里串行执行。一个Big Key的读取或删除,会让整个实例卡住几十毫秒甚至几秒,期间所有其他命令全部排队等。

怎么定义Big Key?没有绝对统一的标准,我一般按照经验值来判断:

  • STRING类型value超过10KB,就算偏大。
  • LIST、SET、ZSET、HASH的元素数量超过5000个,或整体序列化后体积超过1MB,就要警惕。

发现Big Key的手段,比较稳妥的方式是用redis-cli --bigkeys来扫描,但要选在业务低峰期。这个命令会遍历所有key并统计最大的一些key,原理是逐个执行SCAN和类型专属命令,虽然不会阻塞主线程很久,但仍有性能开销,生产高峰期慎用。

Big Key的真正危害在于几个动作:

  • GET一个大字符串,网络传输耗时高,客户端超时概率增大。
  • DEL一个包含几百万元素的LIST,删除操作会阻塞主线程。
  • 集群模式下大key还会导致内存和数据访问倾斜到单个分片,其他分片闲着,这一个分片被打满。

我在排查一次Redis慢查询时,定位到一个HASH里塞了两百多万个字段,每次HGETALL耗时接近3秒,把这个key一拆,实例整体P99延迟立刻降了一半。

4.2 热Key放大单分片压力

热Key是一个key在短时间内被超高并发访问。比如秒杀商品的库存key、双十一的活动配置key,单key QPS可能占到整个实例的一半以上。单线程模型下,一个热Key的读取虽然不是特别耗时,但每个请求都要经过一次网络和协议解析,如果同一个key的并发太高,对应分片的CPU会先打满,进而拖慢该分片上的所有其他key。

常见解法是本地缓存+Redis兜底、把热key复制成多个带后缀的副本分散读压力、或者直接上Redis Cluster让key打散到不同分片。但注意,集群模式解决不了单key热点,因为同一个key只会落到一个分片。

4.3 序列化异常:increment报错的经典现场

很多Java团队使用Spring Data Redis时都遇到过这个报错:

ERR value is not an integer or out of range

这个报错的本意是:对一个非整数类型的值执行了INCR操作。但在实际场景里,出现原因往往是序列化器配置不一致造成的“类型错乱”。

举个例子,你用RedisTemplate默认的JDK序列化器写入了一个Integer对象,底层会把Java对象的二进制流加上类型头存进去。此时如果再用StringRedisTemplate去执行increment,Redis看到的value是一串不可解析的二进制内容,自然无法把它当作整数自增。

反过来的场景也很常见:先用StringRedisTemplate.set(key, "1")存了字符串,再用RedisTemplate.opsForValue().increment(key)自增,同样会因为这个key的value格式与当前序列化器不匹配而报错。

解决方案并不复杂,关键是统一序列化器:

  • 如果存储的都是字符串,就统一用StringRedisTemplate
  • 如果必须用RedisTemplate,则显式设置StringRedisSerializer作为key和value的序列化器,避免默认的JDK序列化。
  • 如果存的是对象,建议使用JSON序列化器,比如GenericJackson2JsonRedisSerializer。

我在团队里定了一条规矩:项目里只允许出现两种Redis访问方式,纯字符串操作一律走StringRedisTemplate,对象缓存一律走配置了JSON序列化器的RedisTemplate,禁止在同一个key上混用两种Template。

4.4 缓存穿透、击穿与雪崩

缓存层的三个经典问题——穿透、击穿、雪崩,本质上也属于数据安全范畴。它们带来的不是“数据被删”,而是“数据源被流量打垮”,进而导致整个业务链路不可用。

  • 穿透:查询一个不存在的key,缓存里没有,每次都打到数据库。
  • 击穿:某一个热点key过期瞬间,大量请求同时打到数据库。
  • 雪崩:大量key在同一时间过期,数据库压力瞬间飙升。

这三类问题的治理方案都比较成熟了:穿透用布隆过滤器或缓存空值;击穿用互斥锁重建缓存;雪崩给过期时间加随机扰动,或者用多级缓存。

我在实际项目里发现,很多团队配置了缓存过期时间但从不关注过期key的分布。一个比较有效的习惯是,给同类缓存key的TTL设置一个基础值加随机偏移,让过期时间在时间轴上均匀散开。

5. 分布式锁的误用与多实例一致性问题

Redis被广泛用来实现分布式锁,这本身没有问题,但分布式锁的安全边界比单机锁要复杂得多。我排查过的很多生产故障,比如秒杀超卖、重复下单、定时任务重复执行,最后都指向了同一个根源:分布式锁写错了。

5.1 加锁必须一条命令完成

最经典的错误写法是先SETNX再单独EXPIRE。这两条命令之间如果应用进程崩溃,或者Redis连接断开,锁就会变成永不过期的死锁。正确写法是用一条原子命令完成加锁和过期时间设置:

SET lock:order:1001 unique-token NX EX 30

对应的Java代码如下:

String lockKey = "lock:order:" + orderId; String requestId = UUID.randomUUID().toString(); Boolean locked = stringRedisTemplate.opsForValue() .setIfAbsent(lockKey, requestId, Duration.ofSeconds(30)); if (Boolean.TRUE.equals(locked)) { try { // 业务逻辑 } finally { // 释放锁时必须校验value是否还是自己的 String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; stringRedisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockKey), requestId); } }

注意释放锁不能用简单的DEL,因为可能出现这种情况:线程A持有锁,业务执行超过30秒锁自动过期,线程B拿到锁,此时线程A执行完业务,如果A直接DEL,会把B的锁误删掉。所以释放锁要用Lua脚本先比较value,确认是自己的再删除。

5.2 锁超时续期与看门狗

上面的写法默认要求业务在锁过期之前执行完。如果业务执行时间不可控,就需要给锁续期。Redisson内置的看门狗机制会自动续期,默认每10秒检查一次,如果锁还存在就自动续期到30秒,直到业务释放锁或者进程退出。

自己实现续期时要注意,续期本身也必须是一个原子操作,比较稳妥的方式是用Lua脚本,在续期前检查value是否仍是自己的。

if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('expire', KEYS[1], ARGV[2]) else return 0 end

5.3 主从切换与锁丢失的边界

Redis单实例分布式锁有个先天问题:如果主库加锁成功,但还没同步到从库,主库宕机触发哨兵切换,新主库上根本没有这把锁,另一个线程就能成功加上同一把锁。这时互斥被破坏,两个线程同时进入临界区。

对这个问题的处理业界存在争议。Redlock算法试图用多个独立Redis节点来降低锁丢失概率,但它在复杂网络环境下也有自身的理论争议。我的个人经验是:如果业务场景对互斥不是绝对严苛(比如下单防重,少一单多一单都能通过对账发现),那单Redis实例锁加上续期机制就够用了;如果场景真的需要强互斥,与其寄希望于Redlock,不如直接引入ZooKeeper或etcd这类带共识算法的协调服务。从成本、运维复杂度、可靠性三个方面综合来看,强一致分布式锁最可靠的家还是ZooKeeper/etcd。

5.4 分布式锁之外的幂等防线

分布式锁不是万能的。锁能挡住同一时刻的并发,但挡不住网络超时后的重复提交、消息队列的重试投递这类场景。更稳妥的做法是在数据层做幂等控制:业务表加唯一索引,处理前先查重、再插入,数据库唯一索引兜底。锁和幂等一起上,才能最大程度避免重复写数据。

6. 一套可以直接落地的Redis安全检查清单与常用排查命令

前面说了这么多理论,最后我整理一份可以直接拿去用的安全检查清单。这份清单是我在团队里做Redis巡检时每天都在用的,覆盖了配置层、网络层、数据层和应用层。

6.1 配置层自检表

检查项推荐配置检查命令
监听地址bind 127.0.0.1 或内网IPCONFIG GET bind
保护模式protected-mode yesCONFIG GET protected-mode
认证requirepass强密码,或ACL授权CONFIG GET requirepass
危险命令rename-command禁用或改名查看redis.conf
RDB快照按业务容忍度配置save策略CONFIG GET save
AOFappendonly yes,everysecCONFIG GET appendonly appendfsync
从库保护min-replicas-to-write和max-lag合理设置CONFIG GET min-replicas-*
版本至少6.x,推荐7.xredis-server --version

6.2 常用排查命令

# 查看当前连接客户端 redis-cli CLIENT LIST # 查看慢查询日志 redis-cli SLOWLOG GET 50 redis-cli SLOWLOG RESET # 查看运行信息 redis-cli INFO keyspace redis-cli INFO replication redis-cli INFO persistence # 查看ACL日志(Redis 6+) redis-cli ACL LOG # 扫描Big Key(低峰期执行) redis-cli --bigkeys # 查看某个key的内存占用 redis-cli --bigkeys 或 MEMORY USAGE key名

MONITOR命令也很常用,它可以实时打印Redis执行的所有命令,但生产环境务必慎用。MONITOR开着的时候,Redis会为每个命令额外做一次输出,对性能影响不小,我见过有人开着MONITOR做在线调试,结果Redis响应时间直接翻倍。调试完记得第一时间关掉。

Redis 6之后新增的ACL LOG很实用。它能记录因权限不足被拒绝的命令,排查时不用再傻乎乎地翻所有日志。比如怀疑某个客户端在尝试执行CONFIG操作,一条ACL LOG就能看到被拦截的记录。

6.3 安全基线之外的三点补充

第一,日志的审计往往被忽视。建议开启Redis的慢查询日志和ACL日志的持久化,为日后的安全事件回溯留下证据。虽然不能完全防止问题发生,但出了事能快速定位到人和时间。

第二,保持版本更新。Redis 7.0之后AOF文件采用了多部分格式,重写和加载效率提升明显,也修复了不少历史安全漏洞。如果一个实例已经运行了两年以上没动过版本,我建议列入升级计划,升级前先做全量备份和恢复演练。

第三,Redis未授权访问类的攻击在最近的安全通报里反复出现,我个人的观点是:不要高估自己团队的安全水位,每隔一个季度就按这份清单重新巡检一遍。配置被改回去、安全组被重新放行、新同事不知道规则误开端口,这些情况我都遇到过。安全不是一次性的项目,而是持续性的习惯。

从我多年的实战经验来看,Redis数据安全的本质,是“访问可控、数据可恢复、运行可预期、并发不踩踏”。访问控制做在前面,持久化兜住底,应用层写代码时多想一层,分布式场景对一致性边界有清醒认知,这四个维度都到位了,Redis基本就不会成为拖垮业务的短板。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/11 3:14:00

容器里跑安卓模拟器:一条命令完成 Docker 部署

容器里跑安卓模拟器&#xff1a;一条命令完成 Docker 部署 【免费下载链接】docker-android Android in docker solution with noVNC supported, video recording and mcp server 项目地址: https://gitcode.com/GitHub_Trending/do/docker-android 不想在宿主机装整套 …

作者头像 李华
网站建设 2026/9/11 3:12:37

ArduPilot 完整指南:如何用开源飞控快速上手无人机自主飞行

ArduPilot 完整指南&#xff1a;如何用开源飞控快速上手无人机自主飞行 【免费下载链接】ardupilot ArduPlane, ArduCopter, ArduRover, ArduSub source 项目地址: https://gitcode.com/GitHub_Trending/ar/ardupilot ArduPilot 是 DIY Drones 社区发起并维护的开源飞行…

作者头像 李华
网站建设 2026/9/11 3:10:47

用ResNet和AVEC2014实现抑郁症诊断:从数据预处理到模型训练全流程

简介&#xff1a;基于AVEC2014数据集与ResNet网络的抑郁症诊断Python源码&#xff0c;面向具备一定Python基础、希望入门深度学习医疗应用的开发者&#xff0c;完整覆盖从视频帧提取、人脸预处理、模型设计到训练测试的端到端流程&#xff0c;代码结构清晰&#xff0c;便于二次…

作者头像 李华
网站建设 2026/9/11 3:08:59

深入解析CMSIS-5:架构、模块与工程治理实践

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

作者头像 李华
网站建设 2026/9/11 3:08:13

基于Spring Boot的智能办公室管理系统:业务建模与权限设计实战

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

作者头像 李华
网站建设 2026/9/11 3:08:10

如何上手 ArduPilot:开源飞控固件从编译到模拟飞行的完整指南

如何上手 ArduPilot&#xff1a;开源飞控固件从编译到模拟飞行的完整指南 【免费下载链接】ardupilot ArduPlane, ArduCopter, ArduRover, ArduSub source 项目地址: https://gitcode.com/GitHub_Trending/ar/ardupilot 写一个能自主飞行的飞行器&#xff0c;最难的从来…

作者头像 李华