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