笔记按照教学视频讲解的顺序,并附上内容所在的视频分p位置
目录
-
- P1 课程介绍
- P3 短信登录-基于session实现短信登录的流程
- P6 短信登录-实现登录校验拦截器
- P7 短信登录-隐藏用户敏感信息
- P8 短信登录-session共享的问题分析
- P9 短信登录-Redis代替session的业务流程
- P10 短信登录-基于Redis实现短信登录
- P11 短信登录-解决状态登录刷新的问题
- P12 什么是缓存
- P13 添加商户缓存
- P14 缓存练习题分析
- P15 缓存更新策略
- P16 实现商铺缓存与数据库的双写一致
- P17 缓存穿透的解决思路
- P18 编码解决商铺查询的缓存穿透问题
- P19 缓存雪崩问题及解决思路
- P20 缓存击穿问题及解决方案
- P21 利用互斥锁解决缓存击穿问题
- P22 利用逻辑过期解决缓存击穿问题
- P23 封装Redis工具类
- P24 缓存总结
- -----分割线----以上是基础课程以下是高级实战课程--------
- P25 优惠券秒杀-全局唯一ID
- P26 优惠券秒杀-Redis实现全局唯一ID
- P27 优惠券秒杀-添加优惠券
- P28 优惠券秒杀-实现秒杀下单
- P29 优惠券秒杀-库存超卖问题分析
- P30 优惠券秒杀-乐观锁解决超卖
- P31 优惠券秒杀-实现一人一单功能
- P32 优惠券秒杀-集群下的线程并发安全问题
- P33 分布式锁-基本原理和不同实现方式对比
- P34 分布式锁-Redis的分布式锁实现思路
- P35 分布式锁-实现Redis分布式锁版本1
- P36 分布式锁-Redis分布式锁误删问题
- P37 分布式锁-解决Redis分布式锁误删问题
- P38 分布式锁-分布式锁的原子性问题
- P39 分布式锁-Lua脚本解决多条命令原子性问题
- P40 分布式锁-Java调用Lua脚本改造分布式锁
- P41 分布式锁-Redisson功能介绍
- P42 分布式锁-Redisson快速入门
- P43 分布式锁-Redisson的可重入锁原理
- P44 分布式锁-Redisson的锁重试和WatchDog机制
- P45 分布式锁-Redisson的multiLock原理
- P46 秒杀优化-异步秒杀思路
- P47 秒杀优化-基于Redis完成秒杀资格判断
- P48 秒杀优化-基于阻塞队列实现秒杀异步下单
- P49 Redis消息队列-认识消息队列
- P50 Redis消息队列-基于List实现消息队列
- P51 Redis消息队列-PubSub实现消息队列
- P52 Redis消息队列-Stream的单消费模式
- P53 Redis消息队列-Stream的消费者组模式
- P54 Redis消息队列-基于Stream消息队列实现异步秒杀
- P55 达人探店-发布探店笔记
- P56 达人探店-查看探店笔记
- P55 达人探店-点赞功能
- P58 达人探店-点赞排行榜
- P59 好友关注-关注和取关
- P60 好友关注-共同关注
- P61 好友关注-Feed流实现方案分析
- P62 好友关注-推送到粉丝收件箱
- P63 好友关注-滚动分页查询收件箱的思路
- P64 好友关注-实现滚动分页查询
- P65 附近商铺-GEO数据结构的基本用法
- P66 附近商铺-导入店铺数据到GEO
- P67 附近商铺-实现附近商户功能
- P68 用户签到-BitMap功能演示
- P69 用户签到-实现签到功能
- P70 用户签到-统计连续签到
- P71 UV统计-HyperLogLog的用法
- P72 UV统计-测试百万数据的统计
P1 课程介绍
项目是前后端分离,不是微服务
P3 短信登录-基于session实现短信登录的流程
登录校验功能
在ThreadLocal中存储用户信息
MyBatisPlus支持对单表的CRUD
例如不用自己去写一条查询语句
原理是在结果的类中使用@TableName注解已经配置好CRUD的表名称了
P6 短信登录-实现登录校验拦截器
主要讲在controller之前加一个拦截器用于一个预先的处理,通过session获取用户信息
P7 短信登录-隐藏用户敏感信息
这节主要讲User和UserDTO的区别,就是说不要把不用的信息(或敏感的信息)返回给前端网页,这里要进行一个类型转换
这两个对象的属性信息拷贝可以用hutool工具包的BeanUtil.copyProperties()方法
P8 短信登录-session共享的问题分析
P9 短信登录-Redis代替session的业务流程
使用redis的时候这里的key不能像使用session一样都使用code,因为每个session都有一个sessionID可以保证不冲突,
但这里是redis集群,它的key必须唯一,才能保证获取到正确的值
所以可以使用手机号作为key(手机号不安全有泄露风险),但推荐使用一个随机字符串作为key
在redis中存储一个对象可以使用hash结构(相比string结构更好),
hash结构可以将对象中的每个字段独立存储,可以针对单个字段做CRUD,而如果使用string的话只能整体删掉重新赋值
P10 短信登录-基于Redis实现短信登录
在redis中存储用户的信息的时候(通过token作为key)会设置一个过期时间(比如30分钟),需求是用户在不操作30分钟后过期,而不是一直操作也会过期
方法是,可以在拦截器中刷新key的时间
注意StringRedisTemplate和RedisTemplate的区别,
前者要求存的数据只能是字符串,并且这两个存的数据不能相互查到
通过stringRedisTemplate存储String字符串数据、获取数据
stringRedisTemplate.opsForValue().set()
stringRedisTemplate.opsForValue().get()
通过stringRedisTemplate存储Hash数据、获取数据、设置过期时间
stringRedisTemplate.opsForHash().putAll()
tringRedisTemplate.opsForHash().entries()
stringRedisTemplate.expire()
使用hutool的BeanUtil.beanToMap方法将Bean转为Map
P11 短信登录-解决状态登录刷新的问题
问题:在拦截器中刷新用户的key时间只能针对需要登录的网页(因为只拦截需要登录的网页),我们希望用户在访问不需要的登录的网页的时候也起到一个刷新用户key的效果
解决:在拦截器前再设置一个拦截器,第一个拦截器拦截一切路径并刷新key,第二个拦截器才做真正的拦截
P12 什么是缓存
缓存可以减少后端负载,减少相应时间,但会带来数据一致性成本,也会引入缓存穿透击穿等问题
P13 添加商户缓存
mybatisplus也提供业务层的接口,可以直接查询
在客户端与数据库之间添加缓存
P14 缓存练习题分析
用上述类似的方法给商户类型列表添加缓存,其中的逻辑是相同的,不同点在于是对一个List进行Redis的String的存储和读取
利用hutool的JSONUtil工具包调用toList方法进行字符串转对象数组
P15 缓存更新策略
通常来说更常用的是第一种策略,由自己在更新数据库时更新缓存
第二种策略整合的服务非常复杂,第三种策略需要考虑缓存宕机数据丢失的问题
先操作缓存还是先操作数据库,都可能导致程安全问题,避免多线程下缓存数据库数据不一致
但先操作数据库再删除缓存的方式更好,发生线程安全问题概率较低
P16 实现商铺缓存与数据库的双写一致
当要更新数据库的时候,先更新数据库,再删除缓存(为了防止删除失败数据库又更新了,加一个Transactional注解来回滚)
P17 缓存穿透的解决思路
缓存穿透是指用户请求一个并不存在的数据,故意不命中缓存来消耗数据库的资源
第一种解决方法:可以将这些空对象缓存起来,避免相同的空请求重复地打到数据库
缓存空的对象只能解决重复请求同一个空对象的情况,如果每次请求的都不一样会占用大量的缓存空间(比如随机生成一个请求大概率是不存在的),
此时可以用第二种方法,先用过滤器判断再去查redis缓存
第二种方法:在缓存之前加一个布隆过滤器,这个过滤器可以判断请求的东西在数据库中是否存在(利用bit数组实现)
由于布隆过滤器的误判性,它只能确定请求东西在数据库中一定不存在(此时直接拒绝),或者在数据库中大概率存在(先放行但仍小概率不存在),
原理是一个东西如果加入了过滤器中,那么它一定会被过滤到,也有可能没加入过滤器的东西也被过滤了
P18 编码解决商铺查询的缓存穿透问题
采用缓存空对象的方法解决的逻辑流程
除了以上两种解决方法之外,也有一些其他的手段来解决穿透
比如增加id的复杂的避免有规律的自然数,可以设置id的长度等
然后根据自己制定的id规则来做格式校验,可以提前阻止一部分请求
或者要求查询的用户具有某些权限,比如要先登录等,不同权限的用户查询的频率有不同限制等
P19 缓存雪崩问题及解决思路
给TTL过期时间添加一个随机值,避免同时失效
采用集群来提高可用性
降级限流意为给某些业务降低服务质量,返回拒绝服务
给请求过程的每个环节添加缓存,可以是nginx、redis、JVM、数据库等
P20 缓存击穿问题及解决方案
两种解决方案的时序图
互斥锁在重建缓存时其他线程只能等待,此时服务不可用,如果重建缓存的业务中涉及到的缓存也正在发生缓存重建,可能就会发生死锁。
逻辑过期在缓存中加了一个自定义字段表示数据的过期时间(不是真正的过期),实际上设置它永不过期,如果查询到数据逻辑过期了,那么就进行重建,重建期间返回旧的数据,保证了服务的可用性。
这两种方法如何选择,其实是在一致性和可用性直接选择,
分布式系统中也面临一致性和可用性之间的抉择问题,在这里互斥锁选择了一致性,逻辑过期选择了可用性
P21 利用互斥锁解决缓存击穿问题
在某个缓存失效的情况下,有多个线程(比如1000个)发起查询请求,只有第1个线程去重建缓存(去查数据库,只查1次),其它线程等待
互斥锁可以用redis中的setnx锁来实现,这个锁的数据只能在创建的时候赋值(赋给它1),值不能被修改,只能被删除key
线程等待使用Thread.sleep(),这个方法需要解决异常,往上抛
利用jmeter压力测试工具,发起1000个http请求,
可以在结果中看到所有请求都成功得到了是数据,也可以在也可以在汇总报告中看到吞吐量
P22 利用逻辑过期解决缓存击穿问题
首先考虑逻辑过期这个字段设置在哪里的问题,因为存储的对象本身是不带这个字段的,可以在对象中添加一个字段,但这会修改原来的代码,不推荐
还有一种做法是使用继承,新定义一个带逻辑过期字段的类,它继承原来的那个类。
推荐的做法是定义一个对象,有两个属性,一个属性存过期时间,另一个属性存Object字段,这样所有的对象都可以进行存储。
通过java.time.LocalDateTime获取时间
LocalDateTime.now()得到当前时间,LocalDateTime.now().plusSeconds(秒数)得到当前时间之后一刻时间
测试步骤:
1、往缓存里面存数据(可以运行单元测试方法),缓存里面一定要有一个数据,不管是新的还是旧的,否则被认为要查询的东西不存在
2、在数据库中修改数据,此时数据库的数据是最新的,缓存里面的数据是旧的
3、通过jmeter在1秒内发起100个请求,可以看到前面部分的请求获取的是旧数据(缓存里面旧的),后面部分的请求得到的是新数据(缓存重建之后的新的)
4、这个过程中只发生了1次缓存重建(比如数据库查询),表明它是线程安全的
P23 封装Redis工具类
将这些方法编写到一个工具类中,难点在于:1,类型不确定用泛型,2,方法不确定用函数式编程。这两者都由调用者来确定。
返回的类型不确定,id的类型不确定,利用泛型在类型不确定的情况下指定数据类型,
由使用这个方法的调用者告诉我们类型是什么,例如这里的商品类型Shop
由于查询数据库的方式是不确定的,让方法的调用者传一个参数告诉方法如何查询数据库,需要一个参数并带有返回值,所以进行函数式编程,
用java里的Function来实现,这里传入了一个函数,命名为dbFallback,参数为ID和返回类型R都是泛型,调用apply来执行传入的函数
这是调用方法的传入参数,它说明了返回一个Shop商铺类型,查询数据库的方式为getById(),
id2 -> getById(id2) 由于id2相同可写为 this::getById
P24 缓存总结