Redis复盘
为了更深了解Redis,重新学习一次黑马点评,从原理到实现。
数据类型
- 一种非关系型数据库(NoSQL)、里面都是键值对(key-value)的存储形式,不过value可以使用一个json
- 是一种
非结构化、存储在内存(因此速度快)的数据库 - 单线程,每个命令具备原子性
- 低延迟,速度快(基于内存,IO多路复用,良好的编码)
- 支持数据持久化
- 支持主从集群
- 基本的数据结构类型有五种:String、Hash、List、Set、ZSet(SortedSet)
通用命令
- keys:查找关键词(不要在生产环境用这个,redis是单线程的,会阻塞其他任务) keys *(*是通配符,查所有)
- del:删除一个key
- exists:判断是否存在 (语法:exists key)
- expire:给key设置有效期,有效期到期后这个key自动删除(因为redis是使用内存的,不能无限制往里面加)
- ttl:查看一个key的生存时间
String
- 底层是使用字节存储的,不同的数据类型采用不同的编码,如果存数字,就用字节,如果存字符串,就存编码,甚至可以存图,最大不能存超过512M
Key层级
- 可以使用set heima:user:id
Hash
- Hash是一个类似于Map<String,Map<String,String>>的结构,其中有开始的关键词key作为键,然后有一个filed和value一起作为值,这样就可以让一个key有多个value
List
- 底层是一个链表,可以LPUSH和RPUSH,想象逐个插入,比如LPUSH 1,2,3,4,5,出来的结果应该是5 4 3 2 1,类似于头插法,RPUSH同理
- 如何用List模拟一个栈?LPUSH&&LPOP
- 如何用List模拟一个队列?LPUSH&&RPOP || RPUSH&&LPOP
- 如何用List模拟一个阻塞队列?使用BLPOP/BRPOP替换上面的队列 BLPOP和BRPOP在没有元素的时候等待指定时间,而不是直接返回null
Set
- set是集合,去重,唯一;可以用来求交集、并集、差集,来判断是不是好友,还能查看一个好友是不是自己的好友(sismember)
SortedSet
- zset中每一个元素都带有一个score属性,可以基于score属性对元素排序,底层是一个跳表(SkipList)加哈希表,具备以下特征,经常用来做排行榜。
- 可排序
- 元素不重复
- 查询速度快
可以使用incrby,range,rank查询排名
黑马点评
登录
- 使用session登录,session可以使用
session.setAttribute(parama)很方便 - 短信验证功能:使用session存储code
- 实现收发功能:调用sendCode,去IUserService这个接口里实现类
- 使用Mybatis-plus的save去生成未注册的用户
- 使用类为Object的cacheCode与DTO里的Code进行比较,相同的话
- 登陆验证功能使用ThreadLocal作为拦截器对象,这样我们客户端发送请求,先在拦截器里完成鉴权,然后再将请求发送给不同的接口,去实现一个Interceptor类,然后实现HandlerInterceptor接口
- 优化,登录的时候我们直接传了User对象,这个对象里面有很多敏感信息,我们就使用BeanUtil里的copyProperties拷贝给UserDTO,这样就可以把信息传递给DTO,这样很多函数传参的时候也只传给DTO版本而不是完完整整的User版本
- 基于Redis实现共享session登录:因为后期如果要部署到集群上,nginx会把请求转发到不同的服务器上,这些服务器之间不共享session,这时候就需要缓存,redis是一种key-value的数据库,我们每时每刻会发送许多的请求到内存,要保证唯一性,那就使用key token+value:code来实现存储
- 转换变量的时候使用BeanUtil工具包来,想复制就是copyProperties,想要转换成map就是beanToMap
- 本来使用session的都是用stringRedisTemplate去操作,这样又可以实现什么时候token失效,然后如果续期的话,就去拦截器里续期!
- 获得请求头就是用request.getHeader()
- DEBUG了十万年,传错了tokenKey
- 双拦截器的好处:单拦截器的时候只拦截了登录请求,但是如果用户一直没去点登录请求,redis就不会去刷新有效期,所以我们再设置一个拦截一切路径的拦截器,这样子就可以每点击或者刷新一次,都能重新更新TTL
ThreadLocal
- ThreadLocal 不是“线程”,而是一个 线程本地变量工具类。它的作用是:
- 每个线程都有一个独立的副本,互不影响。
- 用来保存某些需要在同一个线程内共享,但又不能被其他线程访问的数据。
- 在我们点击一些商品链接的时候,如果我们没有登陆,是不是就被拦截了,有一些商品信息能直接查看详情,就说明放行了
缓存
- 缓存的逻辑是这样的:一个查询过来的时候,先查缓存,如果缓存里面没有再去查数据库,如果数据库里也没有,就返回错误;如果数据库里有,就写入缓存(opsForValue().set(key,value)),然后返回商铺信息就可以;redis里面的数据一般都是json格式,可以使用JSONUtil.toBean反序列化为shop对象;也可以反过来使用StrUtil.toJsonStr序列化成json格式。
缓存更新策略
- 对于低一致性需求:使用Redis自带的内存淘汰机制
- 对于高一致性需求:主动更新并以超时剔除作为兜底方案
- 读操作
- 缓存命中直接返回
- 缓存未命中则查询数据库,并写入缓存,设定超时时间
- 写操作
- 先写数据库,再删除缓存
- 要确保数据与缓存操作的原子性
- 主动更新(Cache Aside)
- 先删除缓存,再操作数据库
- 先操作数据库,再删除缓存 这个好一点
缓存三大问题
- 雪崩、击穿、穿透
- 击穿有两种解决方式:逻辑过期、互斥锁
- 雪崩加上随机时间TTL
- 穿透缓存空值/布隆过滤器
- 互斥锁:可以使用setnx来实现,setnx当且仅在key不存在的时候才能成功创建,如果key存在的话是不能更改值的,所以有多个线程来访问的时候,只有第一个线程能够修改值
- QPS(query per seconds)可以用jmeter来查
优惠券
- 优惠券秒杀,每一张优惠券需要有一个唯一的全局id,但是这个id不能是自增的太明显的,所以必须要进行一些拼凑 1 + 31 + 32;首位是0,中间31位是时间,可以用69年,最后32就是每秒生成2的32次方个优惠券
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 Explainfuture's Blog!
