redis
2023-10-27 13:50:56 21 举报
AI智能生成
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% 这个区间就可以
使用建议
内存淘汰策略
注意点
惰性删除策略
定期删除策略
内存过期策略
内存回收
使用原子性操作
使用lua脚本
使用分布式锁
多个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 使用分布锁的注意点
使用MULTI命令开启事务;
客户端把事务中本身要执行的具体操作发送给服务器端,redis实例会把这些命令暂存到一个命令队列;
使用EXEC 命令提交事务。
事务的三个阶段
命令入队时就报错,会放弃事务执行,保证原子性
命令入队时没报错,实际执行时报错,不保证原子性
EXEC 命令执行时实例故障,如果开启了 AOF 日志,可以保证原子性
redis事务如何保证原子性
命令入队时就报错,在这种情况下,事务本身就会被放弃执行,可以保证数据库的一致性
命令入队时没报错,实际执行时报错,在这种情况下,有错误的命令不会被执行,正确的命令可以正常执行,也不会改变数据库的一致性
EXEC 命令执行时实例发生故障,这种情况下,如果没有开启aof或rdb,redis实例故障宕机重启后,数据都没有了,数据库是一致的;如果开启了,实例宕机重 启后会根据aof/rdb恢复,可以保证数据库一致性
redis事务如何保证一致性
并发操作在 EXEC 命令前执行,隔离性的保证要使用 WATCH 机制来实现
并发操作在 EXEC 命令后执行,隔离性可以保证
redis事务如何保证隔离性
redis无法保证持久性
相关命令
事务
缓存和数据库数据一致性问题
大量数据同时过期
缓存实例宕机
原因
给缓存数据的过期时间加上小的随机数,避免同时过期
服务降级
服务熔断
请求限流
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
数据访问倾斜
数据倾斜
异常处理
把业务名作为前缀,然后用冒号分隔,再加上具体的业务数据名
规范一:key 的命名规范
String类型的数据大小控制在10KB以下
集合类型的元素个数控制在 1 万以下
规范二:避免使用 bigkey
json序列化
gzip/snappy压缩方法
规范三:使用高效序列化方法和压缩方法
键值对使用规范
规范一:使用 Redis 保存热数据
规范二:不同的业务数据分实例存储
规范三:在数据保存时,要设置过期时间
规范四:控制 Redis 实例的容量(2~6GB)
数据保存规范
KEYS
FLUSHALL
FLUSHDB
规范一:线上禁用部分命令
规范二:慎用 MONITOR 命令
可以使用 SSCAN、HSCAN 命令分批返回集合中的数据
把一个大的 Hash 集合拆分成多个小的 Hash 集合
如果集合类型保存的是业务数据的多个属性,而每次查询时,也需要返回这些属性;可以使用String类型操作
优化建议
规范三:慎用全量操作命令
命令使用规范
使用规范
最基本的监控命令:INFO 命令
数据迁移工具 Redis-shake
面向 Prometheus 的 Redis-exporter 监控
集群管理工具 CacheCloud
运维工具
redis6.0 新特性
redis
0 条评论
回复 删除
下一页