Redis
2020-05-07 22:45:59 432 举报
AI智能生成
redis总结
作者其他创作
大纲/内容
IO
redis使用多路复用的epoll
redis6.0读写使用多线程,执行线程依旧是单线程
https://blog.csdn.net/SDDDLLL/article/details/105597205
通信协议
使用RESP协议
纯文本协议,虽然换行符较多,但是其足够简单,解析也极其容易
事务
multi
开启一个事务
exec
执行从multi到当前位置所有命令
discard
取消执行multi到当前位置所有命令
watch
必须在multi之前执行,如果watch到exec之间数据有变动,exec会执行不成功
弱事务
只能保证事务的隔离性,串行化,中间有命令执行错误依然会向下执行,不回滚
管道pipeline
执行多条命令,只需一次网络连接
实际是客户端实现的功能
由open->write->read->close open->write->read->close<br>改为open->write->write->read->read->close
集群
主从同步
全量同步
刚建立主从关系后,会触发全量同步
主节点bgsave,将内存中数据生成rdb文件,传输给从节点
在bgsave的过程中,新生成的数据会放到环形buffer里,<br>rdb传输完成后,传输buffer中的数据
如果在bgsave过程中,buffer超出限制并覆盖,<br>则会再次触发全量同步,所以buffer大小需要合理设置
增量同步
传输buffer中的数据
落后超过限制后,触发全量同步
无磁盘复制
2.8之后支持,纯内存复制
哨兵sentinel
主节点挂了后,从节点升级为主节点
哨兵需要保证高可用
哨兵之间采用raft协议保证分布式一致性<br>
在平常,哨兵之间是平等的,只有在检测到redis节点超过一定时间没有回复,才会使用raft协议进行选主
一个哨兵认为server节点下线标记该server为主观下线<br>
超过配置的哨兵数认为server节点下线,则该server节点为客观下线,需要进行主从切换
主从切换对配置文件也会进行修改
raft动图
http://thesecretlivesofdata.com/raft/
分布式
client模式/proxy模式
hash
redis服务不知道其他节点的存在,完全由客户端对key进行hash取模后决定放到哪个节点
缺点
一个节点挂掉后,或者增减节点,都会导致原先的key不能hash到对应节点,从而引起缓存雪崩
一致性hash
hash环结构
为了改进hash带来的问题,提出了一致性hash环的概念
hash取模后落在hash环的某个位置,取顺时针方向第一个节点读写
当增减节点的时候,去掉节点的所有key都落到了下一个节点上<br>
虽然可以继续使用,但下一个节点承受了2倍的流量,容易过载导致节点挂掉引起雪崩
虚拟节点
由一个实际节点,虚拟出多个节点分散在hash环上
进一步优化了增减节点时导致的下一个节点过载问题
redis cluster
分为16384个hash槽
采用gossip协议实现无中心化通信<br>
https://www.jianshu.com/p/54eab117e6ae
最少3个master节点(投票需要超过半数,防止脑裂)
集群不可用时机
挂掉的master没有对应slave,即有任一slot不可用,集群不可用
超过半数master都挂掉了,不管有没有slave,都将不可用
常见问题
缓存一致性问题
更新数据的时候,先更新db,再删除缓存,可以很大程度避免缓存不一致
先更新db,后更新缓存,可能造成旧的更新覆盖新的更新导致不一致
缓存穿透
缓存中不存在,查询直接打到了db上,即为缓存穿透,一般情况下,问题不大,读完db写会缓存即可
黑客攻击:故意请求db中也不存在的数据<br>(我们这种情况一般不会写到缓存)<br>
解决方案
即使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脚本保证原子性
问题
锁获取成功,还没同步到从节点的时候,主节点挂掉了,哨兵将从升级为主节点后,<br>其他线程又可以争夺锁,此时出现了多人拥有锁
当获取锁的线程执行时间超过了过期时间,此时又出现了多人拥有锁的情况
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
数据结构
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反而会快
扩容
当元素数量等于数组长度时,会触发扩容<br>
遇到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<br>
作者发话
结构图
分层结构,每上一层,数据越少,第二层25%概率,往上12.25%,以此类推,一共64层
查询元素从上往下比对,找到每一层score小于查询值且最大的节点,进入下层继续同理查找
修改score时,先剔除节点,再插入
ZREVRANK原理
每个节点存一个span,记录该层据后一个节点相对底层跨越了几个节点,算排名只需要将查询路径的span加起来即可
hash存value和score对应关系
应用
排行榜
滑动窗口
延时队列
geo
可对地理位置进行处理,添加,获取坐标,求hash,算距离,获取坐标附近的元素等
底层使用zset实现
pub/sub
发布订阅,消息队列
支持多个消费者同时消费一组数据
比较鸡肋,消费者必须先在线,如果消费者离线,会丢失消息
stream
5.0新增的持久化发布订阅
狠狠的借鉴了kafka
hyperloglog
海量数据去重计数
精准性换取空间
应用
统计UV
布隆过滤器
海量数据判断是否不存在
不存在一定不存在,存在有可能不存在
精准性换取空间
通过位数组和hash实现,对数据hash后取模存入位数组
可以通过扩大位数组和增加hash函数个数提高准确性
需要在创建时预估数据量
数据量超出很多后,准确度急速下降,这时需要重建过滤器,对数据进行重放
配置数据?
redis4.0之后提供插件功能后可以集成,也可以使用github开源
应用
过滤已经爬取过的网址
防止黑客攻击式的缓存穿透
持久化
rdb
存内存快照,生成慢,还原快
每次持久化需要fork子进程异步进行,相对消耗较大<br>
使用cow方式节省内存,<br>即主进程在修改的时候,<br>直接copy一份内存页修改<br>
aof
存指令,生成快,还原慢
重写
文件大小达到一定阈值,就对当前内存有效key重新生成一份指令
刷盘策略
不主动刷盘,由内核决定何时fsync
丢失数据较多
每次fsync
性能损耗较大
每秒fsync
推荐使用,最多丢失1s数据
4.0新增混合模式
aof文件前面是rdb快照,后面是追加的指令,结合两者优点
优化
为提高性能,可以不在主节点开启持久化,而在从节点开启,但出现网络分区,容易导致数据丢失,需要做好监控
内存淘汰策略
volatile-random<br>
allkeys-random
volatile-lru
allkeys-lru
当用redis存热点数据时,建议用该方案
Java中LinkedHashMap可以方便实现
或者用LinkedList和HashMap组合实现
volatile-ttl
拒绝写入
4.0新增
volatile-lfu
allkeys-lfu
过期策略
定时删除
每隔一段时间抓取一部分key,判断过期时间,<br>过期则删除,过期超过一定比例则重复执行
惰性删除
每次get的时候判断是否过期,过期则删除
IO
IO演进
阻塞IO
传统IO模型,在等待连接和read会阻塞线程
只能通过开启多个子线程的方式实现同时响应多个请求
缺点
每个请求都需要开启线程,并且阻塞不能干其他事情,比较耗费资源<br>
非阻塞IO
不管有没有,在等待连接和read都会立即返回
相比阻塞IO,进化为非阻塞,一个线程可以处理多个客户端请求,<br>在没有客户端请求时,可以处理其他任务
缺点
当有大量客户端连接时,每次判断fd状态都需要从用户态切换到内核态,<br>即10k个连接需要循环10k次系统调用,费时费力
多路复用
select
在非阻塞IO基础上,改进10k次系统调用为1次系统调用
缺点
每次系统调用都需要传10k个fd
实际上是由用户循环10k次改为了内核循环10k次,同样是O(n)复杂度
32位机器上,源码中写死了最多接受的fd为1024个,容易成为瓶颈
poll
在select基础上,改进了1024个的限制
缺点
除1024缺陷外,其余都在
epoll
在poll基础上,改进系统调用只需要传1个fd即可监听多个fd状态,<br>实现是在内核开辟一个缓存区,将监听的fd注册一次即可
10k个fd不再循环10k次,而实现了事件机制,当fd可<br>建立连接或者可读可写,会触发回调通知用户态线程
底层为每个硬件在有操作的时候,都会给CPU一个中断,<br>即网卡给CPU中断的时候,会知道是哪个fd的什么事件
缺点
每次读和写的时候,都是由用户线程自己从内核buffer读
异步IO
在epoll基础上,改进读写的时候由内核完成,用户线程只需要提供读写完成后的回调方法即可
目前linux系统还没有成熟的商用异步IO
C10K问题
http://www.kegel.com/c10k.html
0 条评论
下一页