时长分布

基础篇: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

感谢这篇文章。

Idea快捷键-修改同名变量名 - 飘入尘风 - 博客园

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的可用性,最好用雪花算法 个人觉得异步持久化这块看着还行,其实问题不小