黑马点评

项目综述(P1–175) 42小时45min

Redis: 8个数据结构, 2个消息队列, 4个测试相关

该教学分为四个部分:

  • 基础篇

    • 认识Redis

    • Redis命令

    • Redis的Java客户端

  • 实战篇

    • 短信登录
    • 商户查询缓存
    • 优惠券秒杀
    • 分布式锁
    • 秒杀优化
    • Redis消息队列
    • 达人探店
    • 好友关注
    • 附近商铺
    • 用户签到
    • UV统计
  • 高级篇

    • 分布式缓存
    • 多级缓存
    • Redis最佳实践
  • 原理篇

    • Redis数据结构
    • Redis网络模型
    • Redis通信协议
    • Redis内存回收

本地路径:

D:\git\java\hm-dianping

  • hm-dianping-back
  • hm-dianping-front

实战篇学完了,感觉质量最高的还是在P71之前那部分,后面的话像redis做消息队列一般公司也不会用,随便看看就行。再然后达人探店开始的话就比较基础了,价值一般;好友关注的feed流如果不懂的话,这里概念讲的可以,堪称百万PPT,但是代码实现上很明显没有之前的质量高了,是达不到企业标准的;再往后的其实就是对三个高级数据结构的应用,这里还是不错的,如果没有企业实习的经历,这个能开拓眼界。总体上已经是b站最好的redis教程了。另外,希望这种使用半成品的项目拿来教学的模式以后能多多推广,因为很多CRUD的业务很拉低教程的质量,对于有一定开发经验的人来说,看到很多CRUD基本上就不想再看了。完全可以像这个一样,把基本骨架搭好,然后把当前要学的教学点空出,让学员自己实现,然后官方也可以注明需要学过哪些前置知识,让适合的人来学习,希望黑马越办越好

部署

Redis 6.2+

安装Redis

redis版本: 6.2.6

docker pull redis:6.2

docker run -itd --name redis-hm -p 6379:6379 redis:6.2

# 进入容器执行命令, 这个是无密码的
docker exec -it redis-hm /bin/bash

#这个可以进行
docker run -p 6379:6379 --name redis-hm -v D:/git/java/hm-dianping/conf/redis.conf:/usr/local/etc/redis.conf -v D:/git/java/hm-dianping/data:/data -d redis:6.2


#这个不可以启动成功
docker run -p 6379:6379 --name redis-hm -v D:/git/java/hm-dianping/conf/redis.conf:/usr/local/etc/redis.conf -v D:/git/java/hm-dianping/data:/data -d redis:6.2 redis-server /usr/local/etc/redis.conf --appendonly yes
  • -p 6379:6379 端口映射:前表示主机部分,:后表示容器部分。
  • –name myredis 指定该容器名称,查看和进行操作都比较方便。
  • -v 挂载目录,规则与端口映射相同。
    会将宿主机的配置文件复制到docker中
    重要: 配置文件映射,docker镜像redis 默认无配置文件。
  • -d redis 表示后台启动redis
  • redis-server /etc/redis/redis.conf 以配置文件启动redis,加载容器内的conf文件,最终找到的是挂载的目录/usr/local/docker/redis/redis.conf
    重要: docker 镜像reids 默认 无配置文件启动
  • –appendonly yes 开启redis 持久化

Redis配置

常见配置

# Docker中运行的Redis默认配置文件路径为/usr/local/etc/redis.conf。
# 允许访问的地址,默认是127.0.0.1, 会导致只能在本地访问。修改为. 0.0.0.0则可以在任意TP访问,生产环境不要这样设置
bind 0.0.0.0
# 守护进程, 设置为yes可以后台运行
daemonize yes
# 密码
requirepass 123321

其他配置

#监听端口
port 6379
#工作目录
dir ./
# 数据库数量, 总共16个,设置为1则使用1个数据库
databases 1
# 设置redis最大内存
maxmemory 512mb
#日志文件
logfile "redis.log"

Redis图形化界面

下载安装包: https://github.com/lework/RedisDesktopManager-Windows/releases

Redis自身命令

#查看redis进程
ps -aux|grep redis

