时长分布
基础篇:P1-P22 3h59m
实战篇:P24-P95 19h40m(✔)
短信登录:24-34 2h20m(✔)
商户查询:35-47 3h22m(✔)
优惠券秒杀:48-77 8h27m(✔)
达人探店:78-81 1h9m(✔)
好友关注:82-87 1h56m(✔)
附近商户:88-90 1h3m(✔)
用户签到:91-95 1h12m(✔)
高级篇:P96-P144 9h29m
原理篇:P145-P175 9h38m
推荐博客
推荐两篇博客,一篇和本文类型相同,为debug记录。
黑马点评问题综合(老师可以运行我不可以,还有,老师没有细讲,但我们不知道的,都在本文可以找到)-CSDN博客
野蛮人6号,目前看到的最好的一篇综合debug,文章质量高,解决方案清晰可行。我自己做的时候遇到了文中没出现的bug,绝望中去评论区找作者交流来着,他回复得很快很认真嘿嘿,虽然没解决但哥们很开心,然后凌晨回去查出来了。
另一篇为笔记,非常详尽,涵盖所有内容。
Redis实战(黑马点评--异步秒杀消息队列) - 夏雪冬蝉 - 博客园
夏雪冬蝉,真神。本文涵盖了本项目所有重要代码,所有知识点,非常牛逼的一份笔记。我觉得秒杀优化之后的部分都挺简单的,网课听懂了代码直接抄都行,文中几乎没有错误。
day1-配置环境
导入sql,下载navicat
【2025】Navicat 17最新保姆级安装教程(附安装包+永久使用方法)_navicat17-CSDN博客
夸克网盘下载Navicat链接
https://pan.quark.cn/s/0a032156963f
配环境配半天,不像外卖一样好配,改的东西还挺多的。
数据库名称注意一致,比如idea里的db,navicat里新建都用hmdp。
前端启动nginx之前,先在nginx-1.18.0文件夹里创建两个空文件夹,命名为client_body_temp和temp,不然跑不起来。
pom文件里的修改我只参考了这篇文章。
黑马点评之导入初始项目(java)_黑马点评 jdk8-CSDN博客
pom修改完,maven得reload,时间会比较长,我电脑性能,网速各方面都挺好,还花了3min。
我jdk用的17,注意在project structure, language level, springboot的configuration里都要修改。
day2-短信登录-p28
若UserServiceImpl类中这句代码报错,类前加Slf4j注释。
log.debug("发送短信验证码成功,验证码:{}",code);
p28手机号登录时需要勾选页面下方已阅读协议按钮,可能点选不上,点击按钮上方能点上。
多点几次吧,烦死我了,测试的大部分时间点不上。
day2-登录校验-p29
环境问题很多,等我做完之后发一下我的pom文件。
这篇博客挺有用的,主要把springboot从2换到3,添加Jakarta注解API。但这需要把代码里面的javax导包都改成Jakarta,改的蛮多,所以我没改哈哈,只把其他的都更新成较新版本了。我目前还用的springboot2。
黑马点评项目报错问题整理笔记2025_黑马点评项目导入报错如何解决-CSDN博客
UserServiceImpl文件里调试完这句代码有问题。mybatis里的问题,导致服务器异常。
User user = query().eq("phone", phone).one();
若输入未注册的手机号和正确验证码后,点击登录,预期输出与视频中一致。
实际输出为界面不跳转,上方弹窗“服务器异常”,数据库中未写入信息。
若输入已注册的手机号和正确验证码后,点击登录,预期输出为返回主界面,实际输出一致。
我用的黑马github上源码,非网上各种奇怪版本。区别为,源码版本数据库中数据少,其他版本经他人使用,脏数据多。
我新注册的第一个手机号的id为1010,第二个手机号为1011。
解决方案,修改为:
User user = baseMapper.selectOne(
new QueryWrapper<User>()
.eq("phone", phone) // 确保字段名与数据库一致
);
LoginInterceptor中,强转user类型为UserDTO,若用User会报错。
UserHolder.saveUser((UserDTO) user);
day2-环境崩了-重新开始
拦截器写完了之后,bug太多修不过来,起也起不来,放弃debug。按这篇博客提供的init工程,重新开始。
黑马点评-2025(初始项目保姆级配置讲解)_黑马点评项目搭建-CSDN博客
day2-短信登录-p28-二周目
application.yaml里需要修改数据库的配置。
实现短信登录时,输入未注册手机号与正确验证码后,点击登录,界面能正常跳转,但终端报错类似如下信息。
ERROR 25388 --- [nio-8081-exec-1] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Exception during pool initialization.
java.sql.SQLNonTransientConnectionException: Public Key Retrieval is not allowed
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:110) ~[mysql-connector-java-8.0.30.jar:8.0.30]
是MySQL 连接时出现的 "Public Key Retrieval is not allowed" 问题。主要原因是 MySQL 8.0 及以上版本默认使用了新的身份验证插件 caching_sha2_password,而客户端在连接时没有正确配置。
解决方案:
数据库连接URL中添加以下参数:
allowPublicKeyRetrieval=true&useSSL=false
完整示例:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/hmdp?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
day2-登录校验-p29-二周目
开屏弹幕说如果拦截跳转异常,看到p33就好了。但愿如此。
拦截器实现方法时,快捷键ctrl+i。
视频里说强转成User类型,我还是先按UserDTO去做了。
UserHolder.saveUser((UserDTO) user);
果然拦截器报错,有弹幕说先往后看,之后会解决。要不就是前面把UserDTO转User呗,也不难。
(p33看完回来):确实先不用管,现在改了,之后会再改。
保存用户信息到session中,用的是 Hutool 的 BeanUtil.copyProperties(),不是spring的。
// Hutool 用法(推荐)
UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
session.setAttribute("user", userDTO);
day2-源码问题?-p30
我怀疑给的代码就是有问题,p30做完后,登录没有me请求,成功登录后跳转主页,不过点击“我的”,能看到默认用户信息,可爱多。头像为女生。
day2-登录校验-p33
注入redis后,快捷键alt+insert插入构造器。
p33末尾,点击登录,仍跳转主页,与视频演示不一致,弹幕里的方法都试了,没用。例如看返回值是否含token,相关注解的添加等。有弹幕说先往后看,之后会解决。
day2-登录校验-p34
RefreshTokenInterceptor中两处判空直接放行,是为了防止Redis空指针异常。
p34做完,登录后仍然返回主界面,未显示个人信息,那个界面一闪而过,头像为小猫。尝试了一些修改前端代码后修改nginx配置并重启的解决方案,为解决。/me的请求还是401。有一个说代码没问题的帖子消失了。
服了,现在是2025.7.10凌晨3点,debug失败。
凌晨3点半,又试了试,去掉RefreshTokenInterceptor里的两个if,个人信息界面正常显示,但报错服务器异常。
我猜吧,还是token的问题,不过这个bug不影响整个项目,先往后学吧,不纠结了。
调试完我的token确认存到redis里了,返回后再往下走走的太深了,看不懂,最后抛异常。应该是第一个拦截器从请求头中取token有问题,花时间能改出来。
有一种方案是把/me接口里的userDTO类型全转为user,我没试。
我最后调的状态是,点击登录后,/login报200,跳转主页。点击“我的”,/me接口报200,但前端返回了个undefined,弹窗“服务器异常”。
idea中报错信息如下:
MethodArgumentTypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'java.lang.Long'
根本原因:
NumberFormatException: For input string: "undefined"
前端传递了一个非数字值 "undefined"
给后端,后端尝试将其转为 Long
时失败。
前端问题吧,不搞了。
day2-我是傻逼-p37
写p37缓存作业时查了之前的代码,发现User实现类里把User对象转为HashMap存储之后存储时,定义的tokenKey传错参了。token写成phone了...
String tokenKey = LOGIN_USER_KEY+token;
源码没问题。
p37直接抄的作业。
黑马Redis实战篇.03.练习题—给商铺类型加缓存_黑马redis作业-CSDN博客
day2-双写一致-p39
没用postman,用的apifox,就是构造请求麻烦点。全网没找着接口文档。
day3-互斥锁-p44
本节末尾突然需要jmeter,有弹幕建议先把springcloud微服务学完,这些工具那边都会用到。
jmeter下载完成后,看一些博客启动是用bin文件夹里的bat脚本,我最新版没找着。按这篇文章里的方式一,直接在bin目录里cmd,运行jmeter命令,启动界面。
我jmeter的版本为5.6.3,官网下载的最新版zip,下载速度还挺快的。
Jmeter官网:Apache JMeter - Download Apache JMeter
Jmeter配置:(21 条消息) JMeter安装和使用教程 - 知乎
软件使用也不难,按网课中的配置去配置,测试计划中先添加线程组,后续一级一级添加对应选项即可。我测的吞吐量是199.8,符合预期200左右。
不过idea中有报错:
non null key required
day3-DoubleCheck-p44,45
获取锁成功后应再次检测redis缓存是否过期,如果存在则无需重建缓存,网课中没有代码,需自己手写。若不添加此代码,请求会打到数据库。
p45时弹幕在吵双重检查的必要性,我认为还是有必要。
day3-逻辑过期-p45
这哥们的博客不错。
Redis-黑马点评项目-04-使用逻辑过期来解决缓存击穿问题
手动添加测试类,进行单元测试后,后续操作才有效(缓存预热)。
我实际结果为idea中看到数据库只执行一条,证明并发安全,一次重建。
但jmeter中出现四种状况:
最早的请求:店铺不存在!
随后的请求:服务器异常
随后的请求:修改前的旧数据
随后的请求:修改后的新数据
day3-封装工具类-p46
泛型,函数式编程需要理解。
最终实际测试结果同网课,成功。请求均成功返回预期店铺信息,无异常。
day3-优惠券秒杀-p49,50
单元测试结果1075,生成30000个id。
使用apifox测试,json数据如下。
{
"shopId":1,
"title":"100元代金券",
"subTitle": "周一至周五均可使用",
"rules": "全场通用\\n无需预约\\n可无限叠加\\不兑现、不找零\\n仅限堂食",
"payValue": 8000,
"actualValue": 10000,
"type": 1,
"stock": 100,
"beginTime": "2025-06-26T10:09:17",
"endTime": "2025-06-26T12:09:04"
}
注意设置优惠券起始和结束时间须晚于当前时间,优惠券才能在界面中显示。不需要关拦截器。
day3-优惠券秒杀-p52
jmeter压测配置如下。所示配置错会报401。预期200,但也有可能200,服务器异常。
登录状态头这么配。名称是authorization,不含冒号和token,值从token里取,否则会取不到user_id。
【Bug记录】黑马点评使用jmeter进行秒杀抢购时报401以及200后HTTP请求依旧异常的解决办法_黑马点评jmeter-CSDN博客
JSON断言可参考此博客配置。若不添加断言,异常率为0。
idea快捷键,ctrl+alt+l,代码格式化
day3-一人一单-p54
这节课很精彩,几次代码的修改,分析了多种情况。
pom文件中添加aspectjweaver注意指定版本号,我使用的1.9.4。或许1.9.7也行,不指定可能启动报错。
悲观锁压测结果同网课。99.5%异常,成功一次。
day3-集群-p55
集群配置-Dserver仍有效,不行再试试--server。我有其他项目就先用8083了。
在modify options中,点选add VM options:-Dserver.port=8083
nginx配置负载均衡时,配置文件注意42和43行,和网课中一样。
nginx重载命令:
nginx.exe -s reload
配置文件修改完后记得ctrl+s保存,修改才能生效(我就忘了)。保存后启动,输出符合预期。
若失败可以尝试通过任务管理器,把nginx进程关闭,重新启动。重启电脑是不必的。
两个jvm测秒杀时,注意使用没抢过秒杀券的用户,或将当前用户数据库中下单信息删除。
day4-分布式锁-p58
锁的接口:ILock.java
public interface ILock {
/**
* 尝试获取锁
*
* @param timeoutSec 锁持有的超时时间,过期自动释放
* @return true表示获取锁成功,false表示获取锁失败
*/
boolean tryLock(long timeoutSec);
/**
* 释放锁
*/
void unlock();
}
关于本章内容的一篇不错的博客:
Redis实战(黑马点评--分布式锁) - 夏雪冬蝉 - 博客园
字符大小写切换idea快捷键:ctrl+shift+u
day4-分布式锁误删-p60
注意UUID用hutool包里的。
本节在代码中添加线程标识逻辑,但并未解决一人多单问题。
p63的lua脚本我没写hh。
day4-Redisson-p65
一个傻逼问题卡了2h,apifox发请求测redisson代码,发送请求后提示连接错误。
错误:Put "http://localhost:8081/shop": dial tcp [::1]:8081: connect: connection refused
后来发现测别的接口也这样,报错如上所示。
原来是我把apifox插件关了..他没权限了..开开就好了。
再就注意秒杀券的起止时间,过期就重设。
我没单测,因为感觉要改conf,懒。就用的俩jvm测,输出符合预期。异常99.5%,只下了一单。
注意redisson配置,大部分人address应该是:
config.useSingleServer()
.setAddress("redis://localhost:6379")
day5-秒杀优化-p70
我redisson版本用的3.36.0,启动时warn报netty版本过低。
尝试pom中显式声明netty为4.1.108,重载未成功,仍然报错,还是用的Spring Boot 2.7.4 默认的 Netty 版本,算了没关系。
测试还是集群去测吧,单测需要修改配置文件。
shift+F6同时修改变量方法,我的电脑不生效,使用的Ctrl+Shift+Alt+J
感谢这篇文章。
day5-阻塞队列-p71
p71结尾没法测,服务器异常。没有网课中给到的一千用户与token。
createVoucherOrder函数:
@Transactional
public void createVoucherOrder(VoucherOrder voucherOrder) {
// 5.一人一单
Long userId = voucherOrder.getUserId();
// 5.1 查询订单
Long count = query().eq("user_id", userId).eq("voucher_id", voucherOrder.getVoucherId()).count();
// 5.2 判断是否存在
if (count > 0) {
log.error("用户已经购买过一次!");
return;
}
// 6.扣减库存
boolean success = seckillVoucherService.update()
.setSql("stock=stock-1")
.eq("voucher_id", voucherOrder.getVoucherId()) // where id = ? and stock > 0
.gt("stock", 0)
.update();
if (!success) {
// 扣减失败
log.error("库存不足!");
return;
}
save(voucherOrder);
}
day5-补lua脚本-p76
学到stream,发现lua脚本逃不掉,重新从p63开始写。代码倒是不难,逻辑很清楚。
p63添加unlock.lua脚本:
先下载lua依赖,选择emmylua。
-- 比较线程标示与锁中的标示是否一致
if(redis.call('get', KEYS[1]) == ARGV[1]) then
-- 释放锁 del key
return redis.call('del', KEYS[1])
end
return 0
p63修改分布式锁实现为lua脚本实现,execute()调用代码:
@Override
public void unlock() {
// 调用lua脚本
stringRedisTemplate.execute(
UNLOCK_SCRIPT,
Collections.singletonList(KEY_PREFIX + name),
ID_PREFIX + Thread.currentThread().getId());
}
p65,jmeter压测结果符合预期。
day6-配置新redis-p77
个人本节跳过。
创建stream队列时,使用XGROUP命令需要redis版本>5.0,而默认使用的redis为3.2版本。
解决方案是在linux环境下配新redis,或原win环境安装新版本redis。
我win装网上的6.2.6版本失败,搞半天没搞出来,有些安装包说适配win,解压出来没配置文件,得自己配,从github上找。
redis官方没win版,也不推荐在win环境下使用。
有个5.0的win版redis,下载大佬给的msi文件,b站上有哥们有教程,我没尝试,但看着可行。
关于此问题的其他大部分解决方案,我都看过了,没啥可行的。
另外,有大佬建议stream部分可以跳过,本项目中stream部分比较简陋。
我先往后做了,后面都简单。本项目最难的部分是秒杀优化,都做完了。
day6-附近商户-p90
需要改pom文件,使用redis6.2提供的geosearch命令。
day0-评论区大佬的建议
实战篇学完了,感觉质量最高的还是在P71之前那部分,后面的话像redis做消息队列一般公司也不会用,随便看看就行。再然后达人探店开始的话就比较基础了,价值一般;好友关注的feed流如果不懂的话,这里概念讲的可以,堪称百万PPT,但是代码实现上很明显没有之前的质量高了,是达不到企业标准的;再往后的其实就是对三个高级数据结构的应用,这里还是不错的,如果没有企业实习的经历,这个能开拓眼界。总体上已经是b站最好的redis教程了。
过来人提醒下,不要用stream,可以跳过不看,用专业的消息队列中间件,同时准备好消息队列的八股,否则简陋的stream很容易被问死。 异步持久化还存在消息丢失、消息重复消费的幂等性问题尤其要注意。 另外生成分布式唯一id的方案也不太行,高度依赖Redis的可用性,最好用雪花算法 个人觉得异步持久化这块看着还行,其实问题不小