Redis
2024-01-08 11:47:35 2 举报
AI智能生成
登录查看完整内容
Redis是一个开源的,基于内存的数据结构存储系统,可以用作数据库、缓存和消息中间件。它支持多种数据结构,如字符串、列表、集合、散列和有序集合,并提供了丰富的操作命令。Redis具有高性能、高可用性和易扩展性的特点,广泛应用于互联网领域。它的设计目标是提供快速、可靠的数据访问服务,以满足大规模数据处理的需求。Redis支持主从复制、分片和事务等高级功能,使得它可以在复杂的应用场景中发挥重要作用。通过使用Redis,开发者可以轻松实现数据的高速读写和高效管理,从而提高应用程序的性能和可扩展性。
作者其他创作
大纲/内容
数数组和压缩列表作为底层数据结构的优势是什么?
Redis 基本 IO 模型中还有哪些潜在的性能瓶颈?
AOF 重写过程中有没有其他潜在的阻塞风险?
为什么主从库间的复制不使用 AOF?
在主从切换过程中,客户端能否正常地进行请求操作呢?
replication buffer 和 repl_backlog_buffer 的区别?
哨兵实例是不是越多越好? 增大down-after-milliseconds是否可以减少误判
为什么 Redis 不直接用一个表,把键值对和实例的对应关系记录下来?而使用哈希槽的方式?
Redis 什么时候做 rehash?
缓存雪崩可以使用服务熔断、服务降级、请求限流三种方法来应对,那这三种方法是否可以应对缓存穿透问题?
在执行事务时,如果 Redis 实例发生故障,而 Redis 使用的是 RDB 机制,那么,事务的原子性还能得到保证吗?
假设我们将 min-slaves-to-write 设置为 1,min-slaves-max-lag 设置为 15s,哨兵的 down-after-milliseconds 设置为 10s,哨兵主从切换需要 5s,而主库因为某些原因卡住了 12s。此时,还会发生脑裂吗?主从切换完成后,数据会丢失吗?
为什么官方声称的redis cluster集群规模不建议超过1000?
答疑篇
RedisObject对象头 16bytes
key
RedisObject对象头 16bytes
value
dictEntry(本身24,内存分配器分配2^n,所以是32)
采用String一组键值对内存占用(16+16+32=64)
entry
采用(hash)字典
问题:键值对需求采用String造成内存增加
固定长度16bytes
Redis对象头结构体
分配内存空间64bytes时,留给数组内容content的空间大小为64-16(对象头)-3(SDS另外三个字段)-1(内容结尾的\\0)
字符串SDS(Simple Dynamic String)结构体
int(长度<8)
embstr(44>长度>8)
raw(长度>44)
存储方式
字符串内部
(String)字符串底层实现
dictEntry
真正的hashtable结构:dictht
扩容时使用渐进式rehash多了一层:dict
结构
渐进式rehash
扩容
只有一个条件:元素个数<数字长度*10%,不考虑是否bgsave
缩容
哈希表
zlbytes:记录整个列表的大小 4bytes
zltail:记录尾结点与开头的偏移量 4bytes
zllen:记录节点数量
记录前一个节点的长度,单位:bytes
前一节点长度>254?1byte :5byte
1/5 byte pre_len
自身长度
4 byte len
记录content里保存数据的类型和长度
1 byte encoding
节点的值
content
zlend:特殊值0xFF,标记压缩列表的末端 1bytes
ziplist
压缩列表
(hash)字典底层实现
键值对的保存
sdiff key1 [ke2 key3 ……]
差集
sinter key1 [key2 key3 ……]
交集
sunion key1 [key2 key3 ……]
并集
set
聚合统计
List
分页优先考虑Sorted Set
Sorted Set
排序统计
getbit key offset
setbit key offset 0/1
功能:统计1的个数
场景:统计签到了多少天
bitcount key [start end]
功能:查找指定范围内第一个0/1
场景:第1次签到的时间
bitpos key [start end] 0/1
功能:按位与
场景:连续签到统计
bittop
bitmap
二值统计
千万级别时内存占用太大
hash
pfadd key value [value2 ……]
pfcount key
pfmerge newkey [oldkey1 oldkey2 ……]
HyperLogLog
基数统计
1、集群模式使用多个key聚合的命令,由于key分布在不同的实例上,多个实例无法做聚合运算
2、建议把统计数据与业务数据拆分开,实例单独部署,防止做统计操作时影响业务
统计总结
集合类的使用
1、对经度和维度分别编码
2、把经纬度编码合成一个编码
GeoHash算法:把二维数组映射到一维数组
底层实现:zset
geoadd 集合key 精度 纬度 key
添加数据
geodist 集合key key1 key2 km/m/ml/ft
计算距离
geppos 集合key key1 [key2 ……]
获取经纬度
geohash 集合key key
获取geohash编码
georadiubymenber 集合key key number km/m [withcoord] [withdist] [withhash] [count n desc]
获取附近元素
命令
1、数据量过大会导致redis阻塞
2、建议Geo单独部署,不要使用集群环境
3、对Geo数据按国家、省、市、区进行拆分,降低单个zset集合的大小
注意事项
GEO
消息顺序
幂等性
保证消息可靠性
要求
LPUSH key value1 [value2]
BRPOP key1 [key2] timeout
BRPOPLPUSH source targetlist timeout
基本命令
缺点:无法多消费者同时消费
基于List的解决方案
基于stream的解决方案
消息队列解决方案
数据类型
Redis采用多路复用,不会导致阻塞
网络IO
操作复杂度为O(N)的操作:比如集合元素的全量查询、聚合统计(交集、并集、差集)
bigkey的删除操作
清空数据库(FLUSHDB、FLUSHALL)
复杂度高的增删改查操作
客户端
AOF日志同步写(注意只是这条写回策略)
磁盘
从库接收RDB文件后会FLUSHDB,见第三条
从库清空数据库后要把RDB加载到内存
主从节点
Redis Cluster方案,实例增删时,数据在不同的实例间迁移,遇到bigkey会阻塞
切片集群
交互对象中的操作
Redis 主线程启动后,会使用操作系统提供的 pthread_create 函数创建 3 个子线程,分别由它们负责 AOF 日志写操作、键值对删除以及文件关闭的异步执行。
配置参数
a)当Hash/Set底层采用哈希表存储(非ziplist/int编码存储)时,并且元素数量超过64个
b) 当ZSet底层采用跳表存储(非ziplist编码存储)时,并且元素数量超过64个
c) 当List链表节点数量超过64个(注意,不是元素数量,而是链表节点的数量,List的实现是在每个节点包含了若干个元素的数据,这些元素采用ziplist存储)
“尝试”异步释放什么时候会变成真正异步执行
惰性删除(lazy-free)
AOF配制成everysec
采用子线程的场景
异步的子线程机制
如何避免主线程阻塞
逻辑核1(共享L1、L2缓存)
逻辑核2(共享L1、L2缓存)
物理核1(共享L3缓存)
物理核2(共享L3缓存)
……
CPU Socket
CPU Socket
cpu
问题:Redis实例被频繁调度到不同的核上运行,就需要重新从L3缓存甚至内存加载数据
1、把Redis实例核CPU的核绑定
2、网络中断程序绑核时和Redis绑定在同一个CPU Socket
解决方案:绑核
1、一个Redis绑定在一个物理核上(两个逻辑核)
2、优化Redis源码【打扰了】
绑核的风险和解决方案
cpu多核(NUMA)对Redis性能的影响
Redis和CUP
1、查看Redis响应延迟
2、基于当前环境基线性能判断,如果运行延迟是极限性能的2倍以上,就是变慢了
如何判断Redis是否变慢
Set、List等集合类型的复杂操作
产生原因:
1、通过slowlog命令或者latency monitor工具找到变慢的请求
2、使用SSCAN多次迭代返回需要的数据,排序、交集、并集等操作放到客户端完成
解决办法:
1、慢查询命令
大量key同时过期,Redis删除操作是阻塞的
避免同时过期
2、过期key操作
Rehash时不会漏key
缩容会导致重复key
SCAN
Hash/Set/Sorted Set在元素较少底层采用intset/ziplist存储时,会无视$count直接返回所有数据
HSCAN/SSCAN/ZSCAN
问题:哪些命令可以代替keys
自身操作特性
1、AOF落盘策略配置为always时,不会使用后台子线程来落盘,此时是同步操作
2、如果遇到AOF重写,造成磁盘IO过大,fsync被阻塞,此时主线程写入新请求需要AOF落盘,发现上次fsync还没返回,就会被阻塞
1、业务允许数据丢失的情况下
2、使用固态硬盘
AOF重写导致IO压力阻塞落盘的fsync
文件系统
系统内存不足
增加机器内存/使用Redis分片集群/Redis迁移到单独的机器运行
排查办法:
内存swap
AOF重写和RDB生成时,由于写时复制机制,在内存大页时会导致大量拷贝,影响Redis正常的内存访问操作,导致变慢
会产生的问题:
cat /sys/kernel/mm/transparent_hugepage/enabled
echo never /sys/kernel/mm/transparent_hugepage/enabled
解决办法
内存大页
操作系统
病因分析
获取当前环境基线性能
是否有慢查询命令
是否有key同时过期
是否存在bigkey
Redis AOF(appendfsync)配置级别是什么
Redis实例内存是否过大?有没有发生swap
Redis实例运行环境是否开启了内存大页机制
是否使用了多核CPU或者NUMA架构的机器运行Redis实例
相互印证
变慢时常用checklist
关于Redis变慢
1、本身内存分配为了减少分配次数,都是按固定大小划分内存空间,就会有冗余
2、删除键值对,释放内存,内存如果够大会被下次分配给新键值对,直到剩下一部分不够分配给新键值对了,就成了永远没法使用的碎片
info memory 查看mem_fragmentation_ratio
1、1到1.5是合理的
2、大于1.5可以考虑采取措施降低碎片率
小于1说明Redis使用swap了,会导致性能下降
应对经验:
查看碎片率
1、重启(基本不可取)
第一步:启动自动内存碎片清理
第二步:配置自动内存清理的条件,同时满足才会开始清理
第三部:设置两个参数控制清理操作占用的CPU时间比例的上下限
2、4.0rc版本以后
清理内存碎片
内存碎片
避免客户端和服务器端发送处理速度不匹配,服务器端为每个客户端都设置了一个输入缓冲区和输出缓冲区
1、写入了bigkey直接导致溢出
2、服务器端处理速度过慢,如:主线程出现了间限性阻塞
溢出原因:
查看输入缓冲区内存:client list
1、占用内存总量超过maxmemory配置项,触发数据淘汰,淘汰不了直接oom
2、单个客户端溢出(超过1G)会被直接关闭
溢出危害
1、调大缓冲区,Redis客户端输入缓冲区上限1G,代码写死,无法调整【行不通】
2、数据发送上避免bigkey,数据处理上避免主线程阻塞
输入缓冲区:
1、大小为16k的固定缓冲空间,暂存ok响应和出错信息
2、可以动态增加的缓冲区
构成
1、服务器端返回bigkey的大量结果
2、执行了MONITOR命令
3、缓冲区大小设置不合理
导致溢出的原因:
1、生产禁止持续使用MONITOR
普通客户端:阻塞式发送,可以对缓冲区不作限制
订阅客户端:不属于阻塞式发送,需要设置缓冲区大小限制
2、设置缓冲区大小的上限阈值、设置输出缓冲区持续写入数据的数量和时间阈值
输出缓冲区:
客户端——服务端
作用:主节点给从节点同步RDB文件时,新增的写命令都保存在从节点的输出缓冲区,等RDB传输完,再发送给从节点同步
发生溢出,主节点会立即关闭和从节点进行复制的连接,导致全量复制失败
2、根据主节点数据量大小、写负载压力、本身内存设置复制缓冲区大小
3、主节点复制缓冲区的内存开销是每个从节点客户端输出缓冲区占用内存的总和,所以需要控制主从连接节点的个数
防止溢出
全量同步:缓冲区replication buffer
作用:主库会记录自己写到的位置,每一个从库都有自己读到的位置,重连之后,如果从库读到的位置未被覆盖,就做增量同步
溢出危害:从库读的位置被覆盖,从而导致从库需要重新全量同步
解决办法:repl_backlog_size合理设置参数的值
增量同步:复制积压缓冲区 repl_backlog_size
主从集群
1、网络连接关闭:针对输入输出缓冲区
2、命令数据丢失:针对复制积压缓冲区
溢出导致的问题
1、命令数据过大:普通客户端避免bigkey,全量同步输出缓冲区避免RDB过大
2、数据处理较慢:介绍Redis主线程阻塞操作
3、缓冲区过小:client-output-buffer-limit设置合理的输出缓冲区,输入缓冲区无解
防止溢出解决方案
总结
缓冲区
最终一致性方案
分布式事务方案
数据库和缓存一致性解决方式
从双写一致性问题来说,不管是数据库还是缓存的先后都可能不一致同步策略 中最常用的还是先写库后删缓存。异步策略可以采用解耦,将修改缓存放入到队列,但维护逻辑会更加复杂
使用redis做只读、和读写缓存的区别
使用如下命令设置缓存的最大值,超过后就会触发淘汰机制了CONFIG SET maxmemory 4gb
在设置了过期时间的数据中进行淘汰,包括 volatile-random、volatile-ttl、volatile-lru、volatile-lfu
在所有数据范围内进行淘汰,包括 allkeys-lru、allkeys-random、allkeys-lfu
缓存最大值配置及淘汰策略(目前共7种淘汰)noeviction 代表不淘汰,添加数据会报错
影响Redis性能的5大因素
秒杀场景下,库存扣减操作不能实时使用数据库来操作,可以想想为什么?
1. 拦截住大部分的请求,只有少量请求实际到达后端
2. 前端页面CDN 静态化处理
3.库存信息过期时间处理,为防止过期突然请求到数据库导致缓存击穿,不要给库存设置过期时间
4.数据库处理订单异常,库存已扣减,但订单处理异常的情况下,需要增加订单重试功能,保证订单最终能成功处理,保障一致性
秒杀要点:
方式1:使用lua脚本,将查询库存余量,判断,扣减操作封装为一个脚本
方式2:使用分布式锁
秒杀场景中的应用
(redis、memcache)通过网络框架以 Socket 通信的形式对外提供键值对操作
(rocksDB)通过函数库调用的方式供外部应用使用
访问
Hash
B+树
字典树
跳表
索引的作用是让键值数据库根据 key 找到相应 value 的存储位置,进而执行操作引出索引数据结构......
索引
DELETE需要删除键值对,并释放相应的内存空间,这个过程由分配器完成
操作逻辑
redis内存分配器提供了多种选择
AOF
RDB
redis的持久化
存储
一个键值数据库包括了访问框架、索引模块、操作模块和存储模块四部分
simpleKV还缺少的模块:集群横向扩展、数据分片存储、内存过载的淘汰策略等等。。。
键值数据库的基本功能,以simpleKV推到redis
内存数据库
hash表、跳表等高效数据结构
io多路复用
redis快的原因
数据结构和底层数据对应关系
2、装载因子>=5
什么时候做rehash
整数数组和压缩列表在查找时间复杂度方面并没有很大的优势,那为什么 Redis 还会把它们作为底层数据结构呢?
redis的数据结构
只是在网络IO和键值读写上来说是单线程的。持久化,集群同步、淘汰等是由其他线程完成的
redis真的只是单线程吗?
IO多路复用有时间仔细看一下
单线程为什么这么快?
a、操作bigkey:写入bigkey分配内存已经删除时释放内存是耗时操作
b、复杂度过高的命令:范围查询,并且查询的数据量大
c、大量key集中过期
d、执行淘汰策略:内存不够时需要淘汰key,耗时长
e、AOF刷盘开启always模式:会导致频繁写入磁盘,写磁盘是个慢操作
f、生成RDB:fork子进程生成数据快照时,如果实例过大,也会阻塞
1、(主线程)耗时的操作
2、并发量大,单线程读写IO瓶颈
造成redis性能瓶颈的原因
redis的高性能IO模型
子主题
记录的是redis收到的每一条命令
避免进行语法的检查
不会阻塞当前的写操作。
为什么先写内存后记录日志?
对比数据库的(write ahead log)WAL
重写不会阻塞主线程,fork出bgrewriteaof线程完成
fork时会把主线程内存页表拷贝一份给bgrewriteaof子进程
新的操作会写入原AOF日志和AOF重写日志的缓冲区,等到根据拷贝重写完成,再把缓冲区的写入重写AOF,然后替代旧的AOF
一个拷贝,两处日志
AOF重写机制
a、fork拷贝内存页表阻塞整个进程
AOF日志重写时会有哪些风险
1、AOF文件过大,导致写入变慢,阻塞线程
同步写回:每个写命令执行完,立马同步地将日志写回磁盘;
Always
每秒写回:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,每隔一秒把缓冲区中的内容写入磁盘
Everysec
操作系统控制的写回:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,由操作系统决定何时将缓冲区内容写回磁盘
No
写回策略
2、写完内存之后AOF还没落盘就宕机导致数据丢失
两个风险
恢复时较慢,需要一条一条的执行命令
缺点
AOF日志(Append Only File)【appendonly yes 开启】
记录某一时刻,redis中的所有内容(全量快照),数据恢复时很快。
save在主线程中执行,会导致阻塞
bgsave会fork一个子进程,专门用于写入RDB。避免主线程的阻塞,也是RDB文件生成的默认值,
save和bgsave
redis 4.0提出混合使用AOF日志和内存快照
最佳实践
RDB(Redis DateBase)快照【默认开启】
数据尽量少丢失
从库给主库发送【psync 主库runID 复制进度offset】
主库用【FULLRESYNC {runID} {offset}】响应,表示采用全量复制
阶段一:
主库执行bgsave生成RDB文件,然后发送给从库
从库接收RDB文件后,清空当前数据库,然后加载RDB文件
阶段二:
replication buffer记录主库RDB文件生成后收到的写操作
把replication buffer中的修改操作发给从库,从库重新执行这些操作
阶段三
在从库执行【replicaof 主库ip 主库端口】 指定要同步的主库
主从级联模式分担全量复制时的压力
全量同步
网络断连或者阻塞之后就会有下面的增量同步
基于长连接的命令传播
通过client-output-buffer-limit限制buffer大小
replication buffer
重连之后,从库根据自己的slave_repl_offset和主库的master_repl_offset决定全量同步还是增量同步
repl_backlog_size 参数需要适当增大,以减少全量同步
repl_backlog_buffer
增量同步
主从库模式
如何监控:周期性地发送ping给各个主从库
主观下线:如果在down-after-milliseconds配置的时间内未回复或pong错误,则判断“主观下线”
客观下线:多实例的哨兵集群,如果超过一半/(配置文件quorum值)判断“主观下线”,那么就最终判定为“客观下线”,开始下一步选主
减少误判
监控
配置项down-after-milliseconds 主从库断连的最大连接时间,如果发生次数超过了10次,就被筛掉
筛选
配置项slave-priority可以给不同的从库设置不同的优先级,某个从库优先级最高就直接是新主库
1、优先级最高的从库得分
根据repl_backlog_buffer中的slave_repl_offset最接近master_repl_offset的就是新主库
2、和旧主库同步程度最接近的从库得高分
实在上面两点都一样的情况下,ID号最小的从库会被选为新主库
3、ID号最小的从库高分
打分
选主
选主流程结束后,使用通知告知各节点,利用replicaof 形成新的主从关系
通知
哨兵,一种特殊的Redis进程
配置文件配置主库信息,以和主库建立联系
sentinel——主库
基于pub/sub(发布订阅)机制,在主库上发现其他sentinel的信息并建立连接
sentinel——sentinel
每个sentinel都向主库发送INFO命令,主库就会把从库列表返回给哨兵
sentinel——从库
+sdown(实例进入“主观下线”状态)
-sdown(实例退出“主观下线”状态)
+odown(实例进入“客观下线”状态)
-down(实例退出“客观下线”状态)
主库下线事件
+salve-reconf-sent(哨兵发送SLAVEOF命令重新配置从库)
+slave-reconf-inprog(从库配制了新主库,单尚未进行同步)
+slave-reconf-done(从库配制了新主库,完成了同步)
从库重新配置事件
+switch-master(主库地址发生变化)
新主库切换
客户端通过【SUBSCRIBE */{具体事件名}】订阅事件,了解比如主从切换的进度
sentinel——客户端
建立连接
sentinel实例判断主库“主观下线”后,给其他实例发送is-master-down-by-addr命令,其他实例回复Y/N,当收到的票数大于quorum时,此实例会标记主库为“客观下线”
1、从“主观下线”到确认“客观下线”
此sentinel实例标记主库为“客观下线”后,马上发起leader选举,并给自己投一票,此时每个收到信息,并且未投票的sentinel实例,都会给他投票。满足两个条件1、拿到半数以上赞成票2、拿到的票数大于等于配置文件中的quorum时,就可以成为leader
2、确认“客观下线”后,进行leader选举
选leader执行主从切换
sentinel机制与用法
哨兵集群
方式:使用大内存主机
优点:实施起来简单、直接
1、主线程fork子线程时由于内存太大会阻塞
2、受到硬件和成本的限制
1、 纵向扩展
方式:切片集群
1、数据在多个实例如何分布
2、客户端如何找到数据所在实例
问题
2、横向扩展
redis数据量增大
cluster create创建集群,槽会自动平均分布在集群实例上
自动分配哈希槽
cluster meet命令手动建立实例间的链接
cluster addslots指定每个实例上的哈希槽个数
手动分配时需要把16384个槽都分完,否则Redis集群无法正常工作
手动分配哈希槽
数据切片和实例如何对应
1、Redis实例会把自己的哈希槽信息发给相连的其他实例(每个实例都会有全部的哈希槽信息)
2、客户端会把哈希槽信息缓存本地,客户端请求时会计算对应的hash槽然后给相应实例发送请求
数据不变时
1、客户端给实例发送数据读写操作,实例没有相应数据
2、实例返回MOVE命令响应结果,里面包含了新实例地址
3、客户端向新地址发送请求,同时更新本地缓存
重定向机制
1、客户端给实例发送数据读写操作,实例里没有相应数据,且实例数据迁移一半,未完成迁移
2、实例返回ASK报错信息,指向新实例地址
3、客户端给新实例发送ASKING信息,让实例允许执行接下来的操作,然后再发送操作命令
4、此次操作不会更新客户端本地缓存
ASK机制
数据变化时
客户端定位数据
Redis Cluster方案
Codis方案
不支持在线扩容
Twenproxy方案
切片集群(数据分片存储)
集群数据同步
服务尽量少中断
redis高可靠
基础概念
对于数据新增不会存在该问题,只发生在数据删改时,此时需要修改数据库,删除缓存
由于分两步操作,总会有失败的可能性,为了保证数据库是最新,通常采用先修改数据库,后删除缓存(个人认为是否删除缓存)可以视情况而定,如果缓存逻辑简单,不需要计算,也是可以直接修改缓存的
由于缓存删除失败会导致数据不一致,可以将删除操作解耦,放入消息队列进行重试,一定次数后预警;
在并发情况下,哪怕两个操作都成功了,也可能因为其他线程的读取顺序问题导致不一致
分布式锁,用于解决写并发:(写+写并发:线程A和线程B同时更新同一条数据,更新数据库的顺序是先A后B,但更新缓存时顺序是先B后A,这会导致数据库和缓存的不一致),同一时间只允许一个线程修改库和更新缓存。
并发情况下的不一致
缓存与数据库不一致
返回空值或缺省值
使用布隆过滤器快速判断数据是否存在,避免从数据库中查询数据是否存在,减轻数据库压力
缓存中不存在,恶意的频繁访问,穿过缓存读数据
缓存穿透
热点数据的频繁访问,突然过期;
解决方法:热点数据不设置过期时间,或者取数据时使用分布式锁,同一时间只有一个线程读数据
缓存击穿
避免大量设置相同的过期时间,可以在固定的基础上加一个随机时间
使用服务降级来应对雪崩,例如非重要情况直接返回,重要的任保留
大量键值同一时间过期导致
此时缓存请求会全部落到数据库上,应对方案是采取服务熔断,或者限流。尝试恢复缓存实例
做好应对方案,多实例部署
缓存实例故障导致
缓存雪崩
缓存异常
Redis 是用 RedisObject 结构来保存数据的,RedisObject 结构中设置了一个 lru 字段,用来记录数据的访问时间戳
redis并没有使用链表来维护LRU,而是简单的在RedisObject实体中维护lru字段,根据时间戳大小来淘汰数据
当触发缓存淘汰时,使用的是随机抽样,在选中的key中,淘汰掉 lru 时间戳最小的
由于LRU只根据判断时间,当遇到扫描式的单词查询时,效果不佳。
LRU最近最久未使用
LFU会先根据访问次数判断,淘汰次数最少的,当次数相同时,淘汰掉时间更小的
Redis 在实现 LFU 策略的时候,只是把原来 24bit 大小的 lru 字段,又进一步拆分成了两部分。其中时间戳占16位,访问次数占8位(最大255)
当 LFU 策略筛选数据时,Redis 会在候选集合中,根据数据 lru 字段的后 8bit 选择访问次数最少的数据进行淘汰。当访问次数相同时,再根据 lru 字段的前 16bit 值大小,选择访问时间最久远的数据进行淘汰
由于访问次数只有8位,最大值也就255,所以redis并不是每访问一次+1,而是根据配置项lfu_log_factor 指数来调整,
LFU最近最少使用,在LRU的基础上添加了访问次数判断。
redis的LRU和LFU
将多个命令合为一个执行(也称单命令操作);如increase,decrease等当需要对类似库存值的属性进行增减时,INCR/DECR 命令可以直接作为单命令运行
lua 脚本尽量只编写通用的逻辑代码,避免直接写死变量。变量通过外部调用方传递进来,这样 lua 脚本的可复用度更高
建议先使用SCRIPT LOAD命令把 lua 脚本加载到 Redis 中,然后得到一个脚本唯一摘要值,再通过EVALSHA命令 + 脚本摘要值来执行脚本,这样可以避免每次发送脚本内容到 Redis,减少网络开销。
将多个操作编写到 lua 脚本中,已脚本为单位执行(疑问:脚本是在单机器上运行还是多机器?)
例如需要限制客户端一分钟内的访问次数,可以使用 ip 作为key,次数作为value;每访问一次 value+1由于 lua 脚本是原子性的,第一次执行时,设置好过期时间为60s,后续在增减操作中判断是否超过阈值。限制客户端的访问
单命令操作的适用范围较小,lua适用范围更广
无锁的原子操作
加锁:SETNX命令,该命令在执行时会判断键值对是否存在,如果不存在,就设置键值对的值,如果存在,就不做任何设置
解锁: 执行完业务逻辑后,使用 DEL 命令删除锁变量。这里存在删除失败的场景,所以需要设置过期时间,以防止死锁出现;过期时间需要根据业务操作时间来确定,需要设置的稍大一些
单节点实现分布式锁:
让客户端和多个独立的 Redis 实例依次请求加锁,如果客户端能够和半数以上的实例成功地完成加锁操作,那么我们就认为,客户端成功地获得分布式锁了,否则加锁失败。
redlock 加锁详细步骤(释放同理)
redlock时钟跳跃问题
多节点实现分布式锁, RedLock思想(单节点下,宕机后会导致锁失败),基于java的实现称为Redission
1、使用 SET $lock_key $unique_val EX $second NX 命令保证加锁原子性,并为锁设置过期时间
2、锁的过期时间要提前评估好,要大于操作共享资源的时间
3、每个线程加锁时设置随机值,释放锁时判断是否和加锁设置的值一致,防止自己的锁被别人释放
4、释放锁时使用 Lua 脚本,保证操作的原子性
5、基于多个节点的 Redlock,加锁时超过半数节点操作成功,并且获取锁的耗时没有超过锁的有效时间才算加锁成功
6、Redlock 释放锁时,要对所有节点释放(即使某个节点加锁失败了),因为加锁时可能发生服务端加锁成功,由于网络问题,给客户端回复网络包失败的情况,所以需要把所有节点可能存的锁都释放掉
7、使用 Redlock 时要避免机器时钟发生跳跃,需要运维来保证,对运维有一定要求,否则可能会导致 Redlock 失效。例如共 3 个节点,线程 A 操作 2 个节点加锁成功,但其中 1 个节点机器时钟发生跳跃,锁提前过期,线程 B 正好在另外 2 个节点也加锁成功,此时 Redlock 相当于失效了
8、如果为了效率,使用基于单个 Redis 节点的分布式锁即可,此方案缺点是允许锁偶尔失效,优点是简单效率高
9、如果是为了正确性,业务对于结果要求非常严格,建议使用 Redlock,但缺点是使用比较重,部署成本高
基于 Redis 使用分布锁的注意点:
概念分析
延迟重启场景
分布式锁
并发访问控制
MULTI、EXEC命令,前者用于声明事务开启,在MULTI之后声明的都会加到命令队列中 ,后者用于提交命令,将整个队列一起执行。
,Redis 中并没有提供回滚机制。虽然 Redis 提供了 DISCARD 命令,但是,这个命令只能用来主动放弃事务执行,把暂存的命令队列清空,起不到回滚的效果
在执行 EXEC 命令前,客户端发送的操作命令本身就有错误(如语法错误)。不执行,保证原子性
事务操作入队时,命令和操作的数据类型不匹配,但 Redis 实例没有检查出错误。不保证,正确的那个命令会执行。
在执行事务的 EXEC 命令时,Redis 实例发生了故障,导致事务执行失败。如果 Redis 开启了 AOF 日志,那么,只会有部分的事务操作被记录到 AOF 日志中。我们需要使用 redis-check-aof 工具检查 AOF 日志文件,这个工具可以把未完成的事务操作从 AOF 文件中去除,此时可以保证原子性
原子性
一致性可以保证,(不管是否开启了持久化)
并发操作在 EXEC 命令前执行,此时,隔离性的保证要使用 WATCH 机制来实现,否则隔离性无法保证;
并发操作在 EXEC 命令后执行,此时,隔离性可以保证
隔离性
持久性,无法保证。AOF 和 RDB 两种策略都存在数据丢失情况
事务
定期删除策略
惰性淘汰策略(访问时才进行淘汰,但新版本已经不会影响,已过期的会返回空)
EXPIRE 和 PEXPIRE(设置一段时间后过期,遇到主从全量复制时,会导致从库的过期时间延后)
EXPIREAT 和 PEXPIREAT(设置在指定时间点过期,没有上述问题,业务上需要计算时间,且主从强依赖时间同步。)
读到过期数据,过期淘汰策略问题
保证网络连接,减小延迟(废话)
使用外部程序来监控主从库间的复制进度
同步方式为异步,因网络延迟或其他阻塞命令导致延迟
protected-mode 设置为了 yes 导致哨兵间无法通信。改为no 即可
cluster_node_timeout 心跳检测超时时间过小,导致宕机误判断。设置为10~20s即可
配置不合理导致
主从不一致问题的原因及解决方式
min-slaves-to-write:这个配置项设置了主库能进行数据同步的最少从库数量;
min-slaves-max-lag:这个配置项设置了主从库间进行数据复制时,从库给主库发送 ACK 消息的最大延迟(以秒为单位)
将两者结合起来使用,至少 X 个从库能在 Y 秒时间内和主库通信,否则,主节点停止接收客户端的请求
解决脑裂现象,脑裂的出现原因示主节点 阻塞 导致“假故障”(请回想阻塞原因有哪些),在执行主从切换过程中又恢复了。在切换完成之前,原主节点依旧在接收读写操作,当切换完成后,原主节点会由于执行 slave of 成为从节点;导致数据全量同步,丢失切换前的数据。避免脑裂方式为:限制切换过程中,主节点不能接收客户端的请求。减小脑裂几率
codis由四部分组成,codis-server、codis-proxy、codis-dashboard、codis-fe
server为普通的redis实例,proxy接收客户端请求,并把请求转发给 codis serverdashboard和fe共同组成集群管理工具,fe属于web界面操作。dashboard提供后台支持增删 codis server、codis proxy 和进行数据迁移。
codis-proxy自身实现了RESP交互协议,客户端连接单实例和集群是一样的,不需要切换
Codis 集群一共有 1024 个 Slot,可以手动分配,默认情况下,dashboard会均分到各个codis-server上
当客户端要读写数据时,会使用 CRC32 算法计算数据 key 的哈希值,并把这个哈希值对 1024 取模。而取模后的值,则对应 Slot 的编号,找到对应的codis-server
slot 和codis-server的对应关系称为路由表,codis-proxy会缓存一份,同时也会存放一份在zookeeper中。当接收到请求后,根据路由表转发到对应的codis-server
当数据位置发生变化时(例如有实例增减),路由表被修改了,codis dashbaord 就会把修改后的路由表发送给 codis proxy
集群数据分布
迁移过程中,以codis的slot为单位,将需要迁移的slot上的key,一个一个的迁移为提升迁移效率,可以通过异步迁移命令 SLOTSMGRTTAGSLOT-ASYNC 的参数 numkeys 设置每次迁移的 key 数量。
bigkey会转化成指令,例如一个1w条记录的List,会转成1w个 LPUSH/RPUSH 指令,同时完全迁移完成之前,添加一个过期时间,完成后再取消该时间。避免中途连接断开导致的问题
扩容:增加 codis server (实例)和增加 codis proxy(转发代理)
codis
redis cluster
1. Codis 应用得比较早,成熟度更高,如果你想选择一个成熟稳定的方案,Codis 更加合适些
2. 如果你的业务应用中大量使用了单实例的客户端,而现在想应用切片集群的话,建议你选择 Codis,这样可以避免修改业务应用中的客户端
不支持的指令集
3. codis是基于redis 3.2.8开发的,Codis 并不支持 Redis 后续的开源版本中的新增命令和数据类型比如 BITOP、BLPOP、BRPOP,以及和与事务相关的 MUTLI、EXEC 等命令
4. 从数据迁移性能维度来看,Codis 能支持异步迁移,异步迁移对集群处理正常请求的性能影响要比使用同步迁移的小。所以,如果你在应用集群时,数据迁移比较频繁的话,Codis 是个更合适的选择
集群方案选择-codis和redis cluster的优劣分析
实践篇下
Redis
0 条评论
回复 删除
下一页