#查找redis查找目录
whereis redis

#关闭redis
redis-cli -h 127.0.0.1 -p 6379 shutdown

Mysql 5.7+

安装mysql

docker pull redis:6.2

#3306被本地的给占用了
docker run -itd --name mysql-hm -p 3307:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql:5.7

导入项目

要检查项目的maven地址正确, 设置为自己的maven路径和maven库和maven配置路径
image-20230426105515533

如果遇到maven的pulgins有波浪线, 先注释掉原有的依赖, 然后再重新加载

alt+8 显示IDEA 的services添加SpringBoot

报错org.yaml.snakeyaml.error.YAMLException: java.nio.charset.MalformedInputException: Input length, 要设置文件编码(file encodings)并将内容重新拷贝到新建同名文件夹applicaion.yaml, 注意必须要新建文件然后复制内容

修改配置:

  • 数据库端口为3307, 密码为123456

  • redishosts为127.0.0.1, 密码为空

  • 完整版代码需要额外修改:
    RedissonConfig下面的地址和密码, 因为我的Redis没有密码, 所以这句话需要注释掉后面

     // config.useSingleServer().setAddress("redis://127.0.0.1:6379")setPassword("");
    
    //没有密码是这样
     config.useSingleServer().setAddress("redis://127.0.0.1:6379")
         
    //没有面不是这样
    config.useSingleServer().setAddress("redis://127.0.0.1:6379")setPassword("");
    

还要修改后端SystemConstants.java上传图片的本地地址

public static final String IMAGE_UPLOAD_DIR = "D:\\git\\java\\hm-dianping\\hm-dianping-front\\nginx-1.18.0\\html\\hmdp\\imgs\\";

前端

D:\git\java\hm-dianping\hm-dianping-front\nginx-1.18.0:

启动: start nginx.exe

访问: localhost:8080

基础篇(P1-P24) 4h7min (230420-230422)

认识Redis

Redis是NoSql数据库, 键值数据库.

NoSql键值数据库与Sql的对比

  • Sql是结构化的, 而NoSql是非结构化的(Redis键值型, MongoDB文档类型, HBase列类型, Neo4图类型),
  • Sql是关联的, 而NoSql是无关联的(记录和记录),
  • 关系型数据库使用SQL, 查询语法有SQL规范, 而NoSql自定义
  • 关系型数据库有事务, 可以保持一致性, 而NoSql一般无事务, 不能保障一致性
  • image-20230420200819762
SQL NoSQL
数据结构 结构化(Structured) 非结构化
数据关联 关联的(Relational) 无关联的
查询方式 SQL查询 非SQL
事务特性 ACID BASE
存储方式 磁盘 内存
扩展性 垂直 水平
使用场景 1)数据结构固定
2)相关业务对数据安全性, 一致性高
1)数据结构不固定
2)对一致性、安全性要求不高
3)对性能要求高

Redis的特征

特征:

  • 键值(key-value)型, value支持多种不同数据结构,功能丰富
  • 单线程,每个命令具备原子性
  • 低延迟,速度快(因为基于内存、IO多路复用、良好的编码)。
  • 支持数据持久化
  • 支持主从集群、 分片集群
  • 支持多语言客户端

Redis常见命令

这部分看的书籍Redis实战

Redis的Java客户端

客户端对比

  • Jedis, 命令名称与Redis相同, 但是线程不安全, 需要基于连接池
  • lettuce, 基于Netty, 线程安全的, 支持Redis的哨兵模式, 集群模式和管道模式, 默认兼容, 单机使用
  • Redisson, 基于Redis实现的分布式, 可伸缩的Java数据结构集合,

P17 Jedis快速入门

介绍了Java项目如何使用Jedis

P18 Jedis的连接池

用Jedis连接池来生成Jedis实例

P19认识SpringData

P20Redis Template的简单使用

新建springboot项目, 整合Redis

Redis Template的两种序列化时间方案:

  1. 自定义RedisTemplate, 修改RedisTemplate的序列化器为GenericJackson2JsonRedisSerializer, 但是会占用课外空间
  2. 使用StringRedisTemplate, 写入Redis时手动将对象序列化成Json, 读取Redis时, 手动将读取到的Json反序列化成对象

