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