Redis知识点树
2024-07-04 22:31:58 0 举报
AI智能生成
Redis是一个开源的,高性能的Key-Value存储系统,使用ANSI C语言编写,支持多种数据类型,如字符串、哈希、列表、集合和有序集合。它是内存中的数据结构存储系统,通过磁盘或快照进行持久化。Redis支持主从复制、事务、Lua脚本等特性,确保数据的可靠性和一致性。它具有丰富的客户端库,支持多种编程语言,如Python、Java、C#等。Redis广泛应用于缓存、队列、排行榜、订阅/发布系统等场景,是构建现代Web应用程序的重要基础组件。
作者其他创作
大纲/内容
高可用
持久化
RDB 默认
dump.rdb
Redis DataBase (redis 快照)
触发机制
手动触发
save
主线程去备份,备份期间不会处理其他指令,其他指令必须等待
bgsave
子线程去备份,其他指令正常执行
自动触发
配置触发
save 900 1 900s检查一次,至少有1个key被修改就触发<br>save 300 10 300s检查一次,至少有10个key被修改就触发<br>save 60 10000 60s检查一次,至少有10000个key被修改
shutdown
flushall指令
可靠性低
AOF
Append Only File
appendonly no //默认关闭,可以进行开启
"appendonly.aof" //AOF文件名
同步机制
always
表示每次写入都执行fsync(刷新)函数 性能会<br>非常非常慢 但是非常安全
everysec 默认
每秒执行一次fsync函数 可能丢失1s的数据
no
由操作系统保证数据同步到磁盘,速度最快 你的数<br>据只需要交给操作系统就行
AOF会越来越大
重写机制
fork子线程,来写新的压缩的AOF,最后替换
什么时候重写
# 重写触发机制<br>auto-aof-rewrite-percentage 100<br>auto-aof-rewrite-min-size 64mb 就算达到了第一个百分比的大小,也<br>必须大于 64M
大于64mb重写一次,。重写后的 aof 文件可能是40mb。<br>上面配置了auto-aof-rewritepercentag为100,即 aof 文件到了80mb的时候,进行重写<br>
优势
性能高,默认最多丢失1s数据
磁盘满了,追加失败可以<br>用redischeck-aof 工具来修复<br>
格式都是追加的日志,所以可读性更高
不足
. 数据集一般比RDB大
持久化跟数据加载比RDB更慢
在7.0之前,重写的时候,因为重写的时候,新的指令会缓存在内存<br>区,所以会导致大量的内存使用
并且重写期间,会跟磁盘进行2次IO,一个是写入老的AOF文件,一个<br>是写入新的AOF文件
集群
主从数据同步
配置
# Replication<br>role:master //角色<br>connected_slaves:1 //从节点数量<br>slave0:ip=192.168.8.127,port=6379,state=online,offset=7889<br>9,lag=1 //从节点的信息 状态 偏移量<br>master_replid:04f4969ab63ce124e870fa1e4920942a5b3448e7<br>//# master启动时生成的40位16进制的随机字符串,用来标识master节点<br>master_replid2:0000000000000000000000000000000000000000<br>master_repl_offset:78899 //mater已写入的偏移量<br>second_repl_offset:-1<br>repl_backlog_active:1<br>repl_backlog_size:1048576 //缓冲区大小<br>repl_backlog_first_byte_offset:1<br>repl_backlog_histlen:78899 //缓冲区的数据已有大小(是个环形,跟<br>RedoLog一样会覆盖)
# Replication<br>role:slave //角色<br>master_host:192.168.8.129 //主节点IP<br>master_port:6379 //主节点端口<br>master_link_status:up //连接状态 up是正常同步连接状态 down表<br>示复制端口<br>master_last_io_seconds_ago:1 //主库多少秒没有发送数据到从库 0-<br>10<br>master_sync_in_progress:0 //是否正在跟主服务同步<br>slave_repl_offset:163 //从节点偏移量<br>slave_priority:100 //选举时成为主节点的优先级 越大优先级越高 0<br>不会成为主节点<br><br>
主写从读
slave数据同步
在slave的serverCron方法调用replicationCron方法,里面会发起跟<br>master的数据同步
(master_replid,偏移量offset)
master_replid空全量同步
master_replid有值,判断offset是否在缓存区存在
存在,增量
不存在,全量
全量
master开始执行bgsave,生成一个RDB文件,并且把RDB文件传输<br>给我们的slave,同时把master的replid以及offerset(master的数<br>据进度,处理完命令后,都会写入自身的offerset)
slave接收到rdb文件后,清空slave自己内存中的数据,然后用rdb<br>来加载数据,这样保证了slave拿到的数据是master生成rdb时候的<br>最新数据。
由于master生成RDB文件是用的bgsave生成,所以,在生成文件的<br>时候,是可以接收新的指令的。那么这些指令,我们需要找一个地<br>方保存,等到slave加载完RDB文件以后要同步给slave。
. 在master生成rdb文件期间,会接收新的指令,这些新的指令会<br>保存在一个内存区间,这个内存区间就是replication_buffer。<br>我们可以通过以下方式设置replication_buffer的大小
client-output-buffer-limit replica 256mb 64mb 60<br>256mb 硬性限制,大于256M断开连接<br>64mb 60 软限制 超过64M 并且超过了60s还没进行同步<br>内存数据就会断开连接
这个空间不能太小,如果太小,为了数据安全,会关闭跟从库<br>的网络连接。再次连接得重新全量同步,但是问题还在,会导<br>致无限的在同步
增量
master中有个另外的<br>积压缓存(replication_backlog_buffer)。
只有在至少连接了一个副本后,才会分配积压工<br>作。
会一直被覆盖,变成最新的缓存数据
解决负载、数据备份
sentinel哨兵机制
master挂了,slave不会自动升级为master
功能
监控<br>
能够监控我的Redis各实例是否正常工作
通知<br>
如果Redis的实例出现问题,能够通知给其他实例以及sentinel
自动故障转移<br>
当我的master宕机,slave可以自动升级为master
选举sentinel,来做故障转移
与master的断开连接时间
如果slave与主服务器断开的连接时间超过主服务器配置的超时时间<br>(down-after-milliseconds)的十倍,被认为不适合成为master。直<br>接去除资格
配置的优先级
配置 replica-priority,replica-priority越小,优先级越高,但是配置为<br>0的时候,永远没有资格升为
已复制的偏移量
比较slave的赋值的数据偏移量,数据最新的优先升级为master
Run ID
每个实例启动都会有个Run ID
发现master故障
单个哨兵发现故障做标记SDOWN
询问其他sentinel,如果超过Quorum(法定人数)的sentinel都<br>认为master不可用,那么就会将master标为ODOWN(Objectively Down<br>condition 客观下线)
转移条件<br>
Quorum如果小于等于一半,<br>那么必须超过半数的sentinel授权,<br>你才能去做故障迁移,<br>
Quorum如果大于一半,<br>那么必须Quorum的sentinel授权,<br>故障迁移才能启动<br>
配置提供<br>
sentinel可以提供Redis的master实例地址,<br>那么客户端只需要跟sentinel进行连接,master挂了后会提供新的master
脑裂问题
自动故障转移可能会导致脑裂问题
会有2个master,client会从不同的master写数据,从而<br>在master恢复的时候会导致数据丢失
解决方案
min-replicas-to-write 1 至少有1个从节点同步到我主节点的数<br>据,但是由于是异步同步,所以是最终一致性 不会确保有数据写入<br>min-replicas-max-lag 10 判断上面1个的延迟时间必须小于等于10s
cluster
分片
就是我希望把数据分布到不同的节点。这样如果某些节点异<br>常,其他数据能正常提供服务,跟我们微服务的思想很相似。
hash slot(虚拟槽)
Redis cluster中有16384个虚拟槽。
是根据
会根据key 通过CRC16 取模 16383 得到一个0到16383的值 计算公<br>式:slot = CRC16(key) & 16383,得到的值,就代表这个key在哪个虚拟<br>槽。
key跟虚拟槽是根据key计算的,不会变
如果想把相关key放入一个虚拟槽,也就是一个实例节点,我们可以采用{},那<br>么就只会根据{}里面的内容计算hash槽!
eg:key1{18} 跟 key2{18} 就会在一个虚拟槽
节点移动、宕机故障转移、扩容缩容
每次节点的扩容与缩容,只需要改变节点跟虚拟槽的关系即可,不需<br>要全部变动。
分布式锁
redis 锁原理
标记
抢占到,设置key
标记的可见性
设置过程中是不可见的,redis单线程完美避开
保证原子性
setnx原子性,是单线程执行
redis多指令单原子性
redis事务
multi <br>incr myid<br>incr testid<br>exec
不会先去执行指令,而是将指令放到队列queue
一次性执行多个指令 中间结果不能使用
可能执行一部分,并且不支持回滚!!! 是个笑话
命令是原子的 在执行事务中的指令时 服务器阻塞 不能执行其他指令
lua脚本
解决事务中需要使用中间结果的问题
evalsha
执行lua脚本文件<br>redis-cli --eval 脚本文件 key列表,参数列表<br>
eval
命令行执行:<br> eval 脚本内容 key个数 key列表 参数列表<br>
使用场景
需要原子性执行多个命令
需要中间值来组合后面的命令
需要中间值来编排后边的命令
可重入锁
核心
互斥条件
标记是否有线程
线程信息
用来判断是不是同一线程
重入次数
记录重入的次数,再释放的时候,减少相应的次数
Redission实现
加锁关键源码
:通过Lua保证原子性
利用hash结构保存锁数据
key: 大key<br>filed:UUID+线程ID<br>value:1
看门狗机制--采用时间轮定时 定时时间为10s
加锁失败
返回还要多久时间过期
返回
加入队列,用时间轮处理
订阅频道,释放时获取及时通知
通过Semaphroe进行阻塞,<br>阻塞时间=剩余等待时间<锁释放的时间?剩余等待时间:锁释放时间
加锁方法
tryLock(long waitTime,long leaseTime,TimeUnit unit)
联锁(红锁)
解决加锁后宕机问题
设置加锁机器数通过数量才获得锁 减少宕机导致锁丢失问题
至少3主三从,三主做sharding 3从来保证主宕机后的高可用
ReentrantLock
订阅与发布
订阅
subscribe key1 key2<br>psubscribe key1* key2*<br>
发布
publish key message
数据结构
ZipList
hash-max-ziplist-value 64 // ziplist中最大能存放的值长度<br>hash-max-ziplist-entries 512 // ziplist中最多能存放的entry节<br>点数量
1)哈希对象保存的键值对数量<512个;<br>2)所有的键值对的健和值的字符串长度都<64byte(一个英文字母一个字<br>节)。
会根据存入的数据类型以及大小,分配不同大小的空间
缺点 一整块完整空间,当元素发生变化时,会产生连锁更新,严重影响性能。<br>所以只适用于数据量比较小的场景
优缺点
缺点
缺点 一整块完整空间,当元素发生变化时,会产生连锁更新,严重影响性能。<br>所以只适用于数据量比较小的场景
优点
节省空间
数据结构源码
dict
初始大小4
扩容=已使用大小的2倍且2N次幂
结构 dict.h
dictType *type
支持不同的数据类型对应dictType抽象方法
void *prvidata
也和不同类型相关,不同类型特定函数的可选参数
dictht ht[2]
2个hash表 用来数据存储 2个dictht
long rehashidx
rehash 标记<br>-1代表不在rehash<br>
unsigned long iterator
当前dict中的数量
dict.h
dictEntry
typedef struct dictEntry {<br>void *key; //key<br>union {<br>void *val;<br>uint64_t u64;<br>int64_t s64;<br>double d;<br>} v; //value<br>struct dictEntry *next; //指向下一个节点<br>} dictEntry;
dictht结构
dictEntry **table
dictEntry 数组
unsigend long size
数组大小 默认为4 <br># define DICT_HT_INITIAL_SIZE 4
unsigned long sizemask
size-1 用来取模得到数据的下标值
unsigned long used
表中已有的节点数据
quickList
兼顾了ZipList的节省内存,并一定程度上解决了连锁更新问题
quicklistNode节点里是个ziplist
list-max-ziplist-size 设置每个node的ziplist大小
整数表示node的数量<br>-1:max size 4kb<br>-2: max size 8kb<br>-3: max size 16kb<br>-4 -5...
链式结构
quicklistNode组成的链
QuicklistNode<br>count ziplist中的元素个数<br>zl 指针
count
Set
inset
不是整数类型,用dictht hash表 (数组+链表)
元素不超过512个,也用hashtable存储
set-max-intset-entries 512
hashtable
Zset
Ziplist
默认
skiplist +dict
如果元素数量大于等于128<br>或着任一member长度等于64字节用
zset-max-ziplist-entries 128<br>zset-max-ziplist-value 64<br>
介绍
<ul><li>K-V结构内存型数据服务</li></ul>
应用场景
分布式锁
应用缓存
数据类型
String
List
Hash
Set
Zset
bitMap
Streams流
HyperLogLog
Geo(地理位置)<br>
意大利西西里岛小伙antirez,为解决访客信息网站LLOOGG.COM(类似百度统计)用mysql数据库的性能问题,所创建的C语言内存数据库。<br>redis全称是REmote Dictionary Service<br>
中文官网
http://www.redis.cn/
快
内存操作,不需要和磁盘交互
本身就是k-v结构,类似hashMap,所以查询速度接近O(1)
同时redsi自己底层数据结构支持,如跳表、SDS
命令执行是单线程,同时通讯采用多路复用
IO多路复用,单个线程中通过记录跟踪每个(I/O流)的状态来管理多个I/O流
数据结构和扩容
k-v结构
头插法
单线程,头插更快
单个key/value都是 默认最大512M
zipList
节省空间
缺点
数据越大越慢
skipList
查找更快
基本数据类型对应结构
String
SDS
Simple Dynamic String
优势
c字符串不记录自身长度,时间复杂度O(n),SDS为O(1)
SDS减少修改字符串带来的内存重新分配次数<br>
空间预分配
SDS长度<1MB,预分配和长度一样,<br>SDS长度>1MB,每次跟len的大小多1M<br>
惰性释放
二进制安全
c字符串中的字符必须符合某种编码(ASCII),并除了字符串末尾外,字符串中不能包含空字符串,否则会误读空字符串为结尾。c是以空字符串来标记是否结束的。限制了c字符串的存储类型,只能文本,不能音视频等
SDS字符串是否结束是根据len来,所以也就不会有这样的问题。
结构
len
alloc
flags
buffer
基本指令
mset key1 v1 key v2
批量设置
mget key1 key2
批量获取
strlen key
获取长度
append key value
字符串追加元素
getrange key 0 5
获取指定范围的字符
incr intkey / incrby inkey 50
整数值递增
set f 2.6 /incrbyfloat f 7.3
浮点值递增
set lock1 1 EX 10 NX
EX 设置指定的到期时间 秒<br>PX 设置置顶的到期时间 毫秒<br>NX 仅在key不存在的时候设置<br>XX 只在key存在的时候才设置
set nx k1 1
应用场景
缓存 、token(过期)
线程安全的技术场景 软限流、分布式ID
Set 无序集合 不可重复
String类型的无序集合,最大2ˇ32-1
添加删除时间O(1)
基本指令
sadd mawh a b c d e f
smembers key 获取所有元素
scard key 获取所有元素个数
srandmember key 随机获取一个元素
spop key 随机弹出一个元素
srem key a 弹出指定
sdiff key key2 获取前一个集合有后一个集合没的元素
sinter key key2 获取交集
sunion 获取并集
应用场景
抽奖
点赞、签到等 sadd集合存储
关注等场景 交集并集
Zset 有序集合 不可重复
基本命令
zadd z1 10 a 20 b 30 c 40 d
zrange
根据分数从低到高
zrevrange z1 0 -1 withscores
根据分数从高到低
zrangebyscore
根据分数获取值
zrem z1 a
移除元素
zcard z1
获取zset个数
zincrbt z1 20 b
给某个元素加分
zcount z1 50 60
获取范围内个数
zrank z1 d
获取指定元素的索引
zscore z1 d
获取元素的分数
sorted set,有序的set,每个元素有一个score;score相同按照可以的ASCII码排序
应用场景
排行榜 销售、热搜、游戏评分等
List 有序集合 可重复
最大2ˇ32-1个元素,40亿个元素
基本指令
左右添加元素
lpush queue a<br>lpush queue b c<br>Rpush queue d e
左右弹出一条
lpop queue
rpop queue
左右弹出一条,并设置超时时间,知道有数据弹出或者超时
blpop queue 10<br>brpop queue 10<br><br>
取值
lindex queue 0<br>lrange queue 0 -1
应用场景
用户消息时间线 有序
消息队列 BRPOP|BLPOP
BitMap
位图不是实际的数据类型,而是String类型中定义的一种面向位的操作,所以这个位图默认最大长度是512
可以容纳2ˇ32不同的位,可以在不同的位设置0或者1
基本指令
setbit premission 5 1
把位5设置为1
getbit premission 5
得到位5的值
bitcount premission
获得1点总数
bitpos premission 1
获取premission位为1的第一个位置
bitop and hbit bitkey permission
获取bitkey与permission<br>的&运算 并且赋值给hbit
应用场景
实际的统计数据
学生到课率
存储与ID相关的节省空间并报性能的 是或者否的值
用户权限 不同的位代表不同的权限
hash
基本指令
hset h1 f 6<br>hset h1 e 4<br>hmset h1 u 8 r 9<br>hget h1 a<br>hmget h1 a b c<br>hkeys h1<br>hvals h1<br>hgetall h1<br>hgetall h1<br>hincrby h1 a 10 给某个字段添加值<br>hexists h1 y 查key中file字段是否存在<br>hdel h1 a<br>hlen h1
场景
String能做的hash都能做,因为hash就比String多了一层key而已!并且单个key可以单独更改
1存储对象累的数据
2统计类的数据 可以对单个数据进行进行单独修改
底层结构
ditch hash表
ZipList
QA
Redis发生Hash冲突为什么要头插?而HashMap为什么1.8之后要尾插
为什么要扩容?
扩容
时机
没有fork子线程在进行RDB和持久化时,ht[0]的used大于等于size时,触发
有fork子线程在进行RDB和持久化时,ht[0]的used大于size*5时,触发
dictExpand具体方法
步骤
按桶来扩容,每次转移15个元素,不够找新桶,最多100个
满足条件,触发扩容时,判断是否在扩容,如果在扩容,或者扩容大小和我一样,放弃本次
new一个新的dictht,切大小为ht[0].used*2(但必须向上2的幂),并且ht[1]=新创建的dictht
设置rehashidx=0,代表可以进行数据迁移ht[0] 到ht[1]
逐步有序迁移
每次curd都会进行一个hash桶的迁移
if (dictIsRehashing(d)) _dictRehashStep(d);
定时任务进行迁移
serverCron
完成迁移时,<br>ht[0].table= ht[1],<br>ht[1].table=null,<br>ht[1].size=0,<br>ht[1].sizemask=0<br>,ht[1].usd=0<br>
把dict的rehashidx=-1
管道技术pipelining
客户端发一个请求给服务端--》客户端等待服务端响应--〉服务端收到请求并处理-返回客户端
客户端到服务端需要时间的,包括网络传输,连接等
Rounf Trip Time
pipelining = 多个请求一个RTT
性能提升5- 10倍
场景
大数据并发写入且客户端不需要获取读的场景
对性能有要求
管道内是原子性,但客户端命令会交叉执行
不需要以上一个指令的返回结果来判断后续逻辑
指令
redisTemplate.executepipeline()
过期策略 淘汰策略
过期
惰性过期
每次访问key,触发
expireIfNeeded方法
定时过期
定时方法serverCron
1.扫描所有有过期时间的key<br>2.根据hash桶的维度扫描,扫到20个为止<br>3.单个桶不足20,扫描下一个桶,扫到的桶就把桶内扫完,扫描最多不超过400个桶<br>4.删除扫描到的过期的key<br>5.如果400个桶没数据,或者扫描的删除跟扫描的key总数超过10%,继续执行2,3,4<br>6.循环16次后会去检测时间,超过指定时间会跳出
淘汰
策略
noeviction 不淘汰 能读不能写
默认
LRU 最近最少使用<br>least recently used<br>
所有key
有过期时间的所有key
LFU 最不常使用<br>least frequently used<br>
所有key
有过期时间的所有key
random 随机
所有key
有过期时间的所有key
ttl 即将过期
淘汰流程
淘汰池,默认大小16
1.每次指令操作,自旋会判断当前内存是否满足指令所需内存<br>2.如果不满足,会从淘汰池中的尾部取最适合淘汰的数据<br> 2.1 会取样(配置 maxmemory-samples)从Redis中获取随机获<br>取到取样的数据 解决一次性读取所有的数据慢的问题<br> 2.2 在取样的数据中,根据淘汰算法 找到最适合淘汰的数据<br> 2.3 将最合适的那个数据跟淘汰池中的数据比较,是否比淘汰池<br>中的更适合淘汰,如果更适合,放入淘汰池<br> 2.4 按照适合的程度进行排序,最适合淘汰的放入尾部<br>3.将需要淘汰的数据从Redis删除,并且从淘汰池移除
LRU算法
如果redisObject.lru<lruclock(当前时间)
通过lruclock-redisObject.lru得到这个<br>对象多久没访问
如果redisObject.lru>lruclock
通过lruclock+(24bit的最大值-redisObject.lru)
lru
段记录的是对象操作访问时候的<br>秒单位时间的后24bit!
lruclock=当前时间的秒单位的最后24bit
相差越大越容易淘汰
LFU算法
缺点
失效性问题
新的数据进不来,老的数据进不去
最不常使用
typedef struct redisObject {<br> unsigned type:4;<br> unsigned encoding:4;<br> unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or<br> * LFU data (least significant 8 bits frequency<br> * and most significant 16 bits access time). */<br> int refcount;<br> void *ptr;<br>} robj;
counter
低8位用来记录访问频率
counter 8bit最大255
增加
counter并不是一个简单的线性计数器,而是用基于概率的对数计数器来实现 (值越大增加的概率越低)
减少
根据前18位计算距离但前时间,并根据衰减因子计算衰减值
高16位用来记录访问时间(单位为分钟
24 bits = 18bit +6bit <br>
收藏
0 条评论
下一页