P21Redis Template的Serializer

默认是JDKSerializationRedisSerializer

建议值为StringRedisSerializer

建议键为Jackson2.JsonRedisSerializer

image-20230422100257904

弹幕说太麻烦了, 可以配置实现

P22StringRedisTemplate

上一节存储Object, 使用默认的Json序列化工具会存储类对象信息, 会带来额外开销, 所以不要用默认的序列化工具, 而是统一使用String序列化工具, 要求只能存储String类型的key和value, 当存储Java对象时, 手动完成对象的序列化和反序列化.

Spring默认提供了StringRedisTemplate:

image-20230422101513204

P23 RedisTemplate操作Hash类型

演示了一下如何操作Hash类型的数据

实战篇(P25-95)19h33min

短信登录(1h42min)(230423, 230424, 230426)

P24 Redis企业实战课程点评项目

黑马点评:

  • 短信登录: Redis的共享Session应用
  • 商户查询缓存: 企业的缓存使用技巧, 解决缓存雪崩穿透等问题
  • 达人探店功能: 基于List点赞列表和SortedSet点赞排行榜
  • 优惠券秒杀: Redis的计数器, Lua脚本, 分布式锁, Redis的3种消息队列
  • 好友关注: 基于Set集合的关注, 取关, 共同关注和消息推送等功能
  • 附近的商户: Redis的GeoHash的应用
  • 用户签到: Redis的BitMap数据统计功能
  • UV统计: HuperLogLog的统计功能

P25 导入黑马点评项目

单体项目, 前后端分离, 前端Nginx, 后端Tomcat+Mysql+Redis

前端启动: start nginx.exe, 然后访问http://127.0.0.1/8080

P26 基于Session实现短信登陆和注册

image-20230423111642486

P27 实现发送短信验证码功能

  1. 路径前面加/api/可以标明是请求tomcat的请求
  2. 请求方式: Post
    请求路径: /user/code
    请求参数: phone: 电话号码
    返回值: 无
  3. 使用alt+enter可以自动跳转, ctrl+h查看
  4. 这里的手机号和验证码存储有问题, 项目是只把验证码存储到了这个session里面, 没有存储手机号, 应该也要存储手机号, 要不然先用一个手机号发送验证码, 后面验证码得到了再用另一个Redis的kv存储.

P28 实现短信验证码登陆和注册功能

  1. 应该把”code”这种常量值放在一个地方去, 而不是用魔法值

  2. 我觉得先画流程业务图, 然后根据业务图写代码框架, 然后再根据代码大致框架来写代码细节这个方法挺好的
    image-20230426102732806

  3. 有弹幕说实际工作中不用mybatis-plus, 侵入性太强了, 业务逻辑一复杂就完蛋

  4. 登陆就算成功了还是在登陆界面, 所以要在登陆前面完成一个校验信息, 如果已登陆显示用户信息界面

P29 实现登陆校验拦截器

  1. 拦截器LoginInterceptor要实现接口HandlerInterceptor
  2. 拦截器LoginInterceptor要使用需要在MVC框架的配置中配置(定义一个MVC的配置类)
  3. ctrl + alt + z自动补全变量类型和变量名

P30 session共享的问题

  1. session共享问题, 多台tomcat并不共享session的存储空间, 当请求切换到不同tomcat服务时导致数据丢失的问题, 尽管session提供了拷贝功能, 但是浪费内存并且有拷贝时间, 会出现不一致的问题, 所以需要redis来实现以下特点:
    • 数据共享(tomcat访问的redis)
    • 内存存储
    • key, value存储

P31 Redis代替session的业务流程

image-20230426221739129

