redis
查看这篇文章的思维导图格式请移步:https://www.processon.com/view/link/656ee6bc7fb4bc54ff0f5749
概念
为什么用redis
- 1.读写性能优异,数据类型丰富,所有操作都是原子性的,支持持久化,分布式
使用场景?
- 1.热点数据的缓存,redis内部支持事务,保证数据一致性
- 2.计数器相关问题:高并发的秒杀,限制一个接口请求的次数QPS
- 3.分布式锁:用到setnx命令,如果不存在则成功设置并返回1,用于加锁
数据类型
String字符串
- 可以是数字、字符串、或序列化的对象,
- 应用场景:缓存、计数器、session
- 命令:get、set、del、incr,decr
List列表
- 双向链表实现List,
- 应用场景:可实现栈、队列; 比如微博的timeline
命令
- rpush:将值放到右端,相反为lpush
- rpop:从列表右端弹出值,返回;lpop
- lindex:通过索引获取列表中的元素
Set集合
- String类型的无需集合,元素唯一,集合是通过哈希表实现的
- 命令:sadd:添加,smember:返回集合中所有成员
Hash散列
- String类型的field和value映射表,用于存储对象,
- 命令:hset添加键值对,hget:获取键值对
- 场景:比string更省空间的缓存
Zset有序集合
- 和set一样,不同的是每个元素都会关联double类型的分数,redis通过分数进行从小到大排序
- 命令:zadd、zrange
- 场景:排行榜,按用户关注数、播放量、评分等进行排序
持久化
为什么需要持久化?
- redis是基于内存的数据库,如果服务器宕机,从数据库恢复的话,如果是大数据:1.数据库压力大,2.数据库性能不如redis,响应变慢
RDB持久化
- 快照方式,将当前进程数据生成快照保存到磁盘上,是某一时刻的快照
手动触发:
- save和bgsave命令,save会阻塞进程,线上不建议用;bgsave会fork出子进程完成写入rdb文件操作,阻塞时间短
自动触发
优缺点
- 优点:RDB文件体积小,适用于全量复制;redis加载RDB文件速度也快
- 缺点:实时性不够,不能做到秒级持久化
AOF持久化
- AOF使用写后日志
事务
什么是redis事务?
- 本质是一组命令的集合,一个事务里所有命令会被序列化,按顺序串行化执行队列中的命令,
- redis事务是一次性、顺序性、排他性第执行一个队列中的一系列命令
主从复制
为什么要用主从复制
- 1.数据冗余实现热备份;2.故障恢复,主节点故障,从结点继续提供服务;3.负载均衡,应对读多写少的情况,提供并发量;4.读写分离
原理
全量复制
- 1.主从库建立连接,协商同步过程,
- 2.主库将所有数据同步给从库,发RDB文件
- 3.主库将新收到的写命令,再发给从库
增量复制
缓存问题
缓存穿透
- 缓存没有,数据库也没有
- 原因: 因为缓存里查不到,每次去从层层查不存在的数据,致使数据库压力过大
- 解决方式:加校验,参数校验
缓存击穿
- 缓存中没有,数据库有
- 原因:一般是缓存时间到期,因为缓存没有,去数据库中查,引起数据库压力过大
- 解决:热点参数永不过期;接口限流与熔断;
缓存雪崩
- 缓存数据大批量到期,而查询数据量很大引起宕机
- 缓存击穿是指并发查一条数据,雪崩是大量不同数据过期,引起查询走数据库
- 解决:过期时间随机,热点参数不过期
缓存污染
- 很少被访问的数据留在缓存中,浪费空间
缓存淘汰策略
不淘汰
noeviction
- 默认策略,一旦缓存写满,有新写请求时,redis不处理,直接返回错误
对设置了过期时间的数据进行淘汰
volatile-random
- 设置了过期时间的键值对中,进行随机删除
volatile-ttl
- 进行过期时间的排序,越早过期的数据优先被删除
volatile-lru
- 按最近最少使用原则删除数据,随机取数删除时间戳最小的
vola-lfu
- 按LFU算法筛选过期数据
全部数据淘汰
allkeys-lru
- 筛选数据是全部key
allkeys-random
- 从所有数据中随机删除
allkeys-lfu
- 全部key使用LFU筛选删除
数据库和缓存一致性
为什么有库和缓存一致性问题?
- 在并发环境下,涉及数据更新,先写库,再删缓存;还是先删缓存,再写库;都会有数据不一致的情况
解决方式
Cache aside模式
- 读的时候,缓存没有就读数据库,然后成功取出数据后,放入缓存
- 更新的时候,先更新数据库,成功后,再删除缓存
延迟双删
- 原因:cache aside模式,仍然可能存在不一致的问题
- 先删除缓存,再写数据库,休眠1s后再删缓存
- 问题:增加了写请求的耗时
异步更新缓存
- 基于订阅binlog的同步机制
整体思路:
- 读redis:热点数据都在redis;写MySQL:增删改都是操作MySQL;更新redis数据:binlog+消息队列+增量数据更新到redis
更新过程
全量更新
- 将全部数据一次写入redis
增量更新
- 读取binlog后分析,利用消息队列,推送更新到各平台的redis缓存数据。需要结合canal(阿里的开源框架)对MySQL的binlog进行订阅,然后MQ中间件选择kafka实现推送更新
面试问题
redis为什么这么快
redis有哪些数据类型
- 5种,对应的命令也说说
字符串类型存储的最大容量是?
- 512M
内存淘汰策略?
- 用于内存不足时,怎么处理新写入且申请额外空间的数据
- 8种内存淘汰策略:1.默认报错;2.全局键空间选择性删除;3.设置了过期时间的key选择性删除
过期键的淘汰策略?
- 定时过期、定期过期、惰性过期
redis怎么做分布式锁?
- 因为redis单进程、性能高的特点,故可以作为分布式锁
分布式锁与Java的Lock对比?
- Java的锁只能保证单机有效,所以分布式需要分布式锁
分布式锁的特点
-
- 互斥性:任何时刻,同一数据,只有一台应用可以获得锁;
-
- 高可用性:一部分服务器宕机不影响使用,分布式锁以集群方式部署;
-
- 防止锁超时:客户端没有释放锁,服务端一段时间后自动释放
-
- 独占性:加锁解锁必须由同一台服务器进行,即加解锁只能自己完成,不能让别人解锁了
-
实现
-
setnx+expire命令(错误做法)
- setnx命令在key不存在时才能成功,key存在,返回0。用expire命令设置过期时间。问题:这是分开的两步,不是原子操作,第一步成功,第二步可能失败
- 问题:第一个加锁成功,但是应用挂了,锁就得不到释放,其他线程永远得不到锁
-
- 使用lua脚本将两个命令合成一个原子操作
-
setex命令(setex key seconds value)
- 正确做法,将值value关联到key,并将key的生存时间设为seconds(以秒为单位),setex命令会覆盖旧值
- 就算线程挂了,也会在失效时间到了自动释放
解锁
- 为了防止自己加的锁被其他人释放了。需要在删除之前验证key对应的value是否是当前线程持有,再del key就可以了,因此需要lua脚本封装2步
-
问题
- 上面实现的分布式锁,存在问题:不支持可重入。如果要支持可重入,需要使用redisson
XMind - Trial Version