1 数据类型&编码类型
1.1 字符串 string
1.1.1 编码类型
- int:8字节的长整型
- embstr:存储小于等于39字节的字符串,只申请一次内存
- raw:存储大于39字节的字符串,申请两次内存
1.1.2 适用场景
- 缓存,热点数据
- 分布式session,分布式锁
- 计数,限流
1.1.3 SDS(Simple Dynamic String)
- 自动扩容避免内存溢出,空间预分配,惰性空间释放
- 定义len属性,查询数组长度时间复杂度O(1),根据len来判断结束
1.2 哈希 hash
1.2.1 编码类型
- ziplist:元素个数小于512,且所有值都小于64字节
- hashtable:与ziplist相反
1.2.2 适用场景
- 购物车列表
1.3 列表 list
1.3.1 编码类型
- ziplist:元素个数小于512,且所有值都小于64字节
- linkedlist:与ziplist相反
1.3.2 适用场景
- 时间线
- 队列
- 栈
1.4 集合 set
1.4.1 编码类型
- intset:元素个数小于512,且所有值都是整数
- hashtable:与intset相反
1.4.2 适用场景
- 标签
1.5 有序集合 zset
1.5.1 编码类型
- ziplist:元素个数小于128,且所有值都小于64字节
- skiplist:与ziplist相反
1.5.2 适用场景
- 商品的评价标签
1.6 扩展数据类型
1.6.1 BitMaps
- Bitmaps本身不是一种数据类型, 实际上它就是字符串(key-value),但是它可以对字符串的位进行操作
- 可以把Bitmaps想象成一个以位为单位的数组,数组的每个单元只能存储0和1,数组的下标在Bitmaps中叫做偏移量
1.6.2 HyperLogLog
- 用来做基数统计的算法,优点是在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的
1.6.3 GEO
- 元素的2维坐标/地图上的经纬度
- 可以进行经纬度设置/查询,范围查询,距离查询,经纬度Hash等操作
2 持久化
2.1 RDB
将某一时刻的数据持久化到磁盘中,是一种快照式的持久化方法
创建(fork)一个子进程,先将数据写入到一个临时文件中,写完后用这个临时文件替换上次持久化好的文件
- 优点:恢复数据更高效
- 缺点:无法保证数据完整性
2.2 AOF
依次记录执行过指令
- 优点:数据完整性更可靠
- 缺点:文件大、恢复慢
3 删除策略
3.1 定期删除
每隔一段时间就扫描一定数据(设置了过期时间key),并清除其中已过期的key,如果有多于25%的key过期,重复该步骤
3.2 惰性删除
访问key时判断是否过期,如果过期才删除
4 淘汰策略&淘汰算法
4.1 淘汰策略
4.1.1 noeviction(默认策略)
不会删除任何数据
拒绝所有写入操作并返回客户端错误消息(error)OOM command not allowed when used memory
此时只响应删和读操作
4.1.2 volatile-ttl
在设置了过期时间的key中,淘汰过期时间剩余最短的
4.1.3 allkeys-lru
从所有 key 中使用 LRU 算法进行淘汰
4.1.4 volatile-lru
从设置了过期时间的 key 中使用 LRU 算法进行淘汰
4.1.5 allkeys-lfu
从所有 key 中使用 LFU 算法进行淘汰
4.1.6 volatile-lfu
从设置了过期时间的 key 中使用 LFU 算法进行淘汰
4.1.7 allkeys-random
从所有 key 中随机淘汰数据
4.1.8 volatile-random
从设置了过期时间的 key 中随机淘汰数据
4.2 淘汰算法
4.2.1 LRU 算法(时间算法)
每个数据对象中有 lru 字段,表示该数据最近一次访问的时间戳
- 随机 选出 N 个数据,把它们作为一个候选集合
- 比较这 N 个数据的 lru 字段,把 lru 字段值最小的数据淘汰
缺陷:只关心最近依次访问时间,容易将热点数据淘汰
4.2.2 LFU 算法(次数算法)
4.0 版本新增,为每个数据增加了个计数器来统计这个数据的访问次数
- 根据数据的访问次数进行筛选,把访问次数最低的数据淘汰
- 如果两个数据的访问次数相同,再比较两个数据的访问时效性,把上一次访问时间更久的数据淘汰
5 集群
5.1 主从
5.1.1 原理
- 新建立一个从服务器时,将向主服务器发送SYNC命令
- 主服务器接收到SYNC命令后会进行一次BGSAVE命令
在执行期间,会将所有命令写入缓冲区中,当BGSAVE命令执行完毕之后会将生成的RDB文件发送给从服务器- 从服务器接收到RDB文件后将数据加载到内存中
- 此后每次主服务执行命令都会同步给从服务器
以Redis命令协议的格式将缓冲区的命令发送给从服务器
- 一个主服务器可以拥有多个从服务器,而从服务器也可拥有从服务器
- 即使有多个从服务器向主服务器发送SYNC命令,主服务器也只会执行一个BGSAVE命令
- 复制功能不会阻塞主服务器,即使有一个或多个同步请求,主服务器依然能处理命令请求
- 当配置了主从复制模式时需要开启主服务器的持久化功能
如果将主服务器的持久化功能关闭,主服务器一旦重启,所有从服务器的数据将会丢失,即使配置了Sentinel模式,如果主服务器自动拉起进程比较快,以至于在Sentinel模式下还未选举出新的主服务器,主服务的启动也会造成子服务器的数据丢失
5.1.2 缺陷
主服务器挂掉后,需要人为操作,才能将从服务器切换成主服务器
5.2 哨兵(sentinel)
5.2.1 原理
基于主从模式通过哨兵实现故障自动切换
- 哨兵是一个分布式系统,用于对主从结构中的每台服务器进行监控,当出现故障时通过投票机制选择新的主服务器,并将所有从服务器连接到新的主服务器
- 整个运行哨兵的集群的数量不得少于3个节点
5.2.2 结构
哨兵结构由两部分组成,哨兵节点和数据节点
- 哨兵节点:哨兵系统由一个或多个哨兵节点组成,哨兵节点是特殊的redis节点,不存储数据
- 数据节点:主节点和从节点都是数据节点
5.2.3 作用
- 监控:哨兵会不断地检查主节点和从节点是否运作正常。
- 自动故障转移:当主节点不能正常工作时,哨兵会开始自动故障转移操作,它会将失效主节点的其中一个从节点升级为新的主节点,并让其他从节点改为复制新的主节点。
- 通知:哨兵可以将故障转移的结果发送给客户端。
5.2.3 故障转移
- 发现故障
每个哨兵节点每隔1秒会向主节点、从节点及其它哨兵节点发送一次ping命令做一次心跳检测
如果节点在一定时间范围内不回复或者是回复一个错误消息,那么这个哨兵就会认为这个节点主观下线
如果这个节点是主节点且当超过半数哨兵节点认为该主节点主观下线,则达成客观下线,触发后续步骤
普通节点没有客观下线的概念- 哨兵选举
通过Raft算法(选举算法)实现选举机制共同选举出一个哨兵节点为 leader ,来负责处理主节点的故障转移和通知。所以整个运行哨兵的集群的数量不得少于3个节点- 主从切换
将某一个从节点升级为新的主节点,让其它从节点指向新的主节点
若原主节点恢复也变成从节点,并指向新的主节点
通知客户端主节点己经更换
5.2.4 主节点选举
- 过滤掉不健康的(已下线的),没有回复哨兵 ping 响应的从节点
- 选择配置文件中从节点优先级配置最高的(replica-priority,默认值为100)
- 选择复制偏移量最大,也就是复制最完整的从节点
5.2.5 缺陷
需要额外维护一套哨兵系统
5.3 Cluster
5.3.1 原理
- 3.0开始提供的分布式存储方案
- 集群由多个节点组成,数据分布在这些节点中
- 集群中的节点分为主节点和从节点
只有主节点负责读写请求和集群信息的维护,从节点只进行主节点数据和状态信息的复制
5.3.1.1 分布式存储原理
集群有16384个哈希槽(编号0-16383),集群的每个节点负责一部分哈希槽
KEY经过CRC16算法后对16384取余,根据结果值确定对应的哈希槽,去对应的节点进行存取操作
每个节点都保存有其它节点的哈希槽信息,所以程序只需要连接集群中任意一节点即可
5.3.1.2 高可用原理
一个主节点对应一个或者多个从节点,当主节点宕机的时候,就会启用从节点
如果半数以上的主节点与 A 通信超时,那么就认为主节点 A 宕机了
如果主节点 A 和它的从节点都宕机了,那么该集群就无法再提供服务
6 缓存问题
6.1 缓存穿透
6.1.1 原理
key 对应的数据在 redis 中并不存在,每次针对此 key 的请求从缓存获取不到,请求转发到数据库,访问量大了可能压垮数据库
例:用一个不存在的用户 id 获取用户信息
6.1.2 现象
- 应用服务器压力变大
- redis命中率变低,从而会不断查询数据库
6.1.3 解决
- 对空值缓存
如果一个查询返回的数据为空(不管是数据是否不存在),我们仍然把这个空结果(null)进行缓存,这样可以缓解数据库的访问压力,然后设置空结果的过期时间会很短,最长不超过五分钟。(只能作为简单的应急方案)- 设置可访问的名单(白名单)
使用 bitmaps 类型定义一个可以访问的名单,名单 id 作为 bitmaps 的偏移量,每次访问和 bitmap 里面的 id 进行比较,如果访问 id 不在 bitmaps 里面,进行拦截,不允许访问。- 布隆过滤器
将所有可能存在的数据哈希到一个足够大的 bitmaps 中,一个一定不存在的数据会被这个bitmaps 拦截掉,从而避免了对底层存储系统的查询压力- 进行实时监控
当发现 Redis 的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制服务
6.2 缓存击穿
6.2.1 原理
key 对应的数据存在,但在 redis 中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端 DB 加载数据并回设到缓存,这个时候大量并发的请求可能会瞬间把后端 DB 压垮
例:redis某个热门数据过期,大量的合理数据请求到达数据库
6.2.2 现象
- 数据库的访问压力瞬间激增
- redis正常运行
- redis没有出现大量的过期现象(过期后无法访问,若未命中,则需要访问数据库)
6.2.3 解决
- 预先设置热门数据
在 redis 高峰访问之前,把一些热门数据提前存入到redis 里面,加大这些热门数据 key 的时长- 实时调整
现场监控哪些数据热门,实时调整 key 的过期时长- 使用锁
(1)在缓存失效的时候,不是立即去 load DB
(2)先使用缓存工具的某些带成功操作返回值的操作(比如 Redis 的 SETNX)去 set 一个 mutex key
(3)当操作返回成功时,进行 load db 的操作,并回设缓存,最后删除 mutex key
(4)当操作返回失败时,证明有线程在 load db,当前线程睡眠一段时间后再重试整个 get 缓存的方法
6.3 缓存雪崩
6.3.1 原理
类似缓存击穿,不同之处在于缓存击穿是某一个热门 key 失效导致redis无法命中,而缓存雪崩则是针对很多 key 失效导致redis无法命中,从而使数据库压力激增
6.3.2 现象
- 数据库压力变大,服务器崩溃
6.3.3 解决
- 构建多级缓存架构
nginx 缓存 + redis 缓存 +其他缓存(ehcache 等),程序设计较为复杂- 使用锁或队列
用加锁或者队列的方式来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。效率低,不适用高并发情况- 设置过期标志更新缓存
记录缓存数据是否过期(设置提前量),如果过期会触发通知另外的线程在后台去更新实际 key 的缓存- 将缓存失效时间分散开
比如我们可以在原有的失效时间基础上增加一个随机值,比如 1-5 分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件