项目综述(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配置路径
如果遇到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一般无事务, 不能保障一致性
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的两种序列化时间方案:
- 自定义RedisTemplate, 修改RedisTemplate的序列化器为GenericJackson2JsonRedisSerializer, 但是会占用课外空间
- 使用StringRedisTemplate, 写入Redis时手动将对象序列化成Json, 读取Redis时, 手动将读取到的Json反序列化成对象
P21Redis Template的Serializer
默认是JDKSerializationRedisSerializer
建议值为StringRedisSerializer
建议键为Jackson2.JsonRedisSerializer
弹幕说太麻烦了, 可以配置实现
P22StringRedisTemplate
上一节存储Object, 使用默认的Json序列化工具会存储类对象信息, 会带来额外开销, 所以不要用默认的序列化工具, 而是统一使用String序列化工具, 要求只能存储String类型的key和value, 当存储Java对象时, 手动完成对象的序列化和反序列化.
Spring默认提供了StringRedisTemplate:
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实现短信登陆和注册
P27 实现发送短信验证码功能
- 路径前面加/api/可以标明是请求tomcat的请求
- 请求方式: Post
请求路径: /user/code
请求参数: phone: 电话号码
返回值: 无 - 使用
alt+enter
可以自动跳转,ctrl+h
查看 - 这里的手机号和验证码存储有问题, 项目是只把验证码存储到了这个session里面, 没有存储手机号, 应该也要存储手机号, 要不然先用一个手机号发送验证码, 后面验证码得到了再用另一个Redis的kv存储.
P28 实现短信验证码登陆和注册功能
应该把”code”这种常量值放在一个地方去, 而不是用魔法值
我觉得先画流程业务图, 然后根据业务图写代码框架, 然后再根据代码大致框架来写代码细节这个方法挺好的
有弹幕说实际工作中不用mybatis-plus, 侵入性太强了, 业务逻辑一复杂就完蛋
登陆就算成功了还是在登陆界面, 所以要在登陆前面完成一个校验信息, 如果已登陆显示用户信息界面
P29 实现登陆校验拦截器
- 拦截器
LoginInterceptor
要实现接口HandlerInterceptor
- 拦截器
LoginInterceptor
要使用需要在MVC框架的配置中配置(定义一个MVC的配置类) ctrl + alt + z
自动补全变量类型和变量名
P30 session共享的问题
- session共享问题, 多台tomcat并不共享session的存储空间, 当请求切换到不同tomcat服务时导致数据丢失的问题, 尽管session提供了拷贝功能, 但是浪费内存并且有拷贝时间, 会出现不一致的问题, 所以需要redis来实现以下特点:
- 数据共享(tomcat访问的redis)
- 内存存储
- key, value存储
P31 Redis代替session的业务流程
P31 基于Redis实现短信登陆
注意设置有效期
StringRedisTemplate是继承RedisTemplate了, 只是设置了一些序列化和反序列化的方式是String.
Java
util类
和hutool类
中有UUID
类, 可以生成UUID
应该在登陆拦截的时候, 通过的时候要更新token有效期为新的30分钟
StrUtil工具类可以判空
StrUtil.isBlack(token)
.var可以补全
StringRedisTemplate在
LoginInterceptor
的类中使用, 但后面这个类是自己管理的对象, 所以无法使用注入, 所以需要在这个拦截器类的使用的地方进行StringRedisTemplate
的注入(也就是MVC框架那里使用了这个拦截器, 在MVC框架上面定义一个StrintRedisTemplate
传入进来.由于DTO中有非String , 而
StringRedisTemplate
只能接收String
类型的对象, 所以需要转换Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(), CopyOptions.create() .setIgnoreNullValue(true) .setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));
token取的时候用
requst.getHeader("authortization")
取, 当然对应的在前端要写拦截器在每个请求的头上加入key=authortization
的头.
P32 解决登陆状态刷新的问题
如果用一个拦截器对需要登陆的路径来刷新touken, 那么不需要登陆的路径的访问就不会刷新token, 所以需要用一个对于所有路径的过滤器来刷新token(只刷新不拦截, 也就是都返回true, 让下一层再拦截), 然后再在第二个拦截器进行登陆的拦截
拦截器.order可以设置优先级
商户查询缓存(3h8min)(230427,230429,230505)
P35 什么是缓存
缓存的优点:
优点: 降低后端负载, 提高读写效率, 降低响应时间
缺点: 数据一致性成本, 代码维护成本, 还有运维成本
P36 添加商户缓存
根据id查询商户缓存的流程:
- redis的存储用
StringRedisTemplate
类去操作, 比如StringRedisTemplate.opsForValue.get(key)
- 数据库的操作直接用函数, 比如
save()
,getById()
- 将对象和字符串互转为String的类是
JSONUTIL
,JSONUtil.toBean(shopJson, Shop.class)
JSONUtil.toJsonStr(shop)
缓存更新策略
解决一致性问题
低一致性: 内存淘汰. 高一致性: 主动更新.
主动更新策略:
- ==缓存调用者在更新数据库时同时更新缓存(首选)==
- 调用者直接调用封装好的缓存数据库一致性服务
- 调用者只操纵缓存, 其他线程异步定时或按频率将缓存数据持久化到数据库, 但是有过时数据和不一致的情况
操作数据库和缓存需要考虑:
更新数据库后删除缓存还是更新缓存?
==删除缓存==保证缓存与数据库的操作同时成功或失败
单体系统使用事务, 分布式系统使用TCC等分布式事务方案先操作缓存还是先操作数据库?
==先更新数据库,再删缓存==
实现商铺缓存与数据库的双写一致性
- 这里我的前端没有显示图片, 查看后端日志, 可以看出查询到了10条数据, 但是分页好像没分队, 在
ShopServiceImpl=>queryShopByType()
方法中
缓存穿透的解决思路
1.
缓存总结
优惠券秒杀(2h13min)(230505, 230508)
一人一单
应该先事务结束,再释放锁
分布式锁
高级篇(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
欢迎在评论区中进行批评指正,转载请注明来源,如涉及侵权,请联系作者删除。