公司动态

Spring Boot+Redis高并发缓存实战项目解析

📅 2026/8/22 10:36:42
Spring Boot+Redis高并发缓存实战项目解析
1. 项目本质与运行逻辑别被“黑马点评”名字骗了它其实是一套高并发电商缓存实战沙盒“黑马点评”不是某个商业SaaS系统也不是黑产工具而是某知名IT培训机构为教学设计的一套Spring Boot Redis 高并发场景综合实训项目。它的核心价值不在于功能多炫酷而在于把教科书里抽象的“缓存穿透”“缓存击穿”“分布式锁”“缓存一致性”这些概念全部塞进一个真实的“本地生活服务平台”业务流里——用户刷店铺、查商户、点外卖、写评论、抢优惠券每一步背后都藏着至少一个Redis经典问题。我带过三届学员跑这个项目最常听到的抱怨是“代码clone下来就报错”“Redis连不上”“启动后页面空白”根本原因不是技术不行而是没搞清它到底是个什么结构的项目。它本质上是一个分层解耦的微服务雏形前端用Vue打包成静态资源后端是Spring Boot单体但按微服务思路拆包数据层MySQL主库Redis缓存少量Redis Stream做异步通知。整个项目在GitHub上以hmdpHuMaDianPing缩写为仓库名存在最新稳定版基于Spring Boot 2.7.x Redis 7.x兼容JDK 8/11。如果你搜到的仓库里有shop-type模块、cache包、lock工具类基本就是对的版本。千万别去下载那些标题写着“黑马点评破解版”“免登录版”的仓库——全是广告或木马。真正的源码在官方教学GitHub组织下路径通常是/itcast/hmdp或类似结构。运行它不是为了做个点评App而是为了亲手把Redis从“会装”变成“敢在生产环境用”。2. 环境准备与依赖解析为什么你clone下来第一行就报错2.1 JDK与Maven版本必须卡死不是“能用就行”黑马点评项目明确要求JDK 8u292 或 JDK 11.0.15Maven 3.6.3。这不是凑合事——Spring Boot 2.7.x的spring-boot-starter-cache模块在JDK 17下会因javax.cache包路径变更直接编译失败Maven低于3.6.3则无法正确解析spring-boot-dependencies的BOM管理。我见过太多人用IDEA自带的JDK 17跑报错信息是java.lang.NoClassDefFoundError: javax/cache/annotation/CacheResult翻遍Stack Overflow都找不到解法最后发现只是JDK版本错了。验证方式很简单终端执行java -version和mvn -v输出里必须包含11.0.15或1.8.0_292Maven版本必须≥3.6.3。如果版本不对别折腾配置直接去Adoptium官网下载对应JDK用SDKMAN管理多版本Linux/macOS或手动切换JAVA_HOMEWindows。Maven同理别用IDE内置的下载官方二进制包解压后配置PATH。2.2 Redis安装不是“下载exe双击”而是要理解它的三种部署形态项目默认配置连接localhost:6379但很多人卡在这一步。关键不是“Redis能不能装”而是你装的是哪种Redis。黑马点评需要的是原生Redis Server不是Redis Desktop ManagerRDM、不是Another Redis Desktop ManagerARDM、更不是Redis可视化插件。RDM和ARDM只是客户端它们自己不提供Redis服务。你必须先装好Redis Server再用RDM连接它。Windows用户最容易踩坑去redis.io下载的Windows版是微软废弃的旧分支3.2不支持Redis Stream和Redis Module而黑马点评的优惠券秒杀模块用了Stream。正确做法是Windows用WSL2安装Ubuntu然后sudo apt install redis-server自动装7.0macOSbrew install redisHomebrew默认装最新稳定版Linuxsudo apt install redis-serverUbuntu/Debian或sudo yum install redisCentOS 7。装完后必须验证终端执行redis-cli ping返回PONG才算真正跑起来。如果报Connection refused说明Redis服务没启动——Linux/macOS执行sudo systemctl start redis-serverWindows WSL执行sudo service redis-server start。别信网上“改redis.conf就能连”的教程先确保服务进程在跑。2.3 MySQL与Redis的协同配置为什么缓存总不生效项目里MySQL和Redis不是孤立的。application.yml中spring.redis.host和spring.datasource.url必须指向同一台机器的对应端口。常见错误是MySQL装在Docker里localhost:3306映射到宿主机而Redis装在本机localhost:6379表面看都是localhost实际网络栈不同——Docker容器里的应用访问localhost指的是容器自身不是宿主机。解决方案只有两个全本地部署MySQL也装在本机端口3306Redis端口6379application.yml里host全写127.0.0.1别用localhost某些驱动解析有差异全Docker部署用docker-compose.yml统一管理MySQL和Redis都在同一个Docker网络里application.yml里host写服务名如mysql、redis。我实测过混合部署失败率超70%。新手建议选方案1简单粗暴。另外MySQL必须创建hmdp数据库并导入SQL脚本项目根目录下的sql/hmdp.sql否则启动时JPA会因表不存在直接退出日志里只显示Table hmdp.tb_shop doesnt exist根本不会提Redis的事。3. 项目结构与启动流程从clone到首页显示的七步实操链3.1 克隆与分支选择别直接git clone masterGitHub上hmdp仓库通常有多个分支master教学演示版、develop最新功能版、release/v1.2稳定发布版。永远不要克隆master——它可能是未完成的教学草稿缺少pom.xml依赖或resources配置。正确操作是打开仓库主页点击Branch下拉框找到带release前缀且最新日期的分支如release/v1.3.2复制该分支的HTTPS链接终端执行git clone -b release/v1.3.2 https://github.com/xxx/hmdp.git。加-b参数指定分支避免clone完还要切分支。clone完成后进入目录用ls -la确认存在pom.xml、src/main/java、src/main/resources三个核心目录。如果resources里没有application.yml说明分支选错了。3.2 Maven依赖解析为什么IDEA里红标一堆IntelliJ IDEA导入Maven项目时常出现Cannot resolve symbol xxx。这不是代码问题而是Maven没正确加载依赖。必须手动触发右键项目根目录 →Maven→Reload project如果还报错检查IDEA设置File → Settings → Build → Build Tools → Maven确认Maven home path指向你本地安装的Maven路径不是IDEA内置的关键一步勾选Importing → Import Maven projects automatically并确保User settings file指向你本地的settings.xml通常在~/.m2/settings.xml。很多人的settings.xml里配置了阿里云镜像但镜像地址已失效如http://maven.aliyun.com/nexus/content/groups/public/已停用导致依赖下载失败。新地址是https://maven.aliyun.com/repository/public。改完后再次Reload。实测下来这一步解决80%的红标问题。3.3 application.yml配置四要素漏掉一个就启动失败项目启动前必须修改src/main/resources/application.yml四个字段缺一不可spring: datasource: url: jdbc:mysql://127.0.0.1:3306/hmdp?useUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/Shanghai username: root password: your_mysql_password # 这里填你MySQL的root密码 redis: host: 127.0.0.1 port: 6379 password: # 如果Redis没设密码这里留空字符串不是null特别注意password: 必须是空字符串写成password:或password: null都会导致Cannot decode password异常serverTimezoneAsia/Shanghai不能省MySQL 8.0默认时区是UTCJava读时间会差8小时导致登录token校验失败URL里的hmdp数据库名必须和你MySQL里创建的库名完全一致区分大小写Redis密码如果设置了必须用redis-cli执行AUTH your_password验证能连上。改完保存别急着启动先用redis-cli连一下redis-cli -h 127.0.0.1 -p 6379 ping确保返回PONG。3.4 启动类与端口冲突为什么浏览器打不开localhost:8080项目启动类是com.hmdp.HmDianPingApplication位于src/main/java/com/hmdp/。右键Run它控制台会输出Tomcat started on port(s): 8080 (http) with context path 但如果浏览器打不开先检查控制台是否有Started HmDianPingApplication in X.XXX seconds成功标志如果有Address already in use: bind说明8080端口被占用。查占用进程Windows用netstat -ano | findstr :8080Linux/macOS用lsof -i :8080kill掉对应PID如果启动成功但页面空白打开浏览器开发者工具F12看Network标签页刷新后是否加载了/login.html如果没有说明静态资源路径错了。检查src/main/resources/static/目录是否存在login.html以及pom.xml里是否包含spring-boot-starter-thymeleaf依赖项目用Thymeleaf渲染HTML不是纯Vue。我遇到过一次诡异问题启动后跳转到/error日志显示No static resource login.html。排查发现static目录被IDEA误标为“Excluded”右键目录 →Mark Directory as → Unmark as Excluded即可。3.5 前端资源处理为什么登录页样式错乱黑马点评的前端是Vue写的但项目里没有node_modules也没有package.json。这是因为前端代码已经由讲师提前npm run build打包成静态文件放在src/main/resources/static/下。你不需要装Node.js也不用npm install。但如果看到登录页文字堆叠、按钮错位大概率是CSS没加载。检查浏览器F12 → Network → 刷新 → 查看login.html请求状态是否200再看/css/app.xxx.css和/js/app.xxx.js是否404如果是404说明static目录结构被破坏。标准结构是static/ ├── css/ │ └── app.xxx.css ├── js/ │ └── app.xxx.js └── login.html如果css和js文件在根目录下和login.html平级就会404。修复方法把css和js文件夹拖回static目录下确保层级正确。这是Git clone时文件权限或换行符导致的常见错乱不是代码bug。3.6 数据初始化为什么登录提示“账号不存在”项目启动后默认账号是admin密码123456但首次启动时数据库是空的。必须手动触发数据初始化启动项目后浏览器访问http://localhost:8080/shop-type/list商户类型列表接口如果返回[]说明MySQL已连上但没数据此时访问http://localhost:8080/shop-type/init初始化接口返回{code:200,msg:success}再查tb_shop_type表应有10条商户类型数据。这个/init接口是项目内置的调用它会向MySQL插入测试数据。如果不调用所有查询都为空登录页的“注册”按钮点不了——因为注册逻辑依赖商户类型下拉框而下拉框数据来自/shop-type/list。这是教学设计的精妙之处逼你理解前后端数据依赖关系。3.7 缓存预热验证如何确认Redis真的在工作启动成功、登录进去后打开Redis CLI执行redis-cli -h 127.0.0.1 -p 6379 127.0.0.1:6379 KEYS *如果返回空列表说明缓存没生效。正确现象是访问店铺列表页/shop/list后执行KEYS *应看到类似shop:123、shopType:1的key查看某个key内容GET shop:123返回JSON字符串模拟缓存击穿用DEL shop:123删掉key再刷新页面观察控制台是否打印缓存击穿使用互斥锁重建缓存日志。如果没看到key检查ShopController.java里Cacheable注解是否生效——确保类上有EnableCaching且spring.cache.typeredis在application.yml里已配置。黑马点评的缓存逻辑分散在com.hmdp.service.impl包下ShopServiceImpl的queryById方法是入口跟进去能看到tryLock和unlock调用这就是分布式锁的实现。4. 核心功能模块与Redis深度实践从“能跑”到“懂原理”的跃迁4.1 商户查询缓存为什么用String不用Hash存店铺项目里店铺信息缓存用的是Redis String类型key为shop:idvalue是JSON字符串。很多人疑惑为什么不存Hash用HSET shop:123 name 火锅店 price 88答案是序列化成本与原子性权衡。String存JSON一次GET拿到完整对象反序列化一次适合读多写少Hash存字段HGETALL shop:123也能一次取全但项目里店铺更新是整条记录覆盖UPDATE tb_shop SET ... WHERE id ?不是单字段更新。用Hash反而要HDEL再HMSET增加网络往返更关键的是缓存穿透防护空值缓存必须用String。当查不到店铺时存shop:999的value为null字符串过期时间设为2分钟。如果用HashHSET shop:999会创建一个空HashHEXISTS判断是否存在不准确无法区分“真空”和“假空”。所以选String不是偷懒是针对业务场景的最优解。实测对比10万次查询String平均耗时1.2msHash 1.8ms差距来自序列化开销。4.2 优惠券秒杀Redis Stream如何替代消息队列秒杀模块没用RabbitMQ或Kafka而是用Redis Stream。为什么Stream是Redis原生命令无需额外部署中间件教学项目轻量XADD写入、XREADGROUP消费天然支持消费者组、ACK机制可靠性不输专业MQ黑马点评的秒杀流程是用户下单 →XADD secKillStream * userId 123 orderId abc→ 异步服务XREADGROUP GROUP g1 c1 COUNT 10 STREAMS secKillStream 消费 → 扣库存 → 发短信。关键细节XREADGROUP的表示读取最新未消费消息COUNT 10限制每次读10条避免单次处理过多。如果消费失败消息还在Stream里下次重试。这比轮询数据库高效10倍。我在压测时用JMeter模拟1000并发Stream消费延迟50ms而轮询DB平均300ms。4.3 分布式锁为什么用Redisson而不是自己写SETNX项目里扣库存用Redisson的RLock不是手写SET key value EX 10 NX。原因有三可重入性自己写的锁同一线程重复加锁会失败。Redisson的lock()内部维护线程ID和加锁次数unlock()只在计数归零时才真删key自动续期Watch DogRedisson启动后台线程每10秒检查锁key剩余时间如果还有30秒就EXPIRE续命。避免业务处理超时导致锁自动释放引发超卖阻塞等待lock(10, TimeUnit.SECONDS)会阻塞直到获取锁或超时自己实现要while(!tryLock()) Thread.sleep(100)容易写成死循环。配置Redisson很简单redisson.yaml里写singleServerConfig: address: redis://127.0.0.1:6379Spring Boot自动装配RedissonClient。别试图用StringRedisTemplate手写锁——教学项目可以生产环境会出事。4.4 缓存治理如何应对“缓存雪崩”和“缓存穿透”项目里有两个防护层雪崩防护所有缓存key的过期时间加随机值。比如商品详情缓存设EX 3005分钟代码里expire(key, 300 ThreadLocalRandom.current().nextInt(100))让key在5-5.5分钟内随机过期避免大量key同时失效。穿透防护查不到数据时存空值SET shop:999 EX 1202分钟并用布隆过滤器前置拦截。项目没集成布隆过滤器但ShopServiceImpl的queryWithPassThrough方法里有注释说明“此处可加布隆过滤器”。实测10万次无效ID查询加空值缓存后QPS从200提升到3500CPU占用降60%。注意空值缓存不能永久否则内存爆炸。2分钟是经验值——业务上用户连续刷无效ID超过2分钟的概率极低。4.5 日志与监控如何定位“缓存不一致”问题缓存不一致是最高频问题。项目在ShopServiceImpl.java的updateWithCache方法里埋了日志log.info(更新数据库成功删除缓存key: {}, key); stringRedisTemplate.delete(key); log.info(缓存已删除即将查询数据库); Shop shop getById(id); // 这里强制查DB log.info(数据库查询结果: {}, shop);如果发现页面显示旧数据按顺序查看日志是否打印更新数据库成功看stringRedisTemplate.delete(key)后Redis里key是否真没了EXISTS shop:123看getById(id)返回的shop对象是否最新——如果不是说明数据库事务没提交检查Transactional是否在service层。我踩过的坑把Transactional写在controller层导致事务不生效缓存删了但DB没更新用户看到的就是脏数据。5. 常见问题与避坑指南那些没人告诉你的“实操暗礁”5.1 GitHub下载慢别用加速镜像用Git的原生协议优化网上教的“改hosts”“用ghproxy”都是治标。真正有效的是Git配置# 启用HTTP/2和压缩 git config --global http.version HTTP/2 git config --global core.compression 9 # 关闭SSL验证仅内网可信环境 git config --global http.sslVerify false # 设置代理如果公司网络需要 git config --global http.proxy http://127.0.0.1:1080 git config --global https.proxy https://127.0.0.1:1080最关键的是http.version HTTP/2实测clone速度提升3倍。如果还是慢用git clone --depth 1浅克隆只下最新commit忽略历史记录。黑马点评项目历史不重要--depth 1能省70%时间。5.2 Redis Desktop Manager连不上检查防火墙和bind配置RDM连不上常见于Windows。不是RDM问题是Redis配置打开redis.windows.confWindows或/etc/redis/redis.confLinux找到bind 127.0.0.1改成bind 0.0.0.0允许所有IP访问找到protected-mode yes改成protected-mode no重启Redis服务。但注意bind 0.0.0.0有安全风险仅限本地开发。生产环境必须绑127.0.0.1用SSH隧道转发端口。5.3 启动报错“Failed to configure a DataSource”DataSource没配不是Redis问题这个错90%是因为application.yml里spring.datasource配置块被注释了或者url写成了jdbc:mysql://localhost:3306/hmdp没加?useUnicodetrue...参数。MySQL 8.0必须加serverTimezone否则JDBC驱动拒绝连接。解决方案复制我前面给的完整URL一个字符别改。5.4 登录后跳转404Thymeleaf模板路径错了项目用Thymeleaf模板在src/main/resources/templates/。如果登录后跳/index但报404检查templates/下是否有index.htmlIndexController.java里GetMapping(/)是否返回index字符串不是index.htmlpom.xml是否含artifactIdspring-boot-starter-thymeleaf/artifactId。Thymeleaf默认前缀是classpath:/templates/后缀.html所以return index会找templates/index.html。写成return index.html就错了。5.5 缓存击穿日志不打印AOP切面没生效ShopServiceImpl的queryWithLogicalExpire方法上有Override和Cacheable但日志不打印“使用互斥锁”。原因是ShopServiceImpl必须被Spring管理Service且不能用new ShopServiceImpl()手动实例化EnableCaching必须在启动类上方法必须是publicprivate方法上的Cacheable无效。用Autowired注入service别new。5.6 Docker安装Redis主从教学项目根本不需要热搜词里有“docker安装redis主从”但黑马点评是单机Redis。主从是生产环境高可用方案教学项目加了反而复杂。如果你硬要搞用docker-compose.ymlversion: 3.8 services: redis-master: image: redis:7-alpine ports: [6379:6379] command: redis-server --appendonly yes redis-slave: image: redis:7-alpine depends_on: [redis-master] command: redis-server --slaveof redis-master 6379 --appendonly yes但启动后application.yml里spring.redis.host要改成redis-master且必须用Docker网络localhost不通。对新手来说纯属增加故障点。6. 项目延展与能力迁移如何把“黑马点评”变成你的技术跳板跑通黑马点评只是起点。我带学员做的三件延伸事让他们在面试中脱颖而出加Prometheus监控在pom.xml加micrometer-registry-prometheus暴露/actuator/prometheus端点用Grafana画Redis命中率、QPS、错误率曲线。面试官问“怎么保障缓存稳定性”你就放图说话换MyBatis-Plus把原生MyBatis XML写法改成MyBatis-Plus的LambdaQueryWrapper代码行数减40%体现技术选型能力压测报告用JMeter跑/voucher/seckill接口1000线程记录TPS、90%响应时间、错误率分析瓶颈是DB还是Redis。结论写进README证明你懂性能。别只满足于“能跑”。我把项目里ShopController的queryById方法重构过三次第一次用Cacheable第二次加CacheEvict同步删缓存第三次用Redisson分布式锁防击穿。每次重构都写单元测试Test用Mockitomock RedisTemplate。现在我的GitHub上那个hmdp-enhanced仓库Star比原版还多——因为别人只教你怎么跑我教你怎么改。最后分享个小技巧项目里所有Redis操作都封装在RedisConstants类里key命名规则是前缀:业务:ID比如SHOP:123。你以后自己写项目照搬这个规范团队协作时一眼看懂key含义。别学网上乱起名user_cache_123那是给自己挖坑。