redis
2023-10-27 13:50:56 21 举报
AI智能生成
redis
作者其他创作
大纲/内容
数据结构
简单动态字符串O(1)
双向链表O(n)
压缩列表O(n)
哈希表O(1)
跳表(OlogN)
整数数组O(n)
应用场景
string
单值缓存
对象缓存
分布式锁
计数器
web集群session共享
全局ID
hash
对象缓存
购物车
list
消息队列
微博消息&微信公众号消息
set
抽奖
点赞、签到、打卡
商品筛选
微博微信关注模型
zset
排行榜
线程模型
单线程
Redis 单线程是指它对网络 IO 和数据读写的操作采用了一个线程;<br>而采用单线程的一个核心原因是避免多线程开发的并发控制问题
单线程快的原因
Redis 大部分操作是在内存上完成
并且采用了高效的数据结构如哈希表和跳表
Redis 采用多路复用,能保证在网络 IO 中可以并发处理大量的客户端请求,实现高吞吐率
rehash
增加现有哈希桶数量,让逐渐增多的entry元素在更多的桶之间分散保存,减少单个桶元素数量,从而减少冲突
执行步骤
redis会使用两个全局哈希表,哈希表1和哈希表2,首先会使用哈希表1
在rehash过程中会给哈希表2设置更大的空间
最后释放哈希表1的空间
渐进式rehash的引入原因
在rehash执行数据拷贝的过程中,如果一次性的把哈希表1的数据全部迁移,会造成redis线程阻塞,无法服务其他请求
渐进式rehash的执行过程
在数据拷贝的过程中,redis可以正常服务请求,<br>每处理一个请求时,从哈希表 1 中的第一个索引位置开始,顺带着将这个索引位置上的所有 entries 拷贝到哈希表 2 中;<br>等处理下一个请求时,再顺带拷贝哈希表 1 中的下一个索引位置的 entries
rehash时机
装载因子≥1,同时,哈希表被允许进行 rehash
装载因子≥5<br>
持久化
AOF
优点
写后日志可以避免出现记录错误指令的情况<br>
它是在命令执行之后才记录日志,所以不会阻塞当前的写操作
缺点
写后日志如果发生宕机会造成数据丢失
日志写回磁盘可能阻塞主线程<br>
三种写回策略
Always, 同步写回<br>
Everysec, 每秒写回<br>
NO, 操作系统控制的写回
aof文件过大给redis带来性能问题
造成影响<br>
文件系统本身对文件大小有限制,无法保存过大的文件<br>
如果文件太大,写入效率会降低
文件太大,宕机后恢复数据也很耗时
解决方案
aof重写机制
具体实现
检查当前键值数据库中的键值对,记录键值对的最终状态,从而实现对某个键值对 重复操作后产生的多条操作记录压缩成一条 的效果。<br>进而实现压缩AOF文件的大小。
重写过程
一个拷贝,<br>每次执行重写时,主线程 fork 出后台的 bgrewriteaof 子进程;fork 会把主线程的内存拷贝一份给 bgrewriteaof 子进程,bgrewriteaof 子进程就可以在不影响主线程的情况下,逐一把拷贝的数据写成操作,记入重写日志
两处日志,指的是此时如果有写操作,<br>第一处日志就是指正在使用的 AOF 日志,Redis 会把这个操作写到它的缓冲区;<br>第二处日志,就是指新的 AOF 重写日志。这个操作也会被写到重写日志的缓冲区
aof触发重写的时机<br>
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
相关参数
appendonly<br>
(默认no,关闭)表示是否开启AOF持久化
appendfilename
AOF持久化配置文件的名称
appendfsync
AOF持久化策略
RDB
优点
rdb是二进制文件,数据恢复速度快
缺点
快照间隔过长会丢失数据
快照时间过短会加大磁盘写入压力
频繁fork子进程 fork过程会阻塞主线程
如何生成
save
在主线程中执行,会导致阻塞
bgsave(默认配置)
创建一个子进程,专门用于写入RDB文件,避免阻塞主线程
潜在风险
为了保证快照的完整性,在快照执行过程中,主线程只能处理读操作,会给业务服务造成巨大的影响
解决方案<br>
写时复制技术(Copy-On-Write, COW)
频繁执行全量快照带来的影响
频繁将全量数据写入磁盘,会给磁盘带来很大压力,多个快照竞争有限的磁盘带宽,前一个快照还没有做完,后一个又开始做了,容易造成恶性循环
bgsave 子进程需要通过 fork 操作从主线程创建出来;fork这个创建过程本身会阻塞主线程,如果频繁 fork 出 bgsave 子进程,这就会频繁阻塞主线程了
解决方案
4.0推出的混合持久化
相关配置参数
save 900 1 -> 900秒内至少有一次修改则触发保存操作
save 300 10 -> 300秒内至少有10次修改则触发保存操作
save 60 10000 -> 60秒内至少有1万次修改则触发保存操作
高可用
主从
首次同步流程<br>全量复制
从库发送psync指令给主库并建立连接
主库执行bgsave命令生成rdb文件,将rdb文件发送给从库;<br>从库接收到 RDB 文件后,会先清空当前数据库,然后加载 RDB 文件;<br>在同步的过程中,主库不会阻塞,主库会将接收到的新请求记录到replication buffer
主库会将replication buffer中的修改操作发送给从库
增量复制
使用原因:主从库间网络断了
如何实现
可能引发的错误
解决方案:将repl_backlog_size对这个参数进行扩大一倍,repl_backlog_size = 缓冲空间大小 * 2
replication buffer 和 repl_backlog_buffer的区别<br>
哨兵
基本流程
监控<br>通过PING命令来监控主从,哨兵会周期性的给主从库发送PING命令,如果从库在规定的时间内没有响应,就会被标记下线状态;<br>如果主库不响应,就会判定主库下线,然后开始自动切换主库流程
选主<br>主库挂了,根据一定的规则从从库中进行选主<br>
通知<br>哨兵会把新主库的连接信息发给其他从库,让它们执行 replicaof 命令,和新主库建立连接,并进行数据复制<br>
判断下线状态
主观下线
哨兵会使用PING命令检测它自己和主从库的网络连接情况,如果发现主从库有响应超时了,哨兵就会标记成“主观下线”
客观下线
哨兵集群情况下,当有 N 个哨兵实例时,最好要有 N/2 + 1 个实例判断主库为“主观下线”,才能最终判定主库为“客观下线”
选主操作
筛选条件
规则
第一轮:优先级最高的从库得分高;slave-priority 配置项,给不同的从库设置不同优先级
第二轮:如果优先级相同,则旧主库同步程度最接近的从库得分高
第三轮:在优先级和复制进度都相同的情况下,ID 号最小的从库得分最高,会被选为新主库
哨兵集群
哨兵之间互通机制
基于pub/sub机制,在主库中有一个"__sentinel__:hello"的频道,哨兵之间互相发现通信
哨兵与主从库互通机制
哨兵向主库发送INFO指令,可以获取所有从库的信息,实现对主库,从库的监控
哨兵判定主库异常机制
哨兵集群中任意一个实例都可以发起主库异常“投票仲裁”流程
判断客观下线的流程
主从切换哨兵Leader选举
选举条件
a. 拿到半数以上的赞成票
b. 拿到的票数同时还需要大于等于哨兵配置文件中的 quorum 值
切片集群
数据切片和实例对应分布的关系
映射过程
首先根据key,按照CRC16 算法计算一个 16 bit 的值
再对这个16bit值对16384进行取模,得到 0~16383 范围内的模数,每个模数代表一个相应编号的哈希槽
哈希槽如何映射到redis具体实例上
可以使用cluster create 命令创建集群,redis会自动将这些槽平均分配到集群实例上
也可以使用 cluster meet 命令手动建立实例间的连接,形成集群;<br>再使用 cluster addslots 命令,指定每个实例上的哈希槽个数,但是必须将16384个槽分配完,否则redis集群无法工作
客户端如何定位数据
实例和哈希槽的对应关系的常见变化
1. 在集群中,实例有新增或删除,redis需要重新分配哈希槽
2. 为了负载均衡,redis需要把哈希槽在所有实例上重新分布一遍
带来的影响
哈希槽的重新分配会导致客户端无法感知到,它缓存的分配信息和最新分配信息不一致。
解决方案
重定向机制
1. 如果实例上没有该键值对映射的哈希槽,就会返回 MOVED 命令;客户端会更新本地缓存
2. 在迁移部分完成的情况下,会返回ASK;表明 Slot 数据还在迁移中;ASK还会把客户端请求最新实例返回给客户端,但客户端并不会更新本地缓存
集群选举原理<br>
影响redis性能的潜在因素
Redis 内部的阻塞式操作
集合全量查询&聚合操作<br>
优化方案:用scan命令分批读取数据,再在客户端进行聚合操作<br>
big key删除<br>
优化方案:使用UNLINK命令异步删除<br>
清空数据库
优化方案:<br>使用异步清空指令<br>FLUSHDB ASYNC <br>FLUSHALL AYSNC<br>
AOF同步写<br>
优化方案:将写回策略改成everysec
从库加载RDB文件<br>
优化方案:将主库的数据量控制在2~4GB<br>
CPU 核和 NUMA 架构的影响
波动响应延迟:<br>Redis 关键系统配置<br>
redis自身操作特性影响<br>
慢查询命令<br>
优化方案:查看慢查询日志或使用latency monitor工具,用其他高效命令替换慢查询命令
同一时间大量key过期
优化方案:在设置过期时间时,在后面加个随机数,避免同一时间大量key过期
文件系统<br>
AOF写回策略为always<br>
AOF重写
操作系统
内存swap
触发原因<br>
物理机器内存不足<br>
解决方案<br>
增加机器内存/使用redis集群<br>
避免redis和其他内存需求大的应用共享机器的情况<br>
查看swap的方式
内存大页<br>
关闭内存大页方式
查看是否开启了内存大页配置<br>cat /sys/kernel/mm/transparent_hugepage/enabled<br>
关闭内存大页配置<br>echo never /sys/kernel/mm/transparent_hugepage/enabled<br>
删除数据,内存占用率还是很高:<br>Redis 内存碎片
产生内存碎片原因
内因
内存分配器的分配策略 jemalloc<br>
外因
键值对大小不一样和删改操作
判断redis是否有内存碎片的方式<br>
INFO memory<br>
redis的内存利用率指标健康度<br>
mem_fragmentation_ratio 大于 1.5<br>这表明内存碎片率已经超过了 50%。一般情况下,这个时候,我们就需要采取一些措施来降低内存碎片率了<br>
mem_fragmentation_ratio 大于 1 但小于 1.5<br>这种情况是合理的。这是因为内存分配器是一定要使用的,分配策略都是通用的,不会轻易修改;<br>而外因由 Redis 负载决定,也无法限制。所以,存在内存碎片也是正常的。<br>
清理内存碎片方式
开启自动清理内存碎片配置
config set activedefrag yes(4.0-RC3 及以上版本)
相关的四个参数
内存清理开始的必要条件
active-defrag-ignore-bytes 100mb:表示内存碎片的字节数达到 100MB 时,开始清理<br>
active-defrag-threshold-lower 10:表示内存碎片空间占操作系统分配给 Redis 的总空间比例达到 10% 时,开始清理
控制清理操作占用的 CPU 时间比例的上、下限<br>
active-defrag-cycle-min 25<br>表示自动清理过程所用 CPU 时间的比例不低于 25%,保证清理能正常开展<br>
active-defrag-cycle-max 75<br>表示自动清理过程所用 CPU 时间的比例不高于 75%,一旦超过,就停止清理,从而避免在清理时,大量的内存拷贝阻塞 Redis,导致响应延迟升高<br>
Redis 缓冲区
内存回收
内存淘汰策略
LRU算法<br>
应用场景:关注数据的时效性<br>
核心思想:如果一个数据刚刚被访问,那么这个数据肯定是热数据,还会被再次访问
具体实现原理<br>
LFU算法
应用场景:关注数据的访问频次
核心思想<br>
LFU 策略中会从两个维度来筛选并淘汰数据:<br>一是,数据访问的时效性(访问时间离当前时间的远近);二是,数据的被访问次数
具体实现原理
count计数规则<br>
相关参数
lfu_log_factor(控制计数器值增加的速度)<br>
取不同值时,访问次数的变化,一般设置10<br>
lfu_decay_time(控制访问次数的衰减),建议设1
不淘汰(noeviction)<br>
进行淘汰
设置过期时间的数据中进行淘汰<br>
volatile-lru<br>
volatile-random
volatile-ttl
volatile-lfu(Redis 4.0 后新增)
所有数据内进行淘汰<br>
allkeys-lru<br>
allkeys-random
allkeys-lfu(Redis 4.0 后新增)
相关参数
Redis 选出的数据个数 N<br>CONFIG SET maxmemory-samples 100
设置缓存容量<br>CONFIG SET maxmemory 4gb<br>
设置缓存淘汰策略<br>maxmemory-policy allkeys-lru<br>
使用建议<br>
如果业务中有明显的冷热数据区分,优先使用 allkeys-lru 策略<br>
没有明显的冷热数据区分,建议使用 allkeys-random 策略
如果你的业务中有置顶的需求,可以使用 volatile-lru 策略,同时不给这些置顶数据设置过期时间<br>
缓存容量设置<br>结合实际应用的数据总量、热数据的体量,以及成本预算,把缓存空间大小设置在总数据量的 15% 到 30% 这个区间就可以
内存过期策略<br>
惰性删除策略
注意点
定期删除策略<br>
多个redis客户端并发读写问题
解决方案
使用原子性操作
使用lua脚本
使用分布式锁
分布式锁
实现redis单个节点实现分布式锁<br>
SETNX key value
存在的风险
1. 客户端执行了 SETNX 命令加锁之后,操作共享数据发生异常,结果一直没有执行释放锁动作;导致其他客户端获取不到锁
2. 如果客户端 A 执行了 SETNX 命令加锁后,假设客户端 B 执行了 DEL 命令释放锁,此时,客户端 A 的锁就被误释放了。如果客户端 C 正好也在申请加 锁,就 可以成功获得锁,进而开始操作共享数据。这样一来,客户端 A 和 C 同时在对共享数据进行操作,数据就会被修改错误
解决方案
针对风险1<br>给锁加一个过期时间;但无法防止其他客户端误删锁
针对风险2<br>使用 SET key value [EX seconds | PX milliseconds] [NX]加锁,<br>释放锁时先校验唯一值,防止其他线程误删锁(因为这里操作不具备原子性,需要使用 lua脚本),<br>但不能保证锁的可靠性,redis实例宕机就会导致客户端无法进行锁操作
SET key value [EX seconds | PX milliseconds] [NX]
基于多个 Redis 节点实现高可靠的分布式锁
分布式算法RedLock
运行原理
基于 Redis 使用分布锁的注意点
使用 SET $lock_key $unique_val EX $second NX 命令保证加锁原子性,并为锁设置过期时间
锁的过期时间要提前评估好,要大于操作共享资源的时间<br>
每个线程加锁时设置随机值,释放锁时判断是否和加锁设置的值一致,防止自己的锁被别人释放
释放锁时使用 Lua 脚本,保证操作的原子性
基于多个节点的 Redlock,加锁时超过半数节点操作成功,并且获取锁的耗时没有超过锁的有效时间才算加锁成功
Redlock 释放锁时,要对所有节点释放(即使某个节点加锁失败了),因为加锁时可能发生服务端加锁成功,由于网络问题,给客户端回复网络包失败的情 况,所以需要把所有节点可能存的锁都释放掉
使用 Redlock 时要避免机器时钟发生跳跃,需要运维来保证,对运维有一定要求,否则可能会导致 Redlock 失效。例如共 3 个节点,线程 A 操作 2 个节点 加锁成功,但其中 1 个节点机器时钟发生跳跃,锁提前过期,线程 B 正好在另外 2 个节点也加锁成功,此时 Redlock 相当于失效了
如果为了效率,使用基于单个 Redis 节点的分布式锁即可,此方案缺点是允许锁偶尔失效,优点是简单效率高
如果是为了正确性,业务对于结果要求非常严格,建议使用 Redlock,但缺点是使用比较重,部署成本高
事务
事务的三个阶段
使用MULTI命令开启事务;<br>
客户端把事务中本身要执行的具体操作发送给服务器端,redis实例会把这些命令暂存到一个命令队列;
使用EXEC 命令提交事务。
redis事务如何保证原子性
命令入队时就报错,会放弃事务执行,保证原子性<br>
命令入队时没报错,实际执行时报错,不保证原子性
EXEC 命令执行时实例故障,如果开启了 AOF 日志,可以保证原子性
redis事务如何保证一致性
命令入队时就报错,<br>在这种情况下,事务本身就会被放弃执行,可以保证数据库的一致性
命令入队时没报错,实际执行时报错,<br>在这种情况下,有错误的命令不会被执行,正确的命令可以正常执行,也不会改变数据库的一致性
EXEC 命令执行时实例发生故障,<br>这种情况下,如果没有开启aof或rdb,redis实例故障宕机重启后,数据都没有了,数据库是一致的;<br>如果开启了,实例宕机重 启后会根据aof/rdb恢复,可以保证数据库一致性
redis事务如何保证隔离性
并发操作在 EXEC 命令前执行,隔离性的保证要使用 WATCH 机制来实现
并发操作在 EXEC 命令后执行,隔离性可以保证
redis无法保证持久性<br>
相关命令<br>
异常处理
缓存和数据库数据一致性问题<br>
缓存雪崩
原因<br>
大量数据同时过期<br>
缓存实例宕机<br>
解决方案
给缓存数据的过期时间加上小的随机数,避免同时过期<br>
服务降级<br>
服务熔断
请求限流<br>
redis缓存主从集群
缓存击穿<br>
原因
访问非常频繁的热点数据过期<br>
解决方案<br>
不设置过期时间
基于 redis or zookeeper 实现互斥锁,等待第⼀个请求构建完缓存之后,再释放锁,进⽽其它请求才能通过该 key 访问数据<br>
缓存穿透<br>
原因
缓存和数据库中都没有要访问的数据<br>
解决方案
缓存空值
使用布隆过滤器<br>
工作原理
请求入口前端做合法参数校验
缓存污染
原因
在一些场景下,有些数据被访问的次数非常少,甚至只会被访问一次。<br>当这些数据服务完访问请求后,如果还继续留存在缓存中的话,就只会白白占用缓存空间
解决方案
使用LFU内存淘汰策略<br>
数据倾斜
数据量倾斜<br>
存在bigkey
解决方案
业务层避免bigkey<br>
把集合类型的bigkey拆分成多个小集合,分散保存<br>
查询bigkey的方式
使用 ./redis-cli --bigkeys 查找bigkey
使用rdb-tools工具分析
Slot手工分配不均<br>
解决方案:制定运维规范,避免把过多的slot分配在同一实例上<br>
使用Hash Tag,导致大量数据在同一个Slot<br>
解决方案:尽量避免使用Hash Tag
数据访问倾斜<br>
存在hotkey
解决方案
热点数据多副本的方法,在每一个热点key增加一个随机前缀,然后分散在不同的分片上<br>
增加多级缓存
查询hotkey的方法
–hotkeys 配合 redis-cli 命令行工具来探查热点 Key
使用规范
键值对使用规范
规范一:key 的命名规范
把业务名作为前缀,然后用冒号分隔,再加上具体的业务数据名
规范二:避免使用 bigkey
String类型的数据大小控制在10KB以下<br>
集合类型的元素个数控制在 1 万以下<br>
规范三:使用高效序列化方法和压缩方法
json序列化
gzip/snappy压缩方法<br>
数据保存规范<br>
规范一:使用 Redis 保存热数据
规范二:不同的业务数据分实例存储
规范三:在数据保存时,要设置过期时间
规范四:控制 Redis 实例的容量(2~6GB)
命令使用规范
规范一:线上禁用部分命令
KEYS
FLUSHALL
FLUSHDB
规范二:慎用 MONITOR 命令
规范三:慎用全量操作命令
优化建议
可以使用 SSCAN、HSCAN 命令分批返回集合中的数据
把一个大的 Hash 集合拆分成多个小的 Hash 集合<br>
如果集合类型保存的是业务数据的多个属性,而每次查询时,也需要返回这些属性;<br>可以使用String类型操作
运维工具
最基本的监控命令:INFO 命令
数据迁移工具 Redis-shake
面向 Prometheus 的 Redis-exporter 监控
集群管理工具 CacheCloud
redis6.0 新特性
0 条评论
下一页