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