P31 基于Redis实现短信登陆

  1. 注意设置有效期

  2. StringRedisTemplate是继承RedisTemplate了, 只是设置了一些序列化和反序列化的方式是String.

  3. Javautil类hutool类中有UUID类, 可以生成UUID

  4. 应该在登陆拦截的时候, 通过的时候要更新token有效期为新的30分钟

  5. StrUtil工具类可以判空StrUtil.isBlack(token)

  6. .var可以补全

  7. StringRedisTemplate在LoginInterceptor的类中使用, 但后面这个类是自己管理的对象, 所以无法使用注入, 所以需要在这个拦截器类的使用的地方进行StringRedisTemplate的注入(也就是MVC框架那里使用了这个拦截器, 在MVC框架上面定义一个StrintRedisTemplate传入进来.

  8. 由于DTO中有非String , 而StringRedisTemplate只能接收String类型的对象, 所以需要转换

     Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),
                    CopyOptions.create()
                            .setIgnoreNullValue(true)
                            .setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));
    
  9. token取的时候用requst.getHeader("authortization")取, 当然对应的在前端要写拦截器在每个请求的头上加入key=authortization的头.

P32 解决登陆状态刷新的问题

  1. 如果用一个拦截器对需要登陆的路径来刷新touken, 那么不需要登陆的路径的访问就不会刷新token, 所以需要用一个对于所有路径的过滤器来刷新token(只刷新不拦截, 也就是都返回true, 让下一层再拦截), 然后再在第二个拦截器进行登陆的拦截
    image-20230427004331780

    image-20230427004357121

  2. 拦截器.order可以设置优先级

商户查询缓存(3h8min)(230427,230429,230505)

P35 什么是缓存

缓存的优点:

  • 优点: 降低后端负载, 提高读写效率, 降低响应时间

  • 缺点: 数据一致性成本, 代码维护成本, 还有运维成本

P36 添加商户缓存

根据id查询商户缓存的流程:

image-20230427104933605

  1. redis的存储用StringRedisTemplate类去操作, 比如StringRedisTemplate.opsForValue.get(key)
  2. 数据库的操作直接用函数, 比如save(), getById()
  3. 将对象和字符串互转为String的类是JSONUTIL,
    • JSONUtil.toBean(shopJson, Shop.class)
    • JSONUtil.toJsonStr(shop)

缓存更新策略

解决一致性问题

  1. 低一致性: 内存淘汰. 高一致性: 主动更新.

  2. 主动更新策略:

  • ==缓存调用者在更新数据库时同时更新缓存(首选)==
  • 调用者直接调用封装好的缓存数据库一致性服务
  • 调用者只操纵缓存, 其他线程异步定时或按频率将缓存数据持久化到数据库, 但是有过时数据和不一致的情况
  1. 操作数据库和缓存需要考虑:

    1. 更新数据库后删除缓存还是更新缓存?
      ==删除缓存==

    2. 保证缓存与数据库的操作同时成功或失败
      单体系统使用事务, 分布式系统使用TCC等分布式事务方案

    3. 先操作缓存还是先操作数据库?
      ==先更新数据库,再删缓存==

  2. image-20230427113816224

实现商铺缓存与数据库的双写一致性

  1. 这里我的前端没有显示图片, 查看后端日志, 可以看出查询到了10条数据, 但是分页好像没分队, 在ShopServiceImpl=>queryShopByType()方法中

缓存穿透的解决思路

1.

缓存总结

image-20230505162608086

image-20230505163039125

image-20230505163410076

image-20230505164055347

image-20230505164007903

优惠券秒杀(2h13min)(230505, 230508)

一人一单

应该先事务结束,再释放锁

分布式锁

image-20230508223813147

高级篇(P96-P144) 9h28min

原理篇(P145-P175) 9h36min

总结

要学的内容:

Mybatis plus的增删改查的java语句,比如

//5,扣减库存
    boolean success = seckillVoucherService.update()
            .setSql("stock= stock -1")
            .eq("voucher_id", voucherId).update();

Spring事务失效的几种可能性

P54 优惠券秒杀-一人一单

但是以上做法依然有问题,因为你调用的方法,其实是this.的方式调用的,事务想要生效,还得利用代理来生效,所以这个地方,我们需要获得原始的事务对象, 来操作事务

P69 异步秒杀思路

Jmeter多线程测试

快捷键

.var

.for

选中代码新建函数ctrl+alt+m


欢迎在评论区中进行批评指正,转载请注明来源,如涉及侵权,请联系作者删除。

×

喜欢就点赞,疼爱就打赏