news 2026/9/16 3:15:00

Redis核心场景实战:从缓存穿透到分布式锁的15个案例

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Redis核心场景实战:从缓存穿透到分布式锁的15个案例

Redis这玩意儿,我前后用了快十年。从最早只是拿它做某个后台模块的本地缓存,到后来在微服务架构里当分布式锁、扛排行榜、处理延迟任务,一路踩过的坑确实不少。一开始我也觉得它无非就是个厉害点的HashMap,但用久了才意识到,Redis真正的价值不只是“快”,而是它自带的那套丰富数据结构,能帮你把一堆原本要绕很多弯、写很多代码的问题,用几条命令就干净利落地解决掉。

这篇文章把我日常项目里高频出现的15种Redis使用场景整理出来。每个场景我都会讲清楚它到底解决什么问题、底层怎么运作、实际应用中容易踩哪些坑。内容偏实操,适合正在做后端开发、准备做架构设计,或者最近在刷Redis面试题的朋友。即便是刚接触Redis的新人,照着里面的思路和命令去试,也能很快把Redis从“会用”变成“用得稳”。

1. 缓存类场景:让后端真正“快起来”

1.1 场景一:缓存穿透,用空对象和布隆过滤器兜底

缓存穿透形容的是这样一个问题:客户端疯狂请求一个在数据库里根本不存在的key,比如传一个不存在的用户ID。第一次查缓存,发现没有;于是穿透到数据库,数据库里也查不到;然后这个key根本不会被写入缓存。结果就是每个请求都会直接打到数据库,流量一大,DB就扛不住了。

我在生产环境处理这个问题的第一道防线,是给“查不到”的结果也做一层缓存。数据库查询返回空,就向Redis写入一个空值并设置短期TTL,比如60秒:

SET user:info:99999 "" EX 60

这样后续对同一个不存在key的请求,会在Redis这一层直接拿到空结果,不会再去数据库走一遍。

不过空对象缓存有两个毛病。第一,恶意请求如果伪造大量随机ID,每个ID都写一条空缓存,Redis内存会白白被吃掉不少。第二,业务上“不存在”和“系统错误”如果都返回空,容易被其他模块误判。所以更推荐的方式是前置一层布隆过滤器(Bloom Filter),在请求进入Redis之前,直接用过滤器判断“这个key是否大概率存在”。过滤器说不存在,直接返回;过滤器说存在,再走缓存+DB的流程。Redis官方模块RedisBloom提供了现成命令:

BF.RESERVE user_filter 0.01 100000 BF.ADD user_filter user:99999 BF.EXISTS user_filter user:99999

0.01表示误判率,100000表示预期容量。布隆过滤器有个特点:它只会误判“存在”,不会误判“不存在”,所以在缓存穿透场景下使用非常合适。

注意:布隆过滤器是用小概率误判换内存占用。别把容量设得太小,否则误判率会飙升,过滤效果大打折扣。

1.2 场景二:缓存击穿,热点key过期的并发风暴

缓存穿透是“查不存在的内容”,缓存击穿则是“查存在但刚好过期的热点内容”。一个非常热的key,比如某款秒杀商品的详情数据,正常情况下都命中了缓存。但就在它过期的那一瞬间,成千上万个请求同时发现缓存没有,又同时打到数据库,DB瞬间就顶不住了。

处理击穿的思路有两个方向。

第一个方向是互斥锁。用Redis的SETNX命令实现:当某个热点key的缓存为null时,不是所有请求都去数据库查,而是先尝试获取一把分布式锁。拿到锁的线程去数据库查询并回写缓存,其他线程短暂自旋等待后再次读取缓存:

SET lock:product:10001 unique_request_id NX PX 10000

如果设置成功,说明拿到了锁,执行业务回源。设置失败就等几十毫秒后重新走一遍查询流程。这里必须加过期时间,防止拿到锁的线程突然宕机导致死锁。

第二个方向是逻辑过期。value里存的不再是纯粹的业务数据,而是“数据+过期时间点”。请求到来后发现逻辑时间已过期,不会立刻删除缓存,而是让一个线程去数据库更新数据,其余线程继续读旧数据。这种方式用户体验更好,但实现上要处理好异步回源和写线程冲突的细节。

