redis
2021-02-25 18:20:03 3 举报
AI智能生成
redis原理总结
作者其他创作
大纲/内容
数据结构
string
比较小时使用embstr,超出后使用raw
embstr:redis通用对象头和数据存储采用顺序存储,只需要分配一次内存即可
raw为redis对象头有指针指向数据内存块,需要分配两次内存
为何不都使用embstr?
和内存分配方式jemelloc实现有关
底层为字节数组,对编码安全
扩容
当size小于1m时,采用翻倍策略,大于1m时,扩容只会多分配1m
应用
存开关、序列化之后的对象等
存数字,并支持原子增加操作
存二进制数,支持一系列二进制操作(额外有计算1的个数)
list
3.2之前比较小时ziplist,增多时升级为linkedlist
ziplist
由于linkedlist为双向链表,每个节点之间都有两个4字节指针(32位),比较费空间
ziplist采用数组结构,省去了指针空间,并对内部数据使用一定规范进行压缩
由于ziplist没有多余空间,每次增加元素都需要扩容,remelloc(如果旁边有连续空间则不需要copy)
比较适合元素较少时存储
3.2之后,统一使用quicklist
quicklist
结合ziplist和linkedlist
一段使用ziplist,每一段之间用指针连接
应用
简易消息队列
hash
数量较少使用ziplist,达到一定大小后升级为类似Java HashMap结构
数量较少时,ziplist反而会快
扩容
当元素数量等于数组长度时,会触发扩容
遇到bgsave延迟扩容,防止过多页面分离(COW),当元素数量为数组长度5倍时,强制扩容
当元素数量不足数组长度10%,会触发缩容
渐进式rehash
扩容比较耗费资源,为了较少操作卡顿,采用部分迁移,查的时候先查旧后查新
再次触发部分扩容时机
用户每次查询该hash会触发部分迁移
redis会起定时任务去触发扩容
应用
分布式session
缓存整个页面的数据
set
底层使用hash,value为空
当元素都为int时,并且数量较少,结构优化为intset,采用紧凑存储
应用
粉丝列表
zset
底层由skiplist和hash实现
skiplist
skiplist vs 红黑树
skiplist实现更简单,方便调试
skiplist修改结构的时候,添加指针即可,不需要大范围改动,而红黑树可能会触发reblance
ZRANGE和ZREVRANGE使用频率较高,skiplist只需要找到第一个元素,即可根据指针方便查到RANGE
作者发话
空间换时间, 维护多个链接
结构图
分层结构,最底层是全量数据, 每上一层, 一共64层
查询元素从上往下比对,只要score小于查询值,继续流转到下一个节点, 否则进入下一层链表继续查找
修改score时,先剔除节点,再插入
ZREVRANK原理
每个节点存一个span,记录该层据后一个节点相对底层跨越了几个节点,算排名只需要将查询路径的span加起来即可
hash存value和score对应关系
应用
排行榜
滑动窗口
延时队列
geo
可对地理位置进行处理,添加,获取坐标,求hash,算距离,获取坐标附近的元素等
底层使用zset实现
pub/sub
发布订阅,消息队列
支持多个消费者同时消费一组数据
比较鸡肋,消费者必须先在线,如果消费者离线,会丢失消息
stream
5.0新增的持久化发布订阅
狠狠的借鉴了kafka
hyperloglog
海量数据去重计数
精准性换取空间
应用
统计UV
布隆过滤器
持久化
rdb
存内存快照,生成慢,还原快
每次持久化需要fork子进程异步进行,相对消耗较大
使用cow方式节省内存,
即主进程在修改的时候,
直接copy一份内存页修改
即主进程在修改的时候,
直接copy一份内存页修改
aof
append only file, 操作日志
rewrite
文件大小达到一定阈值,就对当前内存有效key重新生成一份指令
刷盘策略
不主动刷盘,由内核决定何时fsync
丢失数据较多
每次fsync
性能损耗较大
每秒fsync
推荐使用,最多丢失1s数据
4.0新增混合模式
aof文件前面是rdb快照,后面是追加的指令,结合两者优点
优化
rdb和aof同时开启时, redis会优先使用aof恢复数据
提高aof的rewrite阀值, 避免rewrite时带来的IO阻塞
不在主节点开启持久化,在从节点开启
内存淘汰策略
volatile-random
从快过期的集中随机删除
volatile-lru
从快过期的集中挑选最近使用频率低的删除
volatile-ttl
从快过期的集中挑选即将过期的删除
volatile-lfu
从快过期的集中挑选使用频率低的删除
allkeys-random
allkeys-lfu
allkeys-lru
拒绝写入
默认
过期策略
定时删除
每隔一段时间抓取一部分key,判断过期时间,
过期则删除,过期比例超过25%则重复执行
过期则删除,过期比例超过25%则重复执行
惰性删除
每次get的时候判断是否过期,过期则删除
IO
redis使用多路复用的epoll
redis6.0读写使用多线程,执行线程依旧是单线程
https://blog.csdn.net/SDDDLLL/article/details/105597205
cpu不是redis的瓶颈
读写基于内存, 多线程会导致复杂的并发事务控制, 得不偿失
读写使用多线程, 主要是为了增强网络IO的读写
管道pipeline
执行多条命令,只需一次网络连接
避免反复的socket通道IO读写时内核态-用户态的频繁切换
由open->write->read->close open->write->read->close
改为open->write->write->read->read->close
改为open->write->write->read->read->close
事务
弱事务
只能保证事务的隔离性,串行化,中间有命令执行错误依然会向下执行,不回滚
multi
开启一个事务
exec
执行从multi到当前位置所有命令
discard
取消执行multi到当前位置所有命令
watch
必须在multi之前执行,如果watch到exec之间数据有变动,exec会执行不成功
通信协议
使用RESP协议
纯文本协议,虽然换行符较多,但是其足够简单,解析也极其容易
集群
主节点可写, 从节点不可以写
主从同步
全量同步
主FORK线程->RDB文件+增删改记录缓冲->从LOAD_RDB文件+执行增删改记录缓冲->数据一致性
增量同步
主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令
落后超过限制后,触发全量同步
无磁盘复制
2.8之后支持,纯内存复制
哨兵sentinel
哨兵需要保证高可用, 需要配置奇数个
主节点挂了后,从节点升级为主节点
哨兵之间采用raft协议保证分布式一致性
在平常,哨兵之间是平等的,只有在检测到redis节点超过一定时间没有回复,才会使用raft协议进行选主
一个哨兵认为server节点下线标记该server为主观下线
超过配置的哨兵数认为server节点下线,则该server节点为客观下线,需要进行主从切换
主从切换对配置文件也会进行修改
raft动图
http://thesecretlivesofdata.com/raft/
选主
1. 每个节点分配100~300ms的倒计时
2. 节点倒计时完成, 将选票给自己, 并请求其他节点投票
3. 其他节点进行投票, 一个任期只能投一次
4. 票数多的节点当选主节点
分布式
client模式/proxy模式
hash
redis服务不知道其他节点的存在,完全由客户端对key进行hash取模后决定放到哪个节点
缺点
一个节点挂掉后,或者增减节点,都会导致原先的key不能hash到对应节点,从而引起缓存雪崩
一致性hash
hash环结构
为了改进hash带来的问题,提出了一致性hash环的概念
hash取模后落在hash环的某个位置,取顺时针方向第一个节点读写
当增减节点的时候,去掉节点的所有key都落到了下一个节点上
虽然可以继续使用,但下一个节点承受了2倍的流量,容易过载导致节点挂掉引起雪崩
虚拟节点
由一个实际节点,虚拟出多个节点分散在hash环上
进一步优化了增减节点时导致的下一个节点过载问题
redis cluster
分为16384个hash槽
采用gossip协议实现无中心化通信
https://www.jianshu.com/p/54eab117e6ae
最少3个master节点(投票需要超过半数,防止脑裂)
集群不可用时机
挂掉的master没有对应slave,即有任一slot不可用,集群不可用
超过半数master都挂掉了,不管有没有slave,都将不可用
常见问题
缓存一致性问题
更新数据的时候,先更新db,再删除缓存,可以很大程度避免缓存不一致
先更新db,后更新缓存,可能造成旧的更新覆盖新的更新导致不一致
缓存穿透
缓存中不存在,查询直接打到了db上,即为缓存穿透,一般情况下,问题不大,读完db写会缓存即可
黑客攻击:故意请求db中也不存在的数据
(我们这种情况一般不会写到缓存)
(我们这种情况一般不会写到缓存)
解决方案
即使db中不存在,也将空值写回缓存,但是时间需要短一些
使用布隆过滤器(不存在肯定不存在,存在可能不存在)
缓存击穿
某个key过期的时候,瞬间有大量请求过来,全部打到了db上,为缓存击穿
解决方案
使用分布式锁,同一时刻只能有一个请求去db拿该数据,其他请求返回错误,或者等待写回缓存
缓存雪崩
同一时间大量key失效,导致大量请求穿透到db,造成雪崩
常见情况
大量key在同一时间失效
解决方案
在设置过期时间的时候加入随机数
redis某个或某几个服务down机
解决方案
需要提前做好数据持久化,重新启动可以快速恢复
提前做好高可用方案,主从,哨兵或者redis cluster等
常见应用
分布式锁
单节点
获取锁
set key value nx ex 5
使用nx确保锁没人用
使用ex是为了防止获取到锁的节点down,导致死锁
value使用随机串,确保唯一性,防止释放了别人的锁
释放锁
先判断当前redis中的value是否与本线程的随机value相等,相等则删除
需要使用lua脚本保证原子性
问题
锁获取成功,还没同步到从节点的时候,主节点挂掉了,哨兵将从升级为主节点后,
其他线程又可以争夺锁,此时出现了多人拥有锁
其他线程又可以争夺锁,此时出现了多人拥有锁
当获取锁的线程执行时间超过了过期时间,此时又出现了多人拥有锁的情况
redlock
多个redis的时候,需要在超过半数的节点上获取锁,没有超过半数,需要主动释放已获取的
缺点
复杂度上升
应用不广泛,可能有遗留bug
限流
滑动窗口
可以使用zset实现,score设置为当前时间,每次请求的时候,删除掉过期的窗口,判断数量是否超出了限额
漏斗
redis-cell模块提供了方便的漏斗限流
消息队列
简单队列
pub/sub
stream
延时队列
可使用zset实现
score存执行时间戳,每次取任务获取0-当前时间戳的一个任务
优化
延迟删除
unlink key
异步回收,防止大key回收内存时卡顿
巨大代价为不再使用共用结构
避免大key造成卡顿
扩容时需要申请巨大空间,删除时内存一次性回收都会造成卡顿
可以使用 redis-cli --bigkeys扫描大key
redis分库
当业务庞大的时候,可以按照业务使用不同的redis集群
其他
scan
防止使用keys导致redis卡顿
每次扫出一部分,需要多次扫描
可能出现重复,需要业务上自己去重
底层为对redis整个字典结构的数组挨个遍历
内存回收机制
并不是删除完一个key之后,内存会立即给操作系统
内存以块为单位,只有一整块内存key都删除了,才会回收这块内存
虽然没有立即给操作系统,但是删除后的空间可以被再次利用
info
info stats
info memory
info replication
info clients
...
安全性
rename-command
将危险指令重命名,防止误操作,比如flushall、keys
绑定指定ip访问
设置访问密码
ssl代理
spiped
0 条评论
下一页