Redis
2021-06-29 15:48:20 1 举报
AI智能生成
Redis
作者其他创作
大纲/内容
原理分析
为什么快
纯内存KV结构
KV结构的内存数据库,时间复杂度O(1)
请求单线程
减少线程创建销毁带来性能消耗
避免线程上下文切换
避免线程竞争
CPU不会成为性能的瓶颈,瓶颈在内存跟网络
IO多路复用机制
网络IO
多个TCP连接
复用一个或多个线程
6.0新增IO多线程 吞吐量翻倍
默认只让写的操作多线程
4核以上才配置,线程数不要超过8
内存回收
过期策略
立即过期
每个key设置过期时间,时间到就会清除
优点:对内存友好
缺点:会占用CPU资源去处理过期数据,影响吞吐
惰性过期
只有get的时候会判断是否过期
setKey 时,发现内存不够用会调用回收方法,释放内存
优点:节省CPU资源
缺点:对内存不友好
定期过期
每个一段时间,会扫描一定数量的数据库expires字典中一定数量的key进行清除,是一个折中方案
优点:在不同情况下会使得CPU和内存资源达到最优平衡
淘汰策略
最大内存设置
32位的最多使用3GB内存
64位系统无限制
淘汰类型
LRU
最近最少使用
LFU
最不常使用
random
随机删除
volatile
针对设置TTL的key
allKey
针对所有key
noeviction
不做内存回收
根据以上关键字进行组合,一共8种
建议使用: volatile-lru 保证正常服务的情况下,优先删除最近最少使用的key
LRU淘汰原理
传统实现
链表 + HashMap
设置链表长度,如果新增或者被访问,就移动到头结点
如果超出链表长度,自动删除队尾节点
Redis实现
会淘汰其中热度最低的key
在redisObject里存着最后一次访问的时间
全局维护了一个时间
两个值进行比对,判断那些key优先淘汰
持久化
RDB 记录快照(默认使用)
自动触发
X秒内至少有X个key被修改,则生成快照
lastsave查看最近一次成功生成快照的时间
正常关机也会触发快照生成
调用flushall也会生成快照,但是是空文件
手动触发
save
阻塞其他命令生成快照
bgsave
后台生成快照
优点
文件紧凑,适用于备份与灾备
生成文件不影响主程序
大数据集回复速度较快
缺点
不能做到实时持久化
宕机会丢失最后一次快照后的数据
AOF 记录日志(开启优先使用)
持久策略
no
不执行fsync,由操作系统保证数据同步到磁盘
优点:速度快
缺点:不安全
always
每次写都执行fsync
优点:保证同步到磁盘
缺点:效率低
everysec(推荐)
每秒执行一次fsync
优点:效率与安全兼顾
缺点:可能丢失一秒数据
压缩重写机制
读取服务器现有的键值对,然后用一条命令去代替之前多条命令,生成新的文件后去替换原来的AOF文件。
自动触发
文件比例百分比
设置文件大小
手动触发
bgrewriteaof
重写时,先把新命令放到缓冲区中,再同步到新文件
优点
AOF提供多种同步频率
缺点
比RDB文件更大
高并发情况下,RDB比AOF性能更好
方案比较
能忍受一小段时间内的数据丢失选RDB
数据非常宝贵AOF
不要单独设置一种持久化机制,两种一起用,AOF比RDB的数据要完整
分布式技术
主从复制
原理
全量复制
第一次连接到主节点,需要全部数据
同步阶段
将RDB文件发送给从节点
从节点如果有数据,先清除自身数据
增量复制
之前连接过master节点,进行增量备份
通过偏移量记录上一次的数据地址
缺点
未解决高可用问题
需要手动切换主从
高可用
能够实现主从自动切换,并且获取最新的master节点
Sentinel 哨兵机制
原理
本质上是运行在特殊模式下的redis
并且通过info命令监听所有Redis的机器
为了保证哨兵高可用,也会对哨兵做集群部署
哨兵也是特殊的Redis,有着订阅发布的功能,会通过_sentinel_:hello 发送消息进行监控
服务下线
主观下线
默认以每秒一次的频率发送Ping命令,
没有收到有效回复,sentinel会把该服务器标记为下线
客观下线
发现master下线的Sentinel节点会继续询问其他Sentinel节点
大多数Sentinel认为下线,master才真正确认被下线
选举机制
哨兵选举
现在Sentinel选举一个Leader,通过Leader进行选举
Raft算法
先到先得,少数服从多数
Master选举
哨兵连接断开的比较久,超过了阈值,就失去了选举权
如果有选举权,看谁的优先级高,在配置文件里可以设置
如果优先级相同,看谁从master中复制的数据最多(复制偏移量最大)
如果复制数量也相同,就选择进程id最小的那个
总结
优点
监控
监控主从服务器是否正常运行
通知
被监控服务器出现问题,Sentinel可以通过API发出通知
自动故障转移
主服务器发生故障,Sentinel可以把从服务器升级为主服务器
配置管理
客户端连接到Sentinel,获取当前主服务器的地址
路由管理的功能
缺点
主从切换的过程中会丢失数据,因为只有一个master
只能单点写,没有解决水平扩容的问题
数据分片
客户端 Sharding
一致性哈希
把所有的哈希值空间组织成一个虚拟的圆环(哈希环)
通过Key哈希后取模,得到哈希环的位置
顺时针找到第一个Node节点存放数据
优点
增加节点不需要全部重新计算哈希值,只影响一部分数据
缺点
节点较少的情况,不是均匀分布
解决方案:引入增加虚拟节点
Redis Cluster
架构
由多个Redis 实例组成的数据集合。客户端不需要关注数据的子集到底存储在哪个节点,只需要关注这个集合整体
数据分部
Redis创建了16384个槽(slot),每个节点负责一定区间的slot
对 key 用CRC16算法计算再模以16384,得到一个slot的值,数据落到负责这个slot的Redis节点上
特点
无中心架构
可动态调整数据分布
可扩展性
高可用性
降低运维成本
面试
数据一致性
以数据库的数据为准
实时一致性
最终一致性
先更新数据库,在删缓存
使用cannel
延时双删
热点数据发现
Redis内存淘汰机制,留下的都是热点数据
客户端
客户端发现热点数据
但是会入侵代码
不知道有多少个KEY,会发生内存泄漏
服务端
Redis的montior的命令,可以监控所有Redis的执行命令
高并发情况会影响性能,不适合长时间使用
只能统计一个节点的热点KEY
机器层面
利用packetbeat插件,网络抓包
缓存雪崩
大量热点数据同时过期
解决
加锁
针对一个KEY,查询时先加锁,然后在写入缓存
预更新
后台定时任务预先更新
TTL加随机数
TTL单独设计过期时间
永不过期
缓存击穿
一个热点的KEY过期
解决同雪崩
缓存穿透
查询数据库不存在的值,Redis失去作用
解决
布隆过滤器
Redis基础
基本特性
速度快 每秒10WQPS
支持多种数据类型
支持多种编程语言
持久化、内存淘汰机制
功能丰富
事务
发布订阅
pipline
lua
分布式集群
数据类型
String
存储类型
Int 整形
Float 单精度浮点
String字符串
数据模型
外层是 K-V 哈希表
内在键值都是dictEntry,通过指针指向Key,Value的存储结构
dictEntry
key
SDS key
val
redisObject
type 对象的类型
OBJ_STRING
OBJ_LIST
OBJ_HASH
OBJ_SET
OBJ_ZSET
encoding 具体的数据结构
refcount 引用计数,为0时可以垃圾回收
*ptr 指向对象实际的数据结构
SDS value
SDS 简单动态字符串
C语言没有字符串 只能用char[]实现,
为什么自己造轮子
传统C字符数组
必须给变量分配足够内存,否则可能会溢出
遍历数组长度的时间复杂度O(n)
变更会对字符数组做内存重分配
结尾以“\0”,不能保证二进制文件的保存
SDS特点
不用担心内存溢出,SDS会进行扩容
遍历数组长度的时间复杂度是O(1),因为定义了len属性
通过“空间预分配”“惰性空间释放”,防止多次内存重分配
结束的标致为len,读取len的长度的数组
String的编码
int 存储8个字节的长整形
int数据不再是整数转为:raw
int大小超过long的范围转为:embstr
embstr 存储小于44个字节的字符串
只分配一次内存空间,RedisObject跟SDS连续
如果value增加,需要重新分配内存空间
embstr长度超过44个字节转为:raw
内容只读,如果增加会转化为:raw再修改
raw 存储大于44个字节的字符串
需要分配两次内存空间,RedisObject跟SDS分开
应用场景
缓存热点数据
分布式session,单点登录
分布式锁 set NX EX
分布式全局ID incr
计数器 incr
限流 incr
位操作
Hash
存储类型
多个无序的键值对 key:field:value
特点
节省内存空间
减少key冲突
取值减少性能消耗
不适用场景
field不能单独设置过期时间
需要考虑数据量分布的问题
数据模型
ziplist 压缩列表
经过特殊编码,连续的内存块组成双向链表
不存指针的地址,存储的上节点长度和当前节点长度
使用条件
保存field数量小于512
所有的field和value的字符串长度都小于64字节
hashtable 哈希表
dict 最外层
字典数据结构,两个dictht表ht[0],ht[1],一个使用 一个为空,方便扩容
dictht 里层
指向dictentry数组的指针
dictentry 最里层
数组+链表形式存放
扩容
元素的个数 / 数组的长度 > 5 触发扩容
在空的dictht表里创建原本大小的2的幂次方
重新rehash分配到新表
释放ht[0]的空间,将ht[1]这张表改为ht[0],创建新的ht[1],为下次做准备
应用场景
与String无异
商品购物车
key用户ID
field商品ID
value商品数量
List
存储类型
存储有序的字符串(从左往右)
元素可重复
数据模型
quicklist
head 记录头节点
tail 记录尾节点
count 所有ziplist的元素个数
len 双向链表的长度
queicklistNode 双向链表
pre 上节点
tail 下节点
ziplist 压缩列表
应用场景
用户消息列表
文章列表
评论列表
公告列表
活动列表
消息队列,栈
BLPOP 左侧阻塞出队
rpush 右侧入队
Set
存储类型
String类型的无序集合
整形数字
存储40E数量
数据模型
intset
数字数组
hashtable
元素数量超过512会用hashtable ,可配置
应用场景
抽奖
spop myset
点赞,签到,打卡的唯一记录
点赞
like:doc_1
点赞 sadd like:doc_1 UID
取消 srem like:doc_1 UID
是否点赞 sismemberlike:doc_1 UID
查看所有点赞 sismembers like:doc_1 UID
点赞总数 scard like:doc_1
商品标签,用户画像
商品标签
tags:good_1
tags:good_1 画面清晰细腻
tags:good_1 144HZ
tags:good_1 32英寸
用户关注,推荐模型
相互关注
我关注的也关注了他
可能认识的人
商品筛选
勾选需要的类型,取交集
差集取不同、交集取相同、并集取所有不同
Zset
存储类型
存储有序元素
不允许重复元素
按照score从小到大排序,score相同按照 ASCII码排序
数据模型
ziplist
元素数量小于128,所有长度小于64字节
skiplist + dict
skiplist跳表
传统有序链表效率不高时间复杂度为O(n)
每间隔一个元素增加一个指针,指向下下个元素的节点
会随机的决定元素的指针数
存在2层,3层,4层的跳表
实现方法比树简洁
应用场景
热搜、排行榜
点击数+1 zincrby hotNews:日期 1 文章ID
获取最多的N条 zrevarange hotNews:日期 0 N withscores
子主题
BitMaps
存储类型
字符串存储
数据模型
一个字节八位二进制位组成
应用场景
用户访问统计
在线用户统计
用户签到
与、或、异或、非运算
Hyperloglog
应用场景
不太精确的基数统计
统计网站UV
日活、月活
Geospatial
存放经纬度,地理位置信息
应用场景
找到最近的门店
找到范围内的门店
Redis高级特性
发布订阅
发布者可以向指定频道发送消息
订阅频道:可以一次订阅多个
取消订阅
按照规则订阅频道
?代表一个字符
*代表0个或多个字符
事务
特点
把一组命令一起执行
按进入队列的顺序执行
不会受到其他客户端的请求的影响
事务不能嵌套,多个multi命令效果一样
用法
multi 开始事务
exec 执行事务
执行exec之前发生错误,事务会被拒绝执行
执行exec之后发生错误
错误前的命令执行成功
官方解释,开发的锅,不关我事
解决使用LUA脚本
discard 取消事务
watch 监视
防止事务过程中某个key的值被其他客户端修改,并且取消事务
CAS乐观锁行为
LUA脚本
特点
批量执行命令
原子性
脚本写入文件,实现操作集合的复用
当有lua脚本时,阻塞其他任何请求
应用场景
对IP限流
每个用户在X秒只能访问Y次
local number = redis.call('incr',KEYS[1])
if tonumber(number ) == 1 then
redis.call('expire',KEYS[1],ARGV[1]) 第一次访问
return 1
else if totnumber(number) > tonumber(ARGV[2]) then
return 0
else
return 1
end
if tonumber(number ) == 1 then
redis.call('expire',KEYS[1],ARGV[1]) 第一次访问
return 1
else if totnumber(number) > tonumber(ARGV[2]) then
return 0
else
return 1
end
./redis-cli --eval "ip_limit.lua" app:ip:limit:192.168.8.10 , 6 10
redis 没有乘,用lua实现自乘
缓存脚本
Redis可以缓存Lua脚本并生成SHA1摘要码,可以直接通过摘要码来执行Lua脚本
script load ‘XXXXX脚本’ 生成脚本摘要ID
evalsha '脚本摘要ID' 0
脚本超时
脚本超时默认5秒
通过script kill停止命令
为了保证原子性 set 跟delete 不能停止命令
只能不保存关机shutdown nosave
0 条评论
下一页
为你推荐
查看更多