Redis知识图谱
2022-06-24 16:22:22 0 举报
AI智能生成
redis的知识图谱整理,整理不易,如果对您有帮助点赞收藏
作者其他创作
大纲/内容
持久化方式
RDB(Redis DataBase)
redis存储的数据生成快照并存储到磁盘等介质上
单独创建(fork)一个子进程来进行持久化,而主进程是不会进行任何IO操作的,这样就确保了redis极高的性能。
RDB的触发机制
指令手动触发
save,该指令会阻塞当前 Redis 服务器
bgsave,执行该命令时,Redis 会在后台异步执行快照操作,此时 Redis 仍然可以响应客户端请求
redis.conf 配置自动触发
配置文件中配置:save 900 1 # 如果 900s 内,至少有 1 个 key 进行了修改,进行持久化操作
serverCron 自动触发的 RDB 相当于直接调用了 bgsave 指令的流程进行处理
AOF(Append Only File)
redis执行过的所有写指令记录下来
只允许追加不允许改写的文件
配置redis.conf中的appendonly yes就可以打开AOF功能
AOF持久化策略是每秒钟fsync一次
redis提供了redis-check-aof工具,可以用来进行日志修复
redis提供了AOF文件重写(rewrite)机制,即当AOF文件的大小超过所设定的阈值时,redis就会启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集
AOF重写
redis会创建(fork)一个“重写子进程”,这个子进程会首先读取现有的AOF文件,并将其包含的指令进行分析压缩并写入到一个临时文件中。
主工作进程会将新接收到的写指令一边累积到内存缓冲区中,一边继续写入到原有的AOF文件中
当“重写子进程”完成重写工作后,它会给父进程发一个信号,父进程收到信号后就会将内存中缓存的写指令追加到新AOF文件中
当追加结束后,redis就会用新AOF文件来代替旧AOF文件,之后再有新的写指令,就都会追加到新的AOF文件中了。
AOF 持久化功能的实现
命令追加( append ):被执行的写命令追加到 Redis 服务端维护的 AOF 缓冲区末尾
文件写入和同步( write ):AOF 缓冲区根据对应的策略向硬盘进行同步操作。
调用 flushAppendOnlyFile函数
always:一旦AOF缓冲区有数据时,就调用fsync命令强制往AOF文件写数据
everysec(默认):当AOF缓冲区有数据时,就调用write命令往io缓冲区写数据,同时每秒调用一次fsync命令强制将io缓冲区的数据写入到aof文件(redis性能、数据可控性适中)
no:当AOF缓冲区有数据时,就调用wirte命令往io缓冲区写数据
文件重写(rewrite):随着 AOF 文件越来越大,需要定期对 AOF 文件进行重写,达到压缩的目的。
重启加载(load):当 Redis 重启时,可以加载 AOF 文件进行数据恢复。
AOF 数据恢复
创建一个不带网络连接的的伪客户端( fake client)
从 AOF 文件中分析并取出一条写命令
使用伪客户端执行被读出的写命令
一直执行步骤 2 和步骤3,直到 AOF 文件中的所有写命令都被处理完毕为止
RDB-AOF混合持久化
通过aof-use-rdb-preamble配置参数控制,yes则表示开启
持久化扩展
RDB 持久化方式能够在指定的时间间隔内进行快照存储
服务器重启的时候会重新执行这些命令来恢复原始 的数据
当redis重启的时候会优先载入AOF文件来恢复原始的数据
因为RDB更适合用于备份数据库(AOF在不断变化不好备份),快速重启
淘汰/过期策略
过期策略
定期删除
定期遍历设置了过期时间的 key字典来删除到期的 key
默认会每秒进行十次过期扫描,不会遍历过期字典中所有的 key
贪心策略
从过期字典中随机 20 个 key
删除这 20 个 key 中已经过期的 key
如果过期的 key 比率超过 1/4,那就重复步骤 1
惰性删除
客户端访问这个key的时候,redis对key的过期时间进行检查,如果过期了就立即删除
定期删除是集中处理,惰性删除是零散处理
淘汰策略
noeviction:当内存使用超过配置的时候会返回错误,不会驱逐任何键
allkeys-lru:加入键的时候,如果过限,首先通过LRU算法驱逐最近使用较少的键
volatile-lru:加入键的时候如果过限,首先从设置了过期时间的键集合中驱逐最近使用较少的键
allkeys-random:加入键的时候如果过限,从所有key随机删除
volatile-random:加入键的时候如果过限,从过期键的集合中随机驱逐
volatile-ttl:从配置了过期时间的键中驱逐马上就要过期的键
volatile-lfu:从所有配置了过期时间的键中驱逐使用频率最少的键
allkeys-lfu:从所有键中驱逐使用频率最少的键
淘汰算法
LRU:最近最少使用
如果一个数据在最近一段时间没有被用到,那么将来被使用到的可能性也很小,所以就可以被淘汰掉。
全局LRU时钟值的计算
LRU时钟:记录数据每次访问的时间戳
每个KV对中的V,会使用个redisObject结构体保存指向V的指针
redisObject除记录值的指针,还会使用24 bits保存LRU时钟信息,对应的是lru成员变量
键值对LRU时钟值的初始化与更新
LRU时钟值最初是在这KV对被创建createObject函数时,进行初始化
只要一个KV对被访问,其LRU时钟值就会被更新:lookupKey
为什么选择
性能问题,由于近似LRU算法只是最多随机采样N个key并对其进行排序
内存占用问题,redis对内存要求很高,会尽量降低内存使用率,如果是抽样排序可以有效降低内存的占用
实际效果基本相等,如果请求符合长尾法则,那么真实LRU与Redis LRU之间表现基本无差异
在近似情况下提供可自配置的取样率来提升精准度
LFU:最少使用被淘汰
根据key的最近被访问的频率进行淘汰,很少被访问的优先被淘汰,被访问的多的则被留下来
原来的key对象的内部时钟的24位分成两部分,前16位还代表时钟,后8位代表一个计数器
LFU的Counter减少策略和增长策略
降低LFUDecrAndReturn
先从高16位获取最近的降低时间ldt以及低8位的计数器counter值
计算当前时间now与ldt的差值(now-ldt),当ldt大于now时,那说明是过了一个周期,按照65535-ldt+now计算
使用第2步计算的差值除以lfu_decay_time,即LFUTimeElapsed(ldt) / server.lfu_decay_time,已过去n个lfu_decay_time,则将counter减少n。
增长LFULogIncr
获取0-1的随机数r
计算0-1之间的控制因子p
如果r小于p,counter增长1
新生KEY策略
为新生key设置一个初始counter。counter会被初始化为LFU_INIT_VAL,默认5。
lua脚本
优点
减少网络开销。可以将多个请求通过脚本的形式一次发送,减少网络时延
原子操作,在脚本运行过程中无需担心会出现竞态条件,无需使用事务
复用:客户端发送的脚本会永久存在redis中
典型问题
缓存击穿
原因
Key过期时候,大批量请求进来,直接访问数据库
Key被页面置换淘汰
解决思路
维护一个定时任务,将快要过期的key重新设置
可以使用分布式锁,当在缓存中拿不到数据时,使用分布式锁去数据库中拿到数据后,重新设置到缓存
请求到达Redis,发现Redis Key过期,查看有没有锁,没有锁的话回到队列后面排队
设置锁,注意,这儿应该是setnx(),而不是set(),因为可能有其他线程已经设置锁了
获取锁,拿到锁了就去数据库取数据,请求返回后释放锁。
缓存雪崩
缓存中数据大批量到过期时间
解决方案
均匀过期:设置不同的过期时间,让缓存失效的时间点尽量均匀。通常可以为有效期增加随机值或者统一规划有效期。
缓存永不过期:缓存在物理上永远不过期,用一个异步的线程更新缓存
加互斥锁:同一时间只让一个线程构建缓存,其他线程阻塞排队
缓存穿透
不断请求系统中不存在的数据
解决方案
参数合法性检查:接口请求参数的校验。对请求的接口进行鉴权,数据合法性的校验等
返回空对象:当缓存未命中,查询持久层也为空,可以将返回的空对象写到缓存中
布隆过滤器
优点
节省空间:不需要存储数据本身,只需要存储数据对应hash比特位
时间复杂度低:插入和查找的时间复杂度都为O(k),k为哈希函数的个数
缺点
存在假阳性:布隆过滤器判断存在,可能出现元素不在集合中;判断准确率取决于哈希函数的个数
不能删除元素:如果一个元素被删除,但是却不能从布隆过滤器中删除,这也是造成假阳性的原因了
数据结构
基于bitmap和若干个hash算法实现的
布隆过滤器可以用于检索一个元素是否在一个集合中
缓存预热
项目上线时,相关的缓存数据直接加载到缓存系统
缓存预热的操作方法
数据量不大的时候,工程启动的时候进行加载缓存动作
数据量大的时候,设置一个定时任务脚本,进行缓存的刷新
数据量太大的时候,优先保证热点数据进行提前加载到缓存
双写不一致问题
数据库数据和缓存不一致
解决方案
写缓存的时候是先删除缓存数据再更新数据库
延迟双删,就是让所有的写请求,在删除缓存的时候删除两次
读写锁,对读操作加读锁,对写操作加写锁,读锁是非互斥的
概述
Redis(Remote Dictionary Server)是一个使用 C语言编写的,开源的(BSD许可)高性能非关系型(NoSQL)的键值对数据库
优点
读写性能优异
完全基于内存,绝大部分请求是纯粹的内存操作,非常快速
数据结构简单,对数据操作也简单
采用单线程,避免了不必要的上下文切换和竞争条件
使用多路 I/O 复用模型,非阻塞 IO
多路: 指的是多个socket网络连接;
复用: 指的是复用一个线程。
多路复用主要有三种技术:select,poll,epoll。epoll是最新的, 也是目前最好的多路复用技术。
使用底层模型不同,Redis直接自己构建了VM机制
支持数据持久化,支持AOF和RDB两种持久化
支持事务,Redis的所有操作都是原子性的
数据结构丰富
支持主从复制,主机会自动将数据同步到从机,可以进行读写分离
缺点
Redis不具备自动容错和恢复功能
数据库容量受到物理内存的限制,不能用作海量数据的高性能读写
Redis较难支持在线扩容
数据结构
String:字符串
一个key对应一个value
String类型是二进制安全的
Redis的string可以包含任何数据
简单动态字符串(SDS)
List:列表
单键多值
它的底层实际是个快速链表quickList,对两端的操作性能很高
通过索引下标的操作中间的节点性能会较差
Hash:哈希表
一个键值对集合
一个string类型的field和value的映射表,hash特别适合用于存储对象
类似Java里面的Map<String,Object>
数据结构
ziplist:压缩列表,当filed-value长度较短,数量较少的时候使用
小于hash-max-ziplist-entries配置(默认512个)
hashtable:哈希表
Set:无序集合
Set是string类型的无序集合
底层其实是一个value为null的hash表
添加,删除,查找的复杂度都是O(1)
Sorted Set:有序集合
一个没有重复元素的字符串集合
元素是有序的
数据结构
hash:关联元素value和score,保证元素的value唯一性
跳跃表:给value排序,根据score范围获取元素列表
每个节点维持多个指向其他节点的指针,而达到快速访问节点的目的
多层的结构组成,每层是一个有序的链表
最底层(level 1)的链表包含所有的元素
跳跃表的查找次数近似于层数,时间复杂度为O(logn),插入、删除也为 O(logn)
跳跃表是一种随机化的数据结构(通过抛硬币来决定层数)
bitmap:布隆过滤器
实际上它就是字符串(key-value) , 但是它可以对字符串的位进行操作
可以把Bitmaps想象成一个以位为单位的数组, 数组的每个单元只能存储0和1, 数组的下标在Bitmaps中叫做偏移量
GeoHash:坐标
元素的2维坐标,在地图上就是经纬度
提供了经纬度设置,查询,范围查询,距离查询,经纬度Hash等常见操作
HyperLogLog:统计不重复数据
用来做基数统计的算法
在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。
Streams:内存版的kafka
数据结构是radix tree
适用场景
缓存:Key-Value形态的内存数据库
数据共享分布式:分布式锁
全局ID
incrby,利用原子性:incrby userid 1000
计数器:int类型,incr方法
限流:int类型,incr方法
位统计:String类型的bitcount
时间轴(Timeline)
list作为双向链表,不光可以作为队列使用。如果将它用作栈便可以成为一个公用的时间轴
消息队列
Redis 中list的数据结构实现是双向链表,所以可以非常便捷的应用于消息队列
消息的生产者只需要通过lpush将消息放入 list
消费者便可以通过rpop取出该消息,并且可以保证消息的有序性
需要实现带有优先级的消息队列也可以选择sorted set
pub/sub功能也可以用作发布者 / 订阅者模型的消息
抽奖:利用set结构的无序性,通过 Spop 随机获得值
点赞、签到、打卡
排行榜
sorted set(有序set)和一个计算热度的算法便可以轻松打造一个热度排行榜,
zrevrangebyscore可以得到以分数倒序排列的序列
zrank可以得到一个成员在该排行榜的位置(是分数正序排列时的位置,如果要获取倒序排列时的位置需要用zcard-zrank)。
倒排索引:通过set进行建立倒排索引
分布式锁
为什么需要分布式锁
多线程程序时,避免同时操作共享变量产生问题,通常会使用一把锁来互斥,以保证共享变量的正确性
如果是多个进程,需要同时操作一个共享资源,需要引入分布式锁实现
分布式锁基本要求
互斥性:只有一个客户端能够持有锁
不会产生死锁:即使持有锁的客户端崩溃,也能保证后续其他客户端可以获取锁
只有持有这把锁的客户端才能解锁
怎样实现
借助一个外部系统,所有进程都去这个系统上申请加锁
外部系统,必须要实现互斥的能力
选择使用 Redis 或 Zookeeper 来做
Redis分布式锁
使用 SETNX 命令,实现互斥
避免死锁
给这个 key 设置一个过期时间
出现的问题
锁过期:客户端 1 操作共享资源耗时太久,导致锁被自动释放,之后被客户端 2 持有
释放别人的锁:客户端 1 操作共享资源完成后,却又释放了客户端 2 的锁
锁被别人释放
客户端在加锁时,设置一个只有自己知道的唯一标识进去。
在释放锁时,要先判断这把锁是否还归自己持有
加锁流程
加锁:SET lock_key $unique_id EX $expire_time NX
操作共享资源
释放锁:Lua 脚本,先 GET 判断锁是否归属自己,再 DEL 释放锁
锁过期时间不好评估
加锁时,先设置一个过期时间,然后我们开启一个守护线程,定时去检测这个锁的失效时间,如果锁快要过期了,操作共享资源还未完成,那么就自动对锁进行续期,重新设置过期时间
Redisson
使用分布式锁时,它就采用了自动续期的方案来避免锁过期,这个守护线程我们一般也把它叫做看门狗线程。
可重入锁
乐观锁
公平锁
读写锁
Redlock(红锁)
Redis 锁的安全性
主从发生切换
分布式锁失效场景
客户端 1 在主库上执行 SET 命令,加锁成功
此时,主库异常宕机,SET 命令还未同步到从库上(主从复制是异步的)
从库被哨兵提升为新主库,这个锁在新的主库上,丢失了!
当引入 Redis 副本后,分布锁还是可能会受到影响。
为此,Redis 的作者提出一种解决方案,就是我们经常听到的 Redlock(红锁)。
为此,Redis 的作者提出一种解决方案,就是我们经常听到的 Redlock(红锁)。
Redlock
不是部署 Redis Cluster,就是部署 5 个简单的 Redis 实例
加锁流程
客户端先获取 当前时间戳T1
客户端依次向这 5 个 Redis 实例发起加锁请求如果某一个实例加锁失败,就立即向下一个 Redis 实例申请加锁
如果客户端从 >=3 个(大多数)以上 Redis 实例加锁成功,则再次获取 当前时间戳T2,如果 T2 - T1 < 锁的过期时间,此时,认为客户端加锁成功,否则认为加锁失败
加锁成功,去操作共享资源
加锁失败,向 全部节点 发起释放锁请求
说明
1. 客户端在多个 Redis 实例上申请加锁
容错 ,部分实例异常宕机,剩余的实例加锁成功,整个锁服务依旧可用
2. 必须保证大多数节点加锁成功
拜占庭将军 问题
分布式系统,需要考虑异常节点达到多少个,也依旧不会影响整个系统的 正确性。
如果只存在故障节点,只要大多数节点正常,那么整个系统依旧是可以提供正确服务的。
3. 大多数节点加锁的总耗时,要小于锁设置的过期时间
如果大多数节点加锁成功,但如果加锁的累计耗时已经超过了锁的过期时间,那么锁也是无效的
4. 释放锁,要向全部节点发起释放锁请求
需要释放 所有节点 的锁,以保证清理节点上 残留 的锁
存在的问题
进程暂停(GC)导致的安全问题
客户端 1 请求锁定节点 A、B、C、D、E
客户端 1 的拿到锁后,进入 GC(时间比较久)
所有 Redis 节点上的锁都过期了
客户端 2 获取到了 A、B、C、D、E 上的锁
客户端 1 GC 结束,认为成功获取锁
客户端 2 也认为获取到了锁,发生 冲突
GC 可能发生在程序的任意时刻,而且执行时间是不可控的
时钟跳跃对 Redlock的影响
系统时钟做出了危险的假设(假设多个节点机器时钟都是一致的),如果不满足这些假设,锁就会失效。
是否使用
使用分布式锁,在上层完成互斥目的,虽然极端情况下锁会失效,但它可以最大程度把并发请求阻挡在最上层,减轻操作资源层的压力
但对于要求数据绝对正确的业务,在资源层一定要做好兜底
哨兵模式
监控Redis集群中Master状态的工具,是Redis高可用解决方案
哨兵可以监视一个或者多个redis master服务,以及这些master服务的所有从服务
哨兵是一个独立的进程,作为进程,它会独立运行
检测异常(主观下线)
每秒一次的频率向所有节点发送 ping 消息,然后通过接收返回判断该节点是否下线
客观下线
一个主节点判断为主观下线后,哨兵会向其他sentinel 节点进行询问,如果确定下线,sentinel 会将主节点判定为客
观下线,并通过领头 sentinel 节点对主节点执行故障转移;
观下线,并通过领头 sentinel 节点对主节点执行故障转移;
故障转移
主节点被判定为客观下线后,开始领头 sentinel 选举
从从节点中选举一个从节点作为新的主节点
通知其他从节点复制连接新的主节点
若故障主节点重新连接,将作为新的主节点的从节点
缺点
redis 采用异步复制的方式
从节点可能没有收到全部的同步消息,这部分未同步的消息将丢失
sentinel 无法保证消息完全不丢失,但是可以通过配置来尽量保证少丢失
作用
不断地检查master和slave是否正常运行
master存活检测、master与slave运行情况检测
当被监视的服务器出问题的时候,向其它(哨兵间,客户端)发送通知
主机故障时,断开master与slave连接,选区一个slave作为master
0 条评论
下一页