实操心得:热点key的过期时间千万别设成固定值。比如你缓存了三分钟的秒杀商品,三分钟后肯定会有一次集中过期请求。给过期时间加一个随机偏移,比如180秒到300秒之间随机,能很有效地降低集体失效的风险。

1.3 场景三:缓存雪崩,大量key同时失效或Redis彻底不可用

雪崩比穿透和击穿更可怕。它通常指两种情况:一种是缓存层里大量key在同一时间段集中过期,导致请求大面积穿透到数据库;另一种是Redis服务本身挂了,所有请求直接打到数据库,整个后端被压垮。

针对第一种情况,核心手段是过期时间随机化。批量写入缓存时,最好给TTL加一个随机数。比如基础过期时间是300秒,那实际写入时在300到600秒之间随机选一个值。这样积压到同一时刻过期的概率会大幅降低。

针对第二种情况,单靠Redis自身不够,需要做高可用部署。最经典的是主从复制加哨兵模式,主节点负责读写,从节点负责备份和故障转移。主节点挂了之后,哨兵会自动提升某个从节点成为新主节点,应用侧感知不到明显中断。更精细一点的,可以在业务侧增加本地缓存兜底,Redis不可用时短暂返回本地旧数据,给故障恢复争取时间。

如果是用Docker Compose部署生产环境Redis,我一般建议至少开一个主节点加两个从节点,再配三个哨兵实例。etcd、consul之类的动态服务发现如果能集成,应用配置里的Redis地址就可以做成动态感知,故障切换后客户端自动重新连接新的主节点,这也是很多公司在高并发场景下的标配做法。

1.4 场景四:缓存与数据库双写一致性,延时双删与最终一致

写缓存和写数据库,谁先谁后?这个问题争论了很多年。我实践下来的结论是:日常业务中,不要试图去做强一致,能把“最终一致”做好已经能解决绝大多数问题。

最常用的是Cache Aside模式。读的时候先读缓存,缓存没有就读数据库,然后回写缓存。写的时候先更新数据库,再删除缓存。这里很多人会问:为什么不更新缓存而是删除缓存?因为频繁的更新操作可能让缓存里的数据还没被读就走废了,删除是懒加载,下一次读的时候再回写,更省资源。

“先更新数据库,再删除缓存”也会有一个坑:如果删除缓存失败,缓存里留的还是旧数据。解决手段是延时双删。先删除缓存,再更新数据库,稍微休眠几百毫秒后再次删除缓存。这个二次删除主要对付并发请求期间,某个线程读到了旧数据并且重新写回缓存的“脏写”窗口期。具体延时值要根据业务读取速度来定,一般500ms到1000ms足够了。

还能更彻底一点,用一个后台任务监听数据库的binlog变更事件,数据发生变化时主动删除对应缓存。这样业务代码不用纠缠删除时机,数据库变更和缓存清理通过消息异步解耦,消息队列负责保证这个动作一定被执行。这个方案实现起来略重,但可靠性明显更高。

有个别场景是需要强一致的,比如金额、库存之类的敏感数据。这时候我一般不推荐用Redis做主存储,而是把数据库当唯一事实源,用事务保证最终一致性,Redis只承担读加速的职责。

2. 分布式协作类场景:多实例协调的“内存中间人”

2.1 场景五:分布式锁,解决并发抢占的问题

在单体应用里,多线程竞争共享资源可以用synchronized或者ReentrantLock。但服务拆成多个实例之后,A实例和B实例同时操作同一个订单状态,本地的锁已经管不到对方了。这时候就需要一把全局锁,Redis天然适合干这个活。

最简单的分布式锁就是SETNX。但光有SETNX是不够的,业界普遍使用的是带唯一请求ID和过期时间的原子命令:

SET lock:order:10001 req_001 NX PX 30000

解锁时不能直接DEL,因为有可能当前线程自己的锁已经过期,另一个线程拿到了同一把锁的新的锁权,你贸然删除会把自己的锁释放掉别人的锁。所以解锁操作一定要校验唯一ID,并且把校验和删除放在同一个Lua脚本里保证原子性:

if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("DEL", KEYS[1]) else return 0 end

生产环境我更推荐直接用Redisson框架。它封装好了看门狗续期机制:一个线程拿到锁之后,如果业务还没执行完,看门狗会自动给锁续期,避免业务没跑完锁就过期了。用Java写起来基本上是:

RLock lock = redissonClient.getLock("order:" + orderId); lock.lock(30, TimeUnit.SECONDS); try { // 业务逻辑 } finally { lock.unlock(); }

这里要留个心:分布式锁能解决大多数互斥需求,但如果业务对安全性要求极高,比如防重复扣款,可能还需要配合数据库唯一索引或状态机做最终兜底,不能把所有希望都押在一把Redis锁上。

2.2 场景六:分布式Session共享,解决多实例登录态丢失

传统单体场景,Session存在应用服务器的内存里。到了分布式部署,用户第一次请求落在A机器,第二次请求被负载均衡转发到B机器,如果Session不在B机器上,用户就会被判定为未登录。解决思路是把Session数据从单机内存搬到集中存储,而Redis就是最常用的存储载体。

Spring Boot生态里用Spring Session Data Redis非常方便,引入依赖并配置后,HttpSession的实现自动切换为Redis存储:

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

配置好Redis连接之后,项目里写的session.setAttribute、session.getAttribute底层都由Redis完成,前端Cookie里的Session ID仍然保持不变。这个方案最大的优势是业务代码几乎零侵入,只要注意序列化方式。

说到序列化就不得不提一个常见坑。Spring Session默认的序列化器是JDK序列化,它会把数据以Java二进制形式写进Redis,看起来就是一堆乱码,排查问题时非常痛苦,而且跨语言基本没法读。所以建议在配置里显式替换成JSON序列化:

@Bean public RedisSerializer<Object> springSessionDefaultRedisSerializer() { return new GenericJackson2JsonRedisSerializer(); }

这样做还有个附带好处:如果未来需要把Session数据交给数据分析团队或运维调试,他们可以直接从Redis里读出明文信息。

2.3 场景七:轻量消息队列,用List实现可靠任务分发

很多团队一提到消息队列就上Kafka或者RabbitMQ,这个选择没错,但如果你只是想要一个轻量级的任务队列,不想引入额外的中间件负担,Redis的List结构完全可以胜任。

List自带LPUSH和BRPOP两个命令。LPUSH从队列左侧写入消息,BRPOP从右侧阻塞读取消息。阻塞读取保证了没有消息时消费者不会空轮询,而是挂起等待,节省CPU:

LPUSH task:queue "{json payload}" BRPOP task:queue 0

在可靠性方面,可以用BRPOPLPUSH做消息确认。读消息的同时把消息备份到一个处理中队列,等业务处理成功后主动清除备份;如果消费者处理中途宕机,备份队列里的消息还能被重新捞起来处理。这个机制虽然简单,但比纯LPUSH/BRPOP多了一层可恢复能力。

当然,这种“列表型队列”的局限性也很明显。它没有真正的消费者组概念,多个消费者同时BRPOP时,消息会分发给其中一个消费者,不会广播。做不到分区有序、回溯消费等高级语义。所以我的建议是:任务量少、逻辑简单、不需要复杂路由的轻量梯队用Redis没问题;真正核心的异步消息链路,还是上专业的消息队列更稳妥。

还有一个泄露给新手的细节:批量投递消息时,最好用LPUSH一次塞一个数组,减少网络RTT;消费者端可以用LRANGE加LTRIM实现批量获取并裁剪,吞吐量比一条一条读要高不少。

2.4 场景八:延迟队列,ZSet的自然时间轴

延迟队列在生活中最常见的场景是电商订单超时未支付自动关单。常规做法是定时任务扫描数据库里的待支付订单,但订单量大了之后,频繁全表扫描非常浪费。用Redis的ZSet结构做延迟队列,可以把扫描成本降得很低。

核心思路是用到期时间作为score,业务数据作为value:

ZADD order:delay 1750000000 "order_id_10001"

后台启动一个守护任务,每隔一段时间执行一次ZRANGEBYSCORE查询,把所有score小于等于当前时间戳的任务取出来,然后逐条ZREM删除并执行业务处理:

ZRANGEBYSCORE order:delay 0 now_timestamp ZREM order:delay "order_id_10001"

这个方案实现简单,但有一个注意点:轮询间隔越大,任务执行的实时性越差;轮询间隔太小,又会有不少空轮询消耗CPU。我一般用两个手段来优化:一是轮询间隔控制在1秒左右,对订单关闭这种场景绰绰有余;二是业务处理优先级高的任务单独放一个更小的ZSet,用更短的间隔轮询,避免被大量普通任务挡住。

如果不想自己造轮子,也可以直接用Redisson提供的RDelayedQueue,底层还是基于ZSet,但封装了定时推送逻辑,Java项目用起来更顺手。延迟队列从原理到落地都不复杂,但架构设计中经常能派上大用场。

3. 数据结构衍生类场景:把每种类型用到位

3.1 场景九:排行榜,ZSet一出手就知有没有

排行榜是我最喜欢跟人聊的Redis案例,因为用ZSet来做,代码量少到让人感动。ZSet内部是跳表实现的,每一个元素都带一个score,天然支持按分数排序。

比如某直播平台的礼物热榜,用户每收到一枚火箭,就往对应主播的score上加对应分数:

ZINCRBY live:gift:rank 1000 "anchor_888"

需要读取Top N的时候:

ZREVRANGE live:gift:rank 0 9 WITHSCORES

ZREVRANGE是按分数从高到低取,ZRANGE是按分数从低到高取。分页读取榜单也特别自然,无非是调整start和stop偏移量,这正好对应热词里的“redis分页”用法:

ZREVRANGE live:gift:rank 10 19 WITHSCORES

真正实现的时候,大多数人会忽略一个细节:排行榜同分怎么排名次。ZSet是按score再按字典序排序,如果希望同分情况下按业务规则排序,可以把score改造成“业务分数+业务计数字段”的组合数值。比如实际分数是积分,可以用浮点数把小数的后几位作为参与排行的业务参数,比如“1000.0000001”表示积分为1000且携带权重,虽然有点tricky,但很实用。

3.2 场景十:计数器与接口限流,INCR的高频拿手好戏

Redis的INCR、INCRBY命令是原子性的,天生适合做各种计数器。比如统计文章阅读量,每次点击执行一次INCR,相比更新数据库字段,Redis的写性能高几个量级。每天定时把这个计数同步到数据库做持久化即可。

计数器之外,INCR还可以用来做接口限流。最简单的固定窗口限流:

INCR api:limit:user_10001 EXPIRE api:limit:user_10001 60

先INCR,如果返回值大于设定的阈值就拒绝请求。但EXPIRE和INCR之间可能存在原子性窗口,稳妥做法是把判断和设置过期时间放在一个Lua脚本里:

local num = redis.call("INCR", KEYS[1]) if num == 1 then redis.call("EXPIRE", KEYS[1], ARGV[1]) end if num > tonumber(ARGV[2]) then return 0 end return 1

固定窗口限流有一个边界问题:窗口切换的一瞬间,可能出现双倍请求。比如前59秒都没人访问,最后一秒涌入100个请求,下一个窗口又涌入100个,实际1秒内的请求可能超过阈值。更平滑的是滑动窗口限流,用ZSet记录每个请求的时间戳,判定时间窗口内的请求数量:

ZREMRANGEBYSCORE api:sliding:user_10001 0 (now - 60s) ZCARD api:sliding:user_10001

如果ZCARD大于阈值,就拒绝新请求。每次请求时先ZADD一个当前时间戳,再执行清理和统计。这个方案判断更准确,但内存消耗会随着请求量线性增长,适合中等并发规模。

3.3 场景十一:集合运算与去重,Set的社交生态

Set结构支持交集、并集、差集运算,这让它成了社交类功能的常客。比如“查看我和某个好友的共同关注”,对两个Set执行SINTER即可:

SINTER user:10001:follow user:10002:follow

拉黑名单、抽奖去重、投票去重这些需求也都适合用Set。抽奖时直接把所有参与者的user_id加进Set,然后用SRANDMEMBER随机取出不重复的中奖人,避免了数据库去重的复杂SQL:

SADD lottery:20241001 user_10001 SRANDMEMBER lottery:20241001 3

Set做UV统计有一个问题,用户量极大时内存占用明显。如果只求UV的近似值,可以用HyperLogLog结构。它把每个元素通过哈希映射到bit数组中,PFADD一条命令,PFCOUNT一下就能拿到亿级数据量下的近似去重统计,误差在0.81%左右,内存消耗极小:

PFADD page:uv:20241001 user_10001 user_10002 PFCOUNT page:uv:20241001

对于首页PV/UV这种对精度要求不高的运营指标,HyperLogLog简直是神器。

3.4 场景十二:位图Bitmap,签到和在线状态的“空间魔术”

Bitmap不是Redis的一种独立数据类型,它是String类型的位操作模式。一个用户ID对应一个bit位,日常可以用SETBIT和BITCOUNT完成海量用户的布尔状态标记。

最典型的应用是签到。假设user_id为10001的用户第1天签到,第3天签到,第5天签到,可以这样记录:

SETBIT sign:20241001 10001 1

但用户签到一般是按“用户维度+月份”来存的,key可以是sign:10001:202410,bit位代表日期。这样判断某个用户本月累计签到天数,一个BITCOUNT就出来了:

BITCOUNT sign:10001:202410

在线状态也是类似思路。用户登录时SETBIT online:20241001 userId 1,心跳时确认在线,每天凌晨或者固定周期把所有bit清零,就能快速统计日活和实时在线数。BITCOUNT命令对亿级bit位也能在毫秒级返回结果,速度非常可观。

稀疏数据场景下Bitmap可能浪费内存。比如总共一亿个用户,实际每天在线的只有几千人,那么一个1.25MB的bit数组大部分都是0。这种情况下可以考虑用分段Bitmap,按用户ID范围拆成多个key,只在用户登录时才去SETBIT对应段。

3.5 场景十三:分页列表与时间线,List裁剪玩出花样

List结构是按插入顺序排列的,非常适合做“最新动态”时间线。某个用户发表了新动态,就LPUSH到它的feed列表,读取时LRANGE取前20条:

LPUSH user:10001:feed "{feed_id:1, content:...}" LRANGE user:10001:feed 0 19

如果只保留最近500条,执行一次LTRIM裁剪即可:

LTRIM user:10001:feed 0 499

这样列表永远只保留最新数据,内存有界。这个特性用来做首页feed流、操作日志、站内信列表都非常合适。

分页的两种姿势在这类需求里也经常对比:一种是基于偏移量的LRANGE分页,适合数据总量稳定且不频繁变动的场景;另一种是基于游标的增量分页,每次取完记录最后一条的ID,下一次从这条ID之后继续取,适合数据持续增长的场景。列表结构的LRANGE天然支持偏移量分页,但如果数据量到了百万级,还是建议用游标方式,避免下一次LRANGE重新扫描大量已读数据。

4. 进阶场景与生产落地:从“能用”到“用得稳”

4.1 场景十四:Geo地理位置,附近的人和门店搜索

Redis的Geo功能其实是ZSet的封装,底层把经纬度通过GeoHash编码成一个一维分数,然后复用ZSet的排序能力。最常用的几个命令:

GEOADD store:geo 116.397128 39.916527 "store_001" GEOSEARCH store:geo FROMLONLAT 116.40 39.90 BYRADIUS 5 km ASC

GEOSEARCH能直接查出一个坐标点周围5公里内的所有门店,并且按距离从近到远排列。这个能力用在“附近的人”“附近的门店”“配送范围判断”上都很顺手。

Geo有个使用细节:GeoHash编码后的经纬度距离是近似值,它适合做圈选和排序,但如果你要做精确到米级的距离判断,建议还是用GEODIST计算两个点之间的实际球面距离,而不要通过Geo排名反推。另外,更新一个地理位置的score本质上就是更新ZSet的score,直接GEOADD同一个member多次即可,删除则用ZREM命令。

4.2 场景十五:唯一ID生成与海量去重的布隆过滤器

很多团队的全局主键ID会用雪花算法,但有些场景下,利用Redis的INCR生成趋势递增ID反而更合适,比如订单号、流水号。Redis单线程模型保证了INCR天然原子,多个实例同时调用不会出现重复。为了减少对Redis的依赖,客户端可以一次批量取一段ID区间,在本地逐步分发:

INCRBY global:id:order 1000

拿到第1000号,本地从1001用到2000,用完再向Redis取下一段。这样即使Redis短暂抖动,本地也有存量ID可用。

布隆过滤器的场景在前面缓存穿透里提过,它也可以独立用来做海量数据的去重。比如爬虫服务判断一个URL是否已经被抓取过、新闻推荐判断一篇文章是否已经推送给用户。几十亿级别的数据用布隆过滤器也就几百MB内存,比维护一个全量Set节省太多。RedisBloom模块提供的BF.ADD和BF.EXISTS使用起来非常简单,也可以在客户端直接集成guava的BloomFilter,序列化后存入Redis。

4.3 生产环境落地要点:持久化、主从哨兵、可视化管理

最后聊点基础设施层面的东西。只要你把Redis用到生产环境,持久化就是一个绕不开的话题。Redis提供了RDB快照和AOF日志两种机制。RDB恢复速度快,但会有丢数据窗口;AOF记录每次写命令,可靠性强,但文件膨胀后恢复慢。我目前的实践是:既要,RDB做定期全量快照,AOF开启everysec同步,把崩溃丢失窗口控制在1秒内。新版本的Redis已经默认开启了AOF混合持久化,兼顾了RDB的恢复速度和AOF的数据完整性,默认配置直接用就挺好。

主从复制和哨兵模式是生产环境高可用最常见的一对组合。至少一主两从加三个哨兵实例,哨兵负责心跳检测和故障转移。需要注意的是,主从复制的数据同步是异步的,主节点刚写入一条数据还没同步到从节点,结果主节点宕机了,这部分数据会丢。所以在设计缓存代码时,一定要考虑“从主节点读、从节点同步不保证实时”的特性,不能把重要业务数据在临界时刻依赖从节点读取。

可视化客户端方面,我个人比较推荐Another Redis Desktop Manager。相比老牌的Redis Desktop Manager,它社区版免费,并发连接管理也稳定。我在排查线上问题时用得最多的是它的命令行窗口,可以快速执行KEYS或者SCAN命令看数据结构,查看某个key的TTL、内存占用以及序列化后的具体值。生产库上千万别随意执行KEYS *,数据量大时会阻塞Redis单线程模型导致服务卡顿,要用SCAN命令代替。

最后再分享一个小技巧

踩过几次坑之后,我现在写缓存代码都会习惯性加一个全局的缓存Key前缀规范。比如user模块是user:cache,订单模块是order:cache,线上环境通过前缀就能反推业务归属,排查问题时,ARDB的树形展示也会清晰很多。再有就是每个写缓存的入口都必须显式声明TTL,哪怕是暂时不确定过期时间的场景,也先给一个比较长的默认值。没有任何过期时间的缓存key一旦积累多了,内存会不知不觉被打满,这是我在线上见过最多的隐形事故,没有之一。

Redis不难学,难的是在一个又一个真实场景里把它的特性用对、用稳。希望这15个场景能帮你把它的能力边界看透,下次碰到类似需求,直接抄起来就用。

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

Fast DDS共享内存零拷贝完全指南:配置、验证与避坑

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

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

Java SE游戏开发:从Swing主循环到AABB碰撞的完整实践

简介&#xff1a;这是一份基于Java实现的经典超级马里奥风格小游戏源码&#xff0c;面向计算机、数学、电子信息等专业的本科生&#xff0c;适用于课程设计、期末大作业及毕业设计参考&#xff0c;帮助学习者通过完整可运行项目掌握Swing图形界面开发、游戏主循环、碰撞检测、音…

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

技术人如何通过写博客从极客成为行业意见领袖:路径、方法与避坑

先把话说在前面&#xff1a;这篇文章不是什么成功学&#xff0c;也不是那种“三个月从零做到十万粉”的速成教程。我见过太多技术人&#xff0c;代码写得漂亮、方案讲得清楚&#xff0c;但一提到写博客、做分享&#xff0c;就觉得那是另一个世界的事。老蒋博客从最开始一个没人…

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

ROS多无人机仿真:命名空间、TF前缀与气流耦合的系统级重构

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

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

Kubernetes离线部署全流程:从镜像搬运到集群稳定运行

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

作者头像 李华