
这段时间正好在整理Redis学习笔记又赶上门店营业状态设置这个需求落地两件事撞到了一块儿。很多做业务开发的同事一提到Redis第一反应就是“缓存”再深入一点能说出String、Hash这些数据类型但真到了营业状态这种看似简单的场景反而会纠结这东西到底该存Redis还是MySQL切换状态要不要上分布式锁状态变更怎么通知到其他服务这些问题我在这次实操里都过了一遍今天就把这份学习笔记和落地过程一起整理出来给同样踩在这些坑上的朋友做个参考。营业状态设置这个业务本质上是高频读、低频写、要求实时可见——用户进门店列表、点商家详情、确认能否下单每一次都要查状态而商家改营业状态一天最多几十次。这种数据放MySQL当然也能做但扛不住秒级响应的实时性要求更扛不住大促时的读流量。Redis恰好就是为这种场景设计的状态写入内存查询走缓存再配上过期时间、发布订阅、分布式锁整套方案做下来比想象中要顺但也确实有不少细节容易翻车。这篇文章不会只停留在“怎么用”我会把Redis的核心概念、营业状态的数据模型设计、完整实现步骤、以及安装配置和常见坑都拆开讲适合刚学Redis的开发者也适合正在做类似业务改造的朋友直接抄作业。1. 从“营业状态”这个需求聊起1.1 为什么我会把这个需求和Redis绑在一起先还原一下业务背景。我们这边是连锁门店的商家后台每个门店都有开工、打烊、休息中、暂停营业这么几种状态。商家在后台点一下“开工”用户端的门店列表立刻要能看到“营业中”的标识点一下“打烊”所有入口的下单按钮要马上置灰。一开始用的是MySQL状态字段商家切换后前端轮询接口接口每次都要查库高峰期门店列表接口一秒被刷几千次每个请求都要带一次状态字段的SQL查询虽然加了索引压力依然不小。后来复盘这个场景有几个特点非常鲜明。第一状态数据总量很小无非是几千个门店每个门店一个状态字符串第二读流量远超写流量用户在逛商家在操作读和写的比例可能是几百比一第三状态变更要求秒级甚至毫秒级可见不能接受数据库主从同步的延迟。这几个特点合在一起几乎就是Redis的标准使用场景。我当时在笔记里给自己画了一条线MySQL放账单、订单、门店档案这种强一致数据Redis放营业状态、公告、活动开关这种“短小高频”的实时数据。营业状态这种需求如果继续堆在MySQL上等于拿卡车运一袋米笨重且浪费。1.2 营业状态管理的核心痛点真动手做的时候发现这个看起来“只是改一个字段”的需求背后其实有五个绕不开的问题。第一个是实时性。商家点完开关前端要尽快看到结果这里就涉及到Redis缓存与后端接口之间的配合不能做太重的缓存否则状态切了前端不感知。第二个是一致性。状态存在Redis里但门店的营业时间表、临时休业计划可能还留在MySQL里两边必须有一套同步机制不然就会出现“Redis显示营业中数据库说已打烊”的幺蛾子。第三个是并发。商家可能在小程序端和后台同时操作两个请求同时改状态后写的把先写的覆盖了或者状态被改成脏数据这就是典型的并发写问题。第四个是通知。状态变了以后需要通知到商品服务、订单服务、搜索服务各服务都得知道这个门店现在可不可下单最简单的方式就是Redis的发布订阅。第五个是自动恢复。有些门店是“临时休业两小时”到点自动恢复营业这就要用到Redis的过期时间或者定时任务回写。这几个痛点恰好把Redis的数据类型、过期策略、分布式锁、发布订阅这几块基础知识串起来了。所以我这篇笔记的顺序也是这么走的先补最核心的概念再回到业务场景里把每个痛点逐个击破。2. Redis补课学明白这几个核心概念再动手2.1 数据类型String、Hash、Set、ZSet、Pub/Sub一张表看懂我的学习笔记第一页就是数据类型但记了不等于会用。实际写代码的时候不同业务场景选错类型的例子我见过太多了这里用一个熟悉的生活场景来类比理解起来会快很多。String字符串Redis里最基础的类型可以存字符串、数字、二进制数据。比如门店当前状态可以存成open、closed就像电灯开关就两个值。它支持SET、GET还支持INCR、DECR这类原子自增减操作。Hash哈希相当于一个对象一个key下面有多个field-value。比如门店信息可以存成shop:info:{shopId}里面放name、status、business_hours等字段比塞进一个庞大的JSON字符串更省内存也更好修改单个字段。List列表像排队叫号左侧进右侧出适合做消息队列订阅、最新公告列表。Set集合无序、不重复。可以存“当前所有营业中的门店ID”天然去重还能做并集、交集、差集运算。查“哪些门店既营业又是连锁品牌”这种问题一条命令就出结果。ZSet有序集合在Set基础上带一个分数可以按分数排序。比如管理“距离自动打烊还剩多少秒”的门店每次按剩余时间排序到期的拉出来处理非常顺手。Pub/Sub发布订阅就是广播。一个服务发布消息其他订阅过的服务都收到通知我这次做状态变更通知用的就是它。做营业状态设置的时候最常用到的是String、Set和Pub/Sub。String存单个门店状态Set做批量查询“哪些店还在营业”Pub/Sub做状态变更广播。这三个组合起来业务上九成需求都能覆盖。2.2 过期时间与缓存治理为什么Redis里的数据会“消失”很多人写Redis代码只会SET不会EXPIRE这在大厂面试里基本要被扣分更重要的是在业务上会埋雷。我自己的笔记里专门记了一条凡是缓存必须有失效策略否则就是“无限膨胀的内存垃圾”。Redis里的过期时间有几种玩法。第一种是主动过期。对key执行EXPIRE key seconds时间到了之后这个key会被删除。比如我要做一个“临时休业两小时自动恢复”的功能就可以在商家设置临时休业时给状态key设置一个7200秒的过期时间到期后key消失系统再根据兜底逻辑恢复营业状态。第二种是惰性过期。key过期了但不立即删等下次访问这个key的时候再发现过期、再删掉。这种机制的好处是省CPU坏处是过期key如果一直没人访问会占着内存不释放。第三种是定期清理。Redis后台会定时的扫描一部分过期的key把它们清掉算是主动清理和惰性清理的一种平衡策略。实际业务里缓存治理的核心问题不是“过期时间怎么设”而是缓存和数据库的数据怎么保持一致。这个后面我会专门开一节讲这里先记住一句话不要试图让Redis替代数据库它只是数据库上面的一层加速带加速带会磨损必须定期校准。2.3 分布式锁多台机器抢一个“营业开关”时怎么办如果是单机应用改状态直接用Redisson或者Synchronized就能锁住但现在的服务基本都是多实例部署商家请求打到A机器另一个请求打到B机器两个实例同时改同一家店的营业状态MySQL有事务兜底Redis可没有。这时候就需要分布式锁。Redis实现分布式锁的经典姿势是SET lock_key unique_value NX EX 30。NX表示只有key不存在时才能设置成功EX 30代表锁在30秒后自动过期防止持锁的机器宕机导致死锁。解锁的时候要用Lua脚本比对唯一标识避免误删别人持有的锁。营业状态切换这种操作并发量其实不高但频率再低也有“客户端重复点击”这种事故场景。商家连点两次“打烊”两个请求同时进来没有锁的话状态可能是先设置成打烊又设置成营业用户体验极差。加了锁以后第一个请求拿到锁完成切换第二个请求要么等待要么直接返回“正在切换中”业务就稳了。2.4 序列化与可视化排坑第一步刚接触Redis的同事最容易栽在“代码里存进去的值用客户端一看是乱码”这个问题上。原因很简单——Java的RedisTemplate默认用的序列化器是JDK序列化存进去的对象是带一堆\xAC\xED前缀的二进制RedisDesktopManager这种可视化工具打开当然没法看。所以我的笔记里专门留了一页写序列化方案。通用的做法是统一使用JSON序列化器比如GenericJackson2JsonRedisSerializervalue存成可读的JSON字符串方便排查问题。但是要注意如果你用StringRedisTemplate那所有value都是字符串RedisCli视角看起来最直观。我的建议是业务调试期尽量用StringRedisTemplate直接存字符串上线后再评估是否引入对象序列化。可观测性永远比花哨的序列化方式更重要。可视化工具方面我踩过不少坑。RedisDesktopManager老版本还好用后来分叉出收费版和免费版新用户一不留神就下载成付费版本。社区里现在口碑比较好的替代品是Another Redis Desktop Manager简称ARDM开源的跨平台界面和交互都比较符合习惯官方还有一个RedisInsight功能全、支持集群管理就是启动稍重。Windows用户注意用Docker或者WSL跑Redis的情况下客户端连接时要填对端口和密码两个工具的操作差别不大。3. 营业状态设置的设计与实现3.1 数据模型设计到底该用哪种Redis结构这是整个实战里最核心的决策点。我当时拿着需求和Redis的数据结构清单在草稿纸上对比了很久最后决定用三层设计。第一层也是最基础的用String存每个门店的当前状态。KEY的规范是shop:status:{shopId}VALUE是约定的状态枚举open营业中、closed已打烊、rest休息中、busy繁忙。为什么不用Hash因为状态是一个高频读写的单字段Hash虽然能顺带存其他信息但会让每次读多解析几个字段性能反而下降而且String天然支持过期时间Hash对单个字段做过期需要额外结构没必要。第二层用Set存“当前营业中门店集合”KEY是shop:open:ids里面是所有状态为open的门店ID。这个集合解决的是“批量查询”问题。门店列表接口几十个门店如果挨个查String状态要几十次网络请求用Set先拿到营业中ID集合再配合一次哈希批量取值就能把几十次请求压缩到一两次。第三层用Pub/Sub做状态变更通知这是后话3.5节细讲。设计好之后我还在旁边批了一句话结构不是越复杂越好能满足需求的最小结构才是好结构。有些架构师恨不得一个状态用上五种结构维护起来全是坑。3.2 状态切换流程开门、打烊、自动打烊营业状态切换一共有三种典型操作对应的处理逻辑完全不同。第一种是手动开门。商家点击“开始营业”后端要做四件事把shop:status:{shopId}设置为open把shopId追加进shop:open:ids这个Set里给状态key设置合理的过期时间作为兜底比如超过营业时间自动失效通过Pub/Sub发送一条“门店开业”的消息。这里有个细节手动开门之前要检查门店是否在临时休业状态如果有冲突要给出提示不能直接覆盖。第二种是手动打烊。操作相对简单设置状态为closed把门店ID从shop:open:ids里移除发布一条“门店打烊”消息。需要注意的是有的业务要求打烊后保留收尾时间比如“停止接单但允许顾客取餐到十点”这种情况不能直接删状态建议再加一个shop:closing_time:{shopId}的字段记录收尾截止时间用户端根据这个时间显示不同的提示文案。第三种是自动打烊/自动恢复。这个完全依赖Redis的过期时间。商家选了“临时休业2小时”那就设置状态key的TTL为7200秒。到期后key自动过期此时需要一个兜底任务来恢复状态。最简单的方式是用Spring的定时任务每分钟扫描一次即将过期的门店把数据恢复到默认状态更高效的方式是用ZSet存“临时休业截止时间戳”定时任务只取到期的那一批处理避免全量扫描。我两个方案都搭过数据量小的时候每分钟全量扫描其实也没事数据量大了建议上ZSet。3.3 状态查询与批量查询的演进查询接口是流量最大的地方我把演进过程写出来供参考。第一版是最朴素的单查。前端传一个门店ID后端GET shop:status:{shopId}返回状态。缺点非常明显门店列表页一次返回几十家店前端要循环调几十次后端接口慢且浪费连接。第二版做了批量查询。使用MGET命令一次把几十个状态key全部取回来Java里用redisTemplate.opsForValue().multiGet(keys)就能实现往返次数从几十次降到一次。这个版本在门店数量不超过几百家的时候完全够用但如果是全城搜索“所有营业中的店”MGET也没辙你根本不知道key有哪些。第三版就上了Set集合。商家开店时把ID放进shop:open:ids打烊时移出去。这样“查询所有营业中门店”就变成两步SMEMBERS shop:open:ids拿到ID集合再MGET批量取状态做二次校验。注意这里为什么要二次校验因为Set集合里的ID和String状态可能存在极短时间的不一致比如先改状态后删Set用MGET兜底校验返回前把不一致的数据修正能最大程度保证接口数据的正确性。这里不用图实战中把这段逻辑画成时序图挂在自己的文档里下一节我用文字把时序展开。3.4 状态切换接口用分布式锁兜住并发写切换接口之前我先问了自己一个问题商家连点两次开关最坏会发生什么没有锁的话两个请求同时读到“打烊”同时改成“营业”最后一个请求的写入会把前一个覆盖掉。表面上看状态没坏但如果商家是想“打烊”结果因为双击变成了“营业”呢这种Bug一旦出现用户投诉能打到爆。所以我在状态切换接口里加了分布式锁加锁的KEY就用门店维度lock:shop:status:{shopId}。流程是尝试加锁用SET lock:shop:status:{shopId} {requestId} NX EX 10requestId用UUID生成保证解锁时是自己加的锁。加锁失败说明有另一个请求正在切换直接返回“操作过于频繁请稍后再试”。加锁成功执行状态更新、Set维护、发布消息这一整套动作全部完成后执行Lua脚本释放锁。这里有个经典问题为什么不用SETNX长期持有锁而是要加过期时间因为如果持锁的机器在执行中途宕机了锁会一直被占着其他请求永远拿不到锁。加上过期时间就是“就算你挂了我最多等10秒就能恢复”这个思想在面试里叫“兜底机制”在业务里叫“防死锁”。至于删除锁为什么要用Lua脚本是因为“判断value是否等于自己的requestId”和“删除key”这两个动作必须原子如果分开执行刚好在判断完之后锁过期了另一个请求抢占了锁这时候删除就会误删别人的锁。我笔记里这句话是大写的千万不要用先GET再DEL的方式解分布式锁。3.5 状态变更通知Pub/Sub与消息队列的取舍状态改完之后最怕的是服务之间各改各的谁也不通知谁。我第一次做的时候踩过这个坑商家的状态在商家服务里改了但商品服务不知道导致用户看到门店“营业中”点进菜单却提示“本店已打烊”。这里我用Redis的Pub/Sub做了一版轻量级通知。商家服务在状态变化后向频道channel:shop:status发布一条JSON消息里面带着shopId、fromStatus、toStatus、updateTime。商品服务、订单服务、搜索服务各自订阅这个频道收到消息后更新自己本地缓存里的状态。整体响应速度毫秒级非常直观。不过我也得说实话Pub/Sub有个硬伤——消息是即发即弃的订阅的服务挂了就收不到消息。这时候就必须上消息队列了。营业状态变更这种场景如果你们公司有RabbitMQ或者RocketMQ我更推荐直接用专业MQ来做通知因为MQ有持久化和重试机制可靠性高一个量级。Pub/Sub更适合“通知丢了也无所谓反正下次查询会校正”的轻量场景。我当时是因为项目里MQ链路还没打通先用Pub/Sub顶着后来接上MQ以后又花时间改了重发逻辑这里建议后来者直接一步到位。3.6 与MySQL数据的一致性策略状态存Redis营业时间表留MySQL那两边怎么对账这是整个设计里最容易被人质疑的点但也是最容易答好的点。我的策略是MySQL为主Redis为加速。商家设置营业状态时先更新MySQL的shop_status_record表记录状态和时间戳然后再更新Redis。如果Redis更新失败MySQL里状态已经变了系统靠定时任务把MySQL的状态反向同步回Redis保证最终一致。这里不追求强一致因为营业状态丢几秒在业务上可接受。具体落地有两条路。一条是定时全量对账每分钟把MySQL里的所有门店状态扫描一遍和Redis比对不一致的以MySQL为准回写Redis另一条是监听MySQL的binlog状态变更时binlog会触发回调由回调去更新Redis比定时任务更实时但需要引入canal之类的组件复杂度高。我最终选了定时任务方案简单、可控、容易排查数据量几千家门店完全做得过来。4. 环境准备与工具选型从安装到可视化4.1 Linux、macOS、Windows平台的Redis安装Redis的安装平台不同差别很大这里把三个主流平台的方案和我实际用到的命令整理一下。LinuxCentOS/Rocky系# 方式一直接yum安装EPEL源里有时版本不够新 sudo yum install redis # 方式二编译安装可控性高 wget https://download.redis.io/releases/redis-7.2.4.tar.gz tar xzf redis-7.2.4.tar.gz cd redis-7.2.4 make sudo make install我个人更推荐编译安装虽然多花两分钟但能自己选版本还能加上--enable-threads这些编译参数。macOS# 最省事的是brew brew install redis brew services start redismacOS上原来还有一类人会去搞redis-server源码编译其实没必要brew装完以后配置文件在/opt/homebrew/etc/redis.confM芯片路径需要改密码直接改这个文件就行。Windows这里要重点说Redis官方其实没有正式的Windows原生版本网上流传的所谓“Windows版Redis”很多是第三方的旧版本移植。我对Windows用户有两个建议首选用WSL2Windows 10/11的子系统里装Linux后跑原生Redis性能好、社区方案成熟次选用Docker Desktop拉Redis镜像跑容器前提是Docker Desktop能正常启动。如果非要原生exe可以考虑Memurai这个Windows原生Redis兼容实现但企业商用要注意授权。4.2 Docker部署与主从搭建Docker是最干净的安装方式配置文件好管理环境隔离也无敌。拉镜像跑起来的命令非常简单docker run -d --name my-redis \ -p 6379:6379 \ -v /my/conf/redis.conf:/etc/redis/redis.conf \ redis:7.2 redis-server /etc/redis/redis.conf主从复制主从的搭建思路也不复杂。准备两份配置主库正常启动从库配置里加一行replicaof 192.168.1.10 6379注意旧版本配置里这个参数叫slaveof。主从模式最大的价值是读写分离和容灾备份主库写、从库读主库挂了至少还有从库顶上。但在营业状态这个场景里我建议还是读写都走主库因为状态数据太小了从库读反而要多一次网络跳转延迟更高。主从更多是放在应用系统的订单缓存这些大读场景下使用。4.3 可视化客户端怎么选可视化工具我最终留了两个Another Redis Desktop ManagerARDM免费开源跨平台日常开发调试非常顺手支持批量查看key、命令行、慢日志还支持暗黑主题RedisInsight官方功能更完整有集群拓扑图、内存分析、性能分析团队排查线上问题更好用。选工具这事我多说一句不要装一堆客户端最后发现只用来看一眼key。我见过不少同事电脑里装了五六个Redis客户端其实一个ARDM完全够用。可视化工具只是一个入口真正排查问题还是要靠Redis的命令行比如redis-cli --bigkeys分析大keyredis-cli monitor实时看命令这些是客户端界面替代不了的。4.4 Windows下设置Redis密码的两种方式热词里很多人搜“Windows设置Redis密码”这里单独拿出来讲其实是两种改法。方式一修改配置文件。在redis.conf里找到requirepass这一行去掉注释改成requirepass yourstrongpassword然后重启Redis进程。Docker部署的话命令是docker run -d --name my-redis -p 6379:6379 redis:7.2 redis-server --requirepass yourstrongpassword方式二命令行动态设置redis-cli CONFIG SET requirepass yourstrongpassword注意这种方式是临时生效的Redis重启以后密码就丢了。生产环境一定要用配置文件方式。设置完密码后客户端连接要指定密码redis-cli -h 127.0.0.1 -p 6379 -a yourstrongpassword这里有个安全隐患-a参数会把密码暴露在进程列表里生产环境建议用REDISCLI_AUTH环境变量或者在连接后再执行AUTH命令。我笔记里专门标注了不要在命令行参数里裸奔密码尤其是多人在同一台服务器上共用的场景。5. 常见问题与排查实录5.1 Docker命令返回500错误的路由问题很多人装完Docker Desktop以后执行docker search redis或者docker pull redis会碰到类似“request returned 500 Internal Server Error for API route and version ... check if the server supports the requested API version”这种报错。我帮同事排查过好几次基本就三类原因。第一类Docker Desktop的引擎和客户端版本不匹配。这类报错里经常出现/pipe/dockerDesktopLinuxEngine这种命名管道地址说明客户端试图访问引擎的管道但引擎还没就绪或者版本对不上。解决方法是先重启Docker Desktop打开面板确认左下角的Engine状态是Running再重新执行命令。第二类Docker引擎假死。这时候docker version通常能返回但真正执行docker search就报错。可以打开Docker Desktop的Troubleshoot点Restart彻底重启引擎。Windows上偶尔还需要在“服务”里重启com.docker.service这个系统服务。第三类老版本Docker Desktop和Windows系统更新之间的兼容问题。这种只能升级Docker Desktop到最新版或者回退到之前稳定版本。我的经验是不要盲目追新选一个已经验证过两个月以上的版本最稳。5.2 数据“缓存穿透”门店ID查不到怎么办正常情况下状态key都会存在但总有意外门店ID传错、Redis执行了FLUSHALL、缓存过期后还没回源。这时候如果接口直接返回“该门店不存在”对客户端来说就是一个巨大的逻辑漏洞。我的规避手段有两个。一个是对不存在的key也设置一个空值的短TTL比如PLACEHOLDER占位符过期时间30秒防止恶意遍历接口时每次都穿透到MySQL第二个是在代码里判断如果状态key不存在立即查MySQL兜底查到就回填Redis再返回查不到就返回业务错误。营业状态这种读多写少的场景缓存穿透是头号威胁必须做兜底。5.3 Redis INCR不准聊聊并发与原子性热词里有个“redis incr不准”这个我必须说一说因为它把很多人绕懵了。INCR命令本身是原子的单线程执行不存在“计数丢失”的问题。那为什么不准确最常见的原因是业务层的读改写。比如你想实现“同时营业的门店超过100家就拒绝新门店开业”刚学Redis的人可能会这么写int count redisTemplate.opsForValue().get(open_count); if (count 100) { redisTemplate.opsForValue().increment(open_count); }这个代码在高并发下必出问题——两个请求同时读到count99两个都判断小于100两个都执行increment结果计数变成了101但只有100家店的业务限制被击穿了。这不是INCR不准是读改写逻辑不是原子的。正确做法是用Lua脚本把判断和自增放到Redis服务端原子执行local count redis.call(get, KEYS[1]) if tonumber(count) tonumber(ARGV[1]) then return redis.call(incr, KEYS[1]) else return -1 end脚本一到Redis那边就是单线程一口气跑完不存在并发穿插。我这次营业状态里用到INCR的场景是做“同时开业门店数实时大盘”用了Lua脚本以后数据才真正稳定下来。5.4 从营业状态场景看Redis面试高频题整理学习笔记的时候我发现很多Redis面试题都能用营业状态的场景来解释背题不如实战理解深刻。“Redis为什么快”因为它单线程执行命令避免了线程切换和锁竞争核心操作全在内存搭配IO多路复用epoll高效处理网络连接。营业状态下几千次查询还是毫秒级响应靠的就是这套设计。“Redis持久化怎么选”RDB是快照适合恢复和备份但可能丢最后一次快照后的数据AOF是操作日志更实时但文件大、重启恢复慢。营业状态这个场景Redis里状态丢了可以从MySQL恢复所以我会开AOF做安全兜底但不会把AOF刷盘频率调到最极端避免性能损耗。“缓存雪崩、击穿、穿透怎么解决”雪崩是大批量key同时过期解决办法是过期时间加随机抖动击穿是热点key刚好过期几百个请求同时打到数据库解决办法是互斥锁重建或者逻辑过期穿透就是我上面说的查不存在的key解决办法是空值缓存加布隆过滤器。营业状态里最容易发生的是单个超级头部门店的击穿我在代码里给状态查询加了互斥锁宁可让请求等一下也不能压垮数据库。“缓存和数据库一致性怎么保证”这个问题我在3.6节已经写了实战方案核心思路是MySQL为主、Redis为加速最终一致而非强一致面试官就会觉得你不是背概念的。5.5 缓存治理过期时间该设多长很多人觉得Redis过期的设置是“拍脑袋”其实是可以计算推导的。营业状态这个场景我设的过期时间是24小时参数配在配置中心随时能调。为什么选24小时因为状态必须随时反映实时营业情况超过一天没更新的状态本身就是脏数据应该自动过期兜底。过期时间的设置有一个通用公式可以参考过期时间要略大于业务允许的最长数据延迟。如果业务要求用户看到的状态最多慢10秒那缓存就不能设5分钟否则状态变了前端要等5分钟才能看到如果业务能接受1分钟的延迟那缓存放90秒完全没问题还能扛住比MySQL多几十倍的流量。总之一定要先定义业务容忍度再定参数千万别反过来。我的缓存治理清单里还专门加了一条给所有缓存key命名加版本号。比如营业状态的是shop:status:v1:{shopId}后续如果状态枚举增加一个“休息中”的取值需要强制刷新缓存只需要把key前缀改成v2旧缓存自动作废不用一个个去删。这个思路在发布上线的时候帮了我大忙。5.6 一个让我从入门到熟练的命令行技巧最后分享一个命令行技巧不算问题排查但很实用。排查Redis的慢查询用SLOWLOG GET 10看最近的10条慢命令和执行时间查大key分布用redis-cli --bigkeys它会扫描全部key按类型统计最大的几个我的门店状态缓存曾经因为误存了整个门店JSON对象瞬间变成几百KB的大key全靠这个命令找出来的。如果怀疑某个key的过期时间设置不对用redis-cli TTL shop:status:001返回值如果是-1代表永久有效-2代表key不存在正数代表剩余秒数。这个命令让我在排查“门店一直显示营业中”问题的时候一眼看出是程序里没设过期时间开发小哥还一脸委屈地说“我没写EXPIRE啊”——对啊问题就是出在“没写”上。写在最后这套方案还能怎么扩展我自己的体会是营业状态设置看起来是个小需求但把Redis的数据类型、过期策略、分布式锁、发布订阅、缓存治理全串了一遍学到的比单独刷十篇面试题都扎实。现在这套方案还能往前再走几步比如把门店营业状态接入公司的监控大盘实时统计“当前营业率”比如把状态变更事件接入工单系统门店异常打烊自动报警再比如把临时休业时长累计下来用来分析门店运营规律。如果你也在做类似的需求我的建议就一条先想清楚数据结构再写代码最后调参数。结构对了后面所有事情都顺结构错了后面写多少代码都是在还债。这次实践里我踩过的最大的坑就是一开始图省事全用String硬扛结果批量查询和自动恢复都实现得很别扭后来推翻重来才理顺。希望这份学习笔记能帮你少走一段弯路。