
作为一个Java工程师黑马商城这套项目教程在圈子里名气一直不小尤其是Redis篇。我以前带团队做电商类项目时还专门把这里面的缓存方案摘出来改造成生产级配置。这篇内容不是我对着官方文档念经而是把我自己从零搭建、开发、踩坑到最后部署上线的完整过程捋一遍重点放在Redis在分布式架构里到底是怎么发挥作用的以及那些光看视频学不到的细节。无论你是准备跳槽面试还是手头正好要搞一个高并发的商城系统这篇都值得花几分钟看完。很多人刚开始接触这个项目时会犯一个错误把Redis只当成一个缓存数据库来用存点商品信息就完事了。实际上黑马商城Redis篇的价值在于它把同一个中间件在不同业务场景下的用法全部串起来了缓存只是最基础的一层。真正关键的是分布式锁、Session共享、消息队列、数据统计这套组合拳。我接下来会按照从架构设计到落地部署的顺序把这些核心环节一个个拆开讲清楚。1. 项目定位与整体架构拆解1.1 黑马商城为什么要引入Redis电商类项目有一个典型的共性特点读多写少。用户浏览商品、查看详情、翻购物车这些操作占了请求量的绝大部分而真正的下单支付只是很少一部分。如果所有读取都打到MySQL上数据库很快就会被拖垮。黑马商城引入Redis核心目的就是把热点数据的读取压力从数据库上卸下来。但是这里有个容易忽略的点Redis做缓存并不是简单地把数据塞进去就万事大吉。你还要考虑缓存什么时候更新、数据不一致怎么办、万一缓存雪崩了怎么兜底。黑马商城这个项目妙就妙在它把这些场景都揉进了具体业务里。比如商品信息查询、店铺信息查询、分类列表这类接口都是典型的缓存应用场景而秒杀扣库存、订单幂等性处理就必须靠Redis的原子性操作和分布式锁来支撑。我在实际工作中验证过这套思路的可行性。一个日活几十万的商城系统把我这边Redis缓存层做好之后MySQL的QPS直接从高峰期的两万多降到了三千左右响应时间也从平均300毫秒降到了50毫秒以内。这就是为什么很多企业面试时特别看重候选人能不能说清楚缓存、锁、持久化这些细节因为这些都是线上项目真正会用的东西。1.2 分布式架构里Redis扮演哪几个角色先说一个很多人理解不到位的地方Redis在分布式架构里从来不是“只干一件事”的中间件。在标准的生产环境中Redis至少同时承担四个角色。第一个角色是缓存层这个大家都很熟。商品详情、用户信息、热点榜单这些东西用Redis存一份请求进来先查Redis查不到再去查MySQL然后把结果回填到Redis。第二个角色是分布式锁的载体。高并发场景下多个服务节点同时操作同一份数据比如秒杀扣减库存如果不加锁就会出现超卖问题。基于Redisson或者原生SETNX命令实现的锁就是靠Redis的原子性来保证多节点之间的互斥。第三个角色是Session存储。传统单体应用把Session放在应用内存里但分布式架构下用户请求可能被负载均衡到不同的节点上如果Session不共享就会出现“登录状态漂移”的问题。把Session扔进Redis所有节点读同一份数据问题就迎刃而解。第四个角色是异步队列。虽然专业的消息队列通常是RabbitMQ或者Kafka但Redis的List结构配合阻塞命令完全可以胜任一些轻量级异步任务。黑马商城教程里用Redis实现了订单超时自动取消、异步通知等功能这种设计在中小型项目里既省去了额外部署消息队列的成本又能达到解耦的效果。1.3 项目目录结构与领域划分黑马商城这个项目的包结构很值得学习它严格按照领域划分模块而不是按技术层来分。你去观察controller、service、mapper这些目录会发现它把用户、商品、订单、支付等业务领域拆得很清晰每个领域内部再按照web、service、dao的层次往下组织。这种划分方式在生产环境里有一个明显好处就是多人协作时不会互相冲突。每个人负责一个独立的领域包提交代码时的合并冲突概率大幅降低。另外它也为后续的微服务化改造留了后路哪天你发现订单模块的访问量太大需要拆成独立服务直接把整个订单包拎出来就行因为它内部的依赖是自洽的。在数据库层面项目主要用了MySQL来存储核心业务数据Redis来做缓存和辅助业务逻辑。要注意的是项目里的表结构设计充分考虑了索引和查询效率比如商品表和商品详情表分开设计用户表和用户地址表分开设计这些细节在面试官问起数据库优化时都是加分项。2. 环境准备与工具选型2.1 本机环境JDK、Maven与依赖管理在动手之前先把环境统一好。这个项目基于Spring Boot所以JDK版本建议直接用8或者11不要用太新的版本避免出现兼容性问题。Maven建议使用3.6以上版本你可以在settings.xml里配置阿里云镜像仓库不然依赖下载的速度会让你怀疑人生。依赖管理上项目主要引入了Spring Boot Web、MyBatis Plus、Redis、Redisson这几大块。我建议你打开pom.xml看一下把这些依赖的版本号理清楚。特别是在Spring Boot 2.3.x和2.7.x之间Redis连接方式和Redisson的兼容包名都有变化如果版本对不上很容易出现启动报错。一个非常实用的经验是项目里尽量不要混用不同版本的Redis客户端依赖。之前有粉丝私信我说项目里同时引入了spring-boot-starter-data-redis和redisson-spring-boot-starter结果因为两者内部的jedis和lettuce版本冲突导致Redis连接时好时坏折腾了一天才发现是依赖冲突。统一版本如果不知道怎么处理直接先把redisson注释掉调试完缓存部分再打开这个排查思路会让你少走很多弯路。2.2 Redis安装的两种方式对比Redis本身是Linux上的产物Windows上的安装包其实是别人维护的移植版版本更新速度会慢一些。如果你用的是Windows本机开发我建议用两种方式里更省心的那种直接用Docker跑一个Redis容器。Docker方式只需要一条命令docker run -d --name redis -p 6379:6379 redis:6.2就这么简单。端口映射做完本机的Java项目直接连接localhost:6379就能用。忘掉Windows安装包里那些复杂的配置和烦人的服务注册容器方式五分钟就能搞定。如果你的环境不支持Docker那就老老实实下载Windows版Redis解压后执行redis-server.exe启动。要注意的是Windows版默认没有配置密码监听的又是所有网卡如果你在公司或者学校网络上跑很容易被别人扫到端口有安全风险。建议启动时加上需要认证的配置或者直接把bind设置成127.0.0.1只允许本机访问。Mac用户相对幸运brew install redis直接装装完redis-server启动就行配置文件在/usr/local/etc/redis.conf需要开启持久化时改这里的appendonly参数。2.3 可视化客户端与命令行工具开发调试Redis时纯命令行也能用但效率确实不高。我日常开发用的最多的是Redis Desktop Manager不过这个工具新版开始收费了。开源社区里Another Redis Desktop Manager是它的替代品界面差不多支持Windows、Mac、Linux连接Redis时能看到所有key还能直接查看各种数据类型的内容日常排查数据格式问题很够用。有一点要注意可视化工具只是用来观察数据状态的真正处理复杂问题还是离不开命令行。尤其是排查慢查询、查看内存碎片、观察键过期情况这些操作我建议你还是打开终端敲redis-cli。命令行的info、memory doctor、slowlog get这类命令能给你提供可视化工具给不了的一手数据。我见过太多人在可视化工具里翻半天找不到原因结果一条slowlog命令立刻定位问题的案例了。3. 核心功能模块的开发实战3.1 商品缓存与缓存更新策略商品信息是商城系统的核心热点数据。实战里我会在查询逻辑上做一层封装先从Redis里查命中就直接返回没命中就去MySQL里查查到了就把数据写入Redis并设置过期时间再返回给前端。这里最值得说的是缓存更新策略。黑马商城教程里推荐的是Cache Aside模式简单说就是读的时候先读缓存读不到就查数据库然后重建缓存写的时候先更新数据库再删除缓存。为什么要删除而不是更新缓存因为更新缓存的操作往往需要做很多字段的拼接和序列化代价高且容易出错而删除缓存只是让下一次读取时自然重建逻辑简单可靠。实际操作时有个细节坑就是“先删缓存后更新数据库”和“先更新数据库后删缓存”这两种顺序的差异。后者是主流做法但依然存在一个时间窗口线程A更新完数据库但还没来得及删缓存线程B读到旧缓存就返回了。要彻底解决这个问题思路是引入消息队列或者在更新完数据库后延迟几百毫秒再删除一次缓存俗称“延迟双删”。虽然不能做到绝对最终一致但在绝大多数业务场景下已经足够了。3.2 缓存穿透、击穿、雪崩的防护这三个问题是面试必问的也是线上出事故最多的地方。缓存穿透指的是查询一个根本不存在的keyRedis里没命中请求直接打到数据库恶意攻击时可以用不存在的id把数据库打垮。解决方案是缓存空值或者用布隆过滤器先拦截。缓存击穿是指某个热点key在过期的瞬间突然涌进大量请求全部穿透到数据库。解决方案是互斥锁也就是让同一时刻只有一个线程去数据库加载数据其他线程等着这个线程写完缓存后再读。实现方式可以用Redis的SETNX命令也可以用Redisson提供的锁。缓存雪崩则是大量key在同一个时间点集体过期或者Redis服务本身挂了导致请求全部打到数据库。前者可以通过设置随机过期时间解决比如基础过期时间300秒加上一个0到60秒的随机值后者必须做高可用部署也就是后面会说到的Redis主从加哨兵架构。黑马商城的实战案例里都覆盖了这些场景但很多人在学习时只是敲代码敲过去了没有仔细想为什么这么写。比如互斥锁那一段代码你仿照写出来容易但如果我让你解释锁的时间设置成多少合适、线程等待期间要不要限流、拿到锁之后查询数据库又失败了要不要释放锁很多人就答不上来了。这些问题才是工作中真正会碰到的。3.3 基于Redis实现分布式锁分布式锁是分布式系统里面老生常谈的话题。黑马商城项目中用到的场景主要是防止订单超卖和防止重复下单。如果只是把concurrent的Lock用在单机代码里一到多实例部署就失效了因为每个JVM内部的锁互相不可见。解决思路就是让多个进程抢同一个Redis key谁抢到谁执行。最简单的实现是用SETNX key value如果返回1代表抢锁成功返回0说明锁被其他人持有。但这里的坑非常多。比如拿到锁后进程崩溃了如果没有设置过期时间锁永远不会释放其他线程就被卡死如果设置了过期时间但业务执行时间超过了过期时间锁提前释放其他线程又进来了导致并发安全问题。所以生产环境里我不建议手写SETNX逻辑直接用Redisson就好。Redisson提供的RLock底层会自动续期默认每10秒检查一次只要当前线程还在执行就会把锁的过期时间延长到30秒这个机制俗称“看门狗”。它同时提供了可重入、获取锁超时控制等功能比手写可靠得多。我做过一次压测用Redisson锁住秒杀接口客户端并发量跑到5000的时候订单创建成功率和库存扣减一致性都保持得很好响应时间也比较稳定不会出现因为竞争锁导致的大量超时请求。如果你还停留在手写SETNX阶段这篇文章看完后建议去把Redisson接入你的项目试试你会爱上它的。3.4 Session共享与登录状态管理单体应用时代用户登录后会话信息放在应用服务器的内存里没人在乎换了一台服务器之后Session还在不在。但到了分布式架构前端请求通过Nginx负载均衡可能第一次请求落在服务器A第二次落在服务器B如果两台服务器的Session不互通用户就会莫名其妙被弹出登录状态。黑马商城项目通过Spring Session结合Redis来解决这个问题。引入spring-session-data-redis依赖后原本的HttpSession会被一个Redis支持的实现所替代Session数据自动写入Redis所有服务节点共享同一份Session数据。这里有一个我踩过的坑Session数据在Redis中默认是用JDK序列化方式存储的虽然功能没问题但你在Redis里看到的是一堆二进制乱码不直观。如果希望Session内容可读可以配置一个自定义的RedisSerializer。同时要记得给Session设置合适的过期时间Spring Session默认是30分钟在电商场景中这个时间有点短我习惯调整到2小时避免用户逛着逛着购物车就断线了。3.5 购物车与库存扣减的原子性处理电商项目里最考验技术功底的就是库存扣减超卖就是在这时候出现的。黑马商城的秒杀功能实现里用到了Redis的Lua脚本这是个很关键的技术点。为什么用Lua脚本因为它能保证多个Redis命令在同一个脚本里执行的原子性。比如检查库存是否充足、扣减库存、创建订单这三步如果你分三次发Redis命令中间任何一次网络抖动都会导致状态不一致甚至出现扣了库存订单却没生成的情况。把它们写进一个Lua脚本里Redis会整体执行执行期间不会插入其他命令。同时为了防止一人一单还会把用户ID作为锁的维度结合Redis的SetNX实现接口级别的防重。这里又涉及到分布式锁的一个变体锁的粒度。拿秒杀场景来说如果一个用户一次只能秒杀一单那么锁的维度应该是用户ID如果商品的总库存是多件那么扣减时的操作对象就应该是库存key的原子递减。这两种用法一组合基本能把超卖和重复下单都杜绝掉。我在带队开发商城项目时把这一套秒杀方案原封不动搬到了真实项目里聚合了5000路并发压测最终结果是库存误差为0订单数据全部有效。建议你在学完这个方法后自己也写一个Jmeter测试脚本模拟并发下单亲眼看看在没有Lua脚本和锁保护的情况下库存是怎么变负的这种直观对比会让你印象深刻得多。4. 从开发到部署的完整流水线4.1 构建打包与多环境配置管理开发阶段环境、测试环境、生产环境的配置经常不一样。如果你直接把本机数据库地址、Redis密码等硬编码在application.yml里部署到线上肯定跑不通。黑马商城这种项目通常的做法是使用多Profile配置application-dev.yml、application-prod.yml启动时通过spring.profiles.active来指定用哪套配置。打包命令我一般用mvn clean package -Dmaven.test.skiptrue跳过测试直接打jar包。这里有个细节是不要把src/main/resources下的任何文件改动直接覆盖到包外目录因为Spring Boot的jar包内部有自己的classpath目录。我之前遇到过一个问题部署时为了修改Redis地址直接进到jar包里改了application.yml再把jar包扔到服务器上运行结果怎么都不生效。后来才意识到Spring Boot加载配置的优先级是外部配置文件高于jar包内部配置。正确做法是把application.yml放在jar包同级的config目录下Spring会自动读取外部配置并覆盖内部的值。这个优先级规则如果你没搞明白会浪费很长的时间在“明明改了配置却不生效”的坑里。4.2 Docker部署Redis并搭建主从集群上线环境里单节点Redis风险很大万一Redis进程挂了缓存直接不可用所有流量瞬间砸向数据库。所以生产部署至少要上主从架构。黑马商城的部署篇里用的就是Docker Compose方式。第一步先创建主节点的配置文件redis-master.conf开启持久化appendonly yes设置密码requirepass 123456。然后建两个从节点配置文件redis-slave1.conf、redis-slave2.conf在配置文件里指定replicaof master-ip 6379和masterauth 123456。如果你的Docker网络里主机名是master直接写replicaof master 6379即可。用docker-compose.yml把三个服务编排起来执行docker compose up -d启动然后进入从节点容器执行info replication看到role:slave且master_link_status:up就说明主从同步成功了。这里最关键的一点是主节点必须开启持久化否则一旦主节点重启它的数据是空的从节点会跟着把自己清空重新同步造成数据丢失。我见过一些团队部署主从时只关注同步状态忽略了主节点持久化结果某天Redis宕机重启后所有缓存数据归零数据库直接被流量打穿。这种事故是可以提前规避的主节点开appendonly的成本不过一点点磁盘空间干嘛不给自己留条活路呢。4.3 部署Spring Boot应用至服务器Spring Boot应用打包成jar包后部署就简单了。你可以直接执行java -jar black-mall.jar启动但直接这样跑有很多隐患。首要问题是关闭终端窗口时进程会跟着退出其次日志不好管理而且进程崩溃后不会自动重启。更职业的做法是使用systemd管理进程。写一个black-mall.service文件放到/etc/systemd/system目录下定义ExecStart为java -jar /opt/black-mall/black-mall.jar再定义StandardOutput和StandardError把日志写到指定文件。配置好后systemctl daemon-reload再systemctl start black-mall就能实现开机自启和挂了自动重启。当然如果你追求更标准的容器化部署给应用写一个Dockerfile然后镜像运行也很干净。基础镜像用openjdk:8-jdk-alpine把jar包复制进去暴露8080端口启动命令是java -jar。然后通过docker compose和Redis、MySQL编排在一起一条命令把整套环境拉起来。我团队现在新项目都走这套方案省心程度比systemd那种方式高不少。4.4 Nginx反向代理与动静分离应用部署好了接下来要加一层Nginx来做反向代理。这台Nginx放在应用服务器前面用户请求先进Nginx再有Nginx转发到后端的Spring Boot服务。它能做的远不止转发负载均衡策略、静态资源缓存、请求头过滤都可以在这里配置。黑马商城前端页面里有大量的图片、CSS、JS文件如果这些静态资源也走Java应用去读取会白白消耗Java线程。最理想的做法是在Nginx里配置一个location /static/直接映射到服务器的静态文件目录让Nginx直接返回文件根本不经过Java进程。同时可以开gzip压缩大幅减少流量传输体积响应速度提升非常明显。反向代理配置里还要注意proxy_set_header的设置。如果不设置X-Forwarded-For和Host后端的Java应用获取不到用户的真实IP导致日志里全是Nginx的IP到时候想排查用户来源就抓瞎了。我建议在location块的proxy_set_header里把Host、X-Real-IP、X-Forwarded-For都配齐保证链路信息完整。4.5 监控与日志收集部署上线只是开始怎么保证运行稳定才是真正的考验。Redis本身提供了非常实用的监控命令redis-cli进入交互模式后执行info能直观看到内存使用量、客户端连接数、命中率、持久化状态等关键指标。日常巡检时我会特别关注hit_rate命中率如果低于80%就要反思缓存的有效性是否衰退了是不是大量key没被命中导致请求穿透到数据库。slowlog get是排查Redis变慢的利器。它会把执行时间超过指定阈值的命令记录下来。默认阈值是10000微秒也就是10毫秒。线上我会调到2000微秒把超过2毫秒的操作都记录下来。如果发现某个请求频繁出现在慢日志里就要考虑优化缓存结构或者减少大key的读取。应用侧的监控可以用Spring Boot Actuator暴露/actuator/health、/actuator/metrics端点配合Prometheus抓取指标再用Grafana做可视化大屏。这一整套下来Redis的CPU、内存、命中率、连接数全部图形化呈现预警规则配置好之后半夜收到告警短信的概率都会低很多。5. 实战中的常见问题与排查技巧5.1 缓存与数据库的数据不一致这是Redis缓存场景里最经典的问题。根本原因是双写时的并发竞争。举个例子A请求更新了数据库想把旧缓存删掉B请求在你删除之前读到了旧缓存直接返回了旧数据。如果业务不能容忍这种时延就需要额外手段来兜底。我做电商缓存这么多年总结下来最实用的思路是结合业务场景选择方案。对于价格、库存这类一致性要求高的数据处理方式是更新数据库后直接删除缓存同时开启延迟双删策略也就是主线程删除一次缓存后再通过一个延迟任务在500毫秒后删第二次。对于商品名称、图片这些允许短暂不一致的数据直接把过期时间设置得短一些比如5到10分钟即使出现问题也会很快自愈。还有个进阶操作是监听MySQL的binlog通过Canal中间件把数据库变更同步给Redis做更新这就是生产环境的最终一致性方案了。黑马商城项目本身没有引入这么重的组件但你如果去面试大厂提到这个方案绝对会让面试官眼前一亮。5.2 Redis连接超时与连接池耗尽很多人在本地开发一切正常一部署到Linux服务器就频繁报出Redis command timed out或者RedisConnectionException尤其是使用Lettuce客户端时更容易遇到。这类问题大概率不是Redis真的挂了而是网络配置和连接池参数的问题。Lettuce是Spring Boot 2默认的Redis客户端它基于Netty实现连接复用。在高并发下如果你没有合理配置连接池的上限大量的线程会同时等待获取空闲连接一旦池子被占满请求就会排队等锁出现超时。我常用的一组参数是maxTotal50、maxIdle30、minIdle10你可以根据自己的并发量调整。还有一个经常被忽略的地方Redis的timeout参数。如果你在Redis配置文件里把timeout设置成了一个很小的值当连接空闲时间超过这个值Redis服务端会主动断开连接而客户端的连接池不知道连接已经失效还在尝试复用就会抛出连接异常。解决思路是Redis服务端的timeout不要设置得太小同时客户端增加validateConnection或者用带健康检查的连接池。5.3 Redis序列化乱码与线上问题这是新手最容易踩的坑。使用Spring Data Redis时默认的RedisTemplate采用的序列化器是JdkSerializationRedisSerializer当你往Redis里存一个对象比如Store类的实例控制台里看到的将是类似xACxEDx00x05t...这种乱码。功能上虽然能正常读写但一旦你想跨语言或其他工具去查看数据就会非常痛苦。规范做法是通过自定义RedisTemplate配置来替换序列化器。Key使用StringRedisSerializerValue使用GenericJackson2JsonRedisSerializer。这样存进去的数据是可读的JSON排查问题比较方便。但注意使用JSON序列化意味着对象里必须有无参构造函数否则反序列化会直接报错。黑马商城教程里的DTO类都满足这个条件但你自己扩展功能时千万记得加上无参构造。另一个坑是字符串类型的缓存。如果业务里用StringRedisTemplate存了数据但读取时用RedisTemplate读就会出现类型转换异常。原因还是两者使用的序列化器不同。所以我会严格要求团队所有字符串类型的操作统一走StringRedisTemplate所有对象类型的操作统一走自定义的RedisTemplate不混用能省掉无数个“为什么读不到数据”的排查时间。5.4 分布式锁失效的致命场景分布式锁并不是加了就一劳永逸的。我总结过三个典型的失效场景每个都在实际项目中出现过必须了解清楚。第一个场景是锁的过期时间小于业务执行时间。假如业务逻辑要跑10秒但锁的过期时间只设置了5秒锁自动释放后另一个线程进来了就出现了并发安全问题。解决代码里用的是Redisson它的看门狗机制就是为了应对这种情况。第二个场景是主从切换时锁丢失。客户端A在主节点上拿到了锁但主节点还没把这条数据同步到从节点时就挂了哨兵把从节点提升为主节点后从节点上没有这条锁记录客户端B就也能拿到同一把锁了。这个问题在严格一致性要求的场景下需要用Redlock红锁方案但它在性能和可用性上有一定取舍不是所有业务都需要这么重的方案。第三个场景是GC停顿时间过长。JVM发生Full GC导致线程长时间停顿锁过期了而线程还在执行等到GC恢复后业务还在跑但锁实际已经易主了。这个问题连Redisson的看门狗也无法完全避免因为看门狗线程也可能因为GC冻结。好在这种场景概率较低如果真的不能容忍可以在业务里加上版本号或者使用数据库的唯一约束做兜底。5.5 大Key与热Key治理当Redis的某个key存的数据特别大或者某个key被超高频率地访问就会成为大Key或者热Key。大Key的危害是阻塞网络因为通过网络传输一个10MB的string一次传输就会占用很长时间影响同Redis实例上的其他请求热Key则会让单个Redis节点CPU飙升拖垮整体性能。排查大Key可以使用redis-cli --bigkeys命令它会遍历所有的key并按类型统计大小。对于String类型的一般看value字节数对于Hash和ZSet类型则看元素数量。如果发现大Key解决办法是拆分。比如一个用户购物车列表如果全部塞进一个key里数据量大了就拆成每个商品一个key然后通过管道批量读取。热Key的解决办法则是做本地缓存让热点请求在应用内存层直接返回不再打到Redis上或者给热Key加上随机的后缀分散到多个节点上。在压测黑马商城那些秒杀接口时你会发现秒杀商品的库存key就是典型的瞬时热Key所有请求都在抢这同一个key。我当时的处理方式是加了一层本地缓存把库存快速扣减然后用异步批量同步到Redis这个思路其实已经偏向CQRS模式了效果非常明显。5.6 内存淘汰策略与碎片优化生产环境的Redis内存是有限的当内存满了之后Redis会按照配置的淘汰策略去清除一部分key。如果你不设置maxmemory和maxmemory-policy默认情况是64位系统上不限制内存使用数据一直往里面塞直到Linux物理内存耗尽然后OOM killer把Redis进程干掉这就真的尴尬了。为此我需要主动设置内存上限比如maxmemory 2gb并选择allkeys-lru淘汰策略让最久没被访问的key先被淘汰。电商项目里缓存的数据大多是热点查询很适合用LRU来淘汰冷数据。内存碎片是另一个容易踩的坑。Redis里的对象经常创建和删除内存空闲列表会变得碎小的比较多导致实际内存占用很高。你观察info memory里的mem_fragmentation_ratio指标如果持续在1.5以上说明内存碎片严重此时执行redis-cli memory purge命令可以尝试回收一些碎片空间。对于持久化开启的实例还有一种方式是重启Redis重启会重新加载RDB或AOF文件通常能有效降低碎片率。做完整套黑马商城的部署之后我强烈建议你养成定期巡检Redis内存状态的习惯。一条info memory命令几十个指标花两分钟时间看一眼就能提前发现很多隐患别等线上出事之后才想起去诊断。说回这套教程本身我觉得它在国内Java项目实战中是个很好的标杆。你跟着流程走一遍收获的绝不仅仅是能写几个缓存注解和操作API更重要的是理解了高并发场景下数据一致性和系统稳定性的保障思路。如果你后续真的去做高并发电商项目你会发现这里的很多设计理念是完全可以落地的这也正是它被大量培训机构和企业拿来当教材的原因。