论坛
潜水/灌水快乐,沉淀知识,认识更多同行。
ToB圈子
加入IT圈,遇到更多同好之人。
朋友圈
看朋友圈动态,了解ToB世界。
ToB门户
了解全球最新的ToB事件
博客
Blog
排行榜
Ranklist
文库
业界最专业的IT文库,上传资料也可以赚钱
下载
分享
Share
导读
Guide
相册
Album
记录
Doing
搜索
本版
文章
帖子
ToB圈子
用户
免费入驻
产品入驻
解决方案入驻
公司入驻
案例入驻
登录
·
注册
只需一步,快速开始
账号登录
立即注册
找回密码
用户名
Email
自动登录
找回密码
密码
登录
立即注册
首页
找靠谱产品
找解决方案
找靠谱公司
找案例
找对的人
专家智库
悬赏任务
圈子
SAAS
ToB企服应用市场:ToB评测及商务社交产业平台
»
论坛
›
软件与程序人生
›
后端开发
›
.Net
›
第78篇 Redis常见耽误问题
第78篇 Redis常见耽误问题
海哥
金牌会员
|
2024-12-8 23:02:47
|
显示全部楼层
|
阅读模式
楼主
主题
876
|
帖子
876
|
积分
2628
使用复杂度高的下令
Redis提供了慢日志下令的统计功能
首先设置Redis的慢日志阈值,只有超过阈值的下令才会被记录,这里的单位是微妙,比方设置慢日志的阈值为5毫秒,同时设置只保留最近1000条慢日志记录:
# 命令执行超过5毫秒记录慢日志
CONFIG SET slowlog-log-slower-than 5000
# 只保留最近1000条慢日志
CONFIG SET slowlog-max-len 1000
复制代码
实行SLOWLOG get 5查询最近5条慢日志
127.0.0.1:6379> SLOWLOG get 5
1) 1) (integer) 32693 # 慢日志ID
2) (integer) 1593763337 # 执行时间
3) (integer) 5299 # 执行耗时(微秒)
4) 1) "LRANGE" # 具体执行的命令和参数
2) "user_list_2000"
3) "0"
4) "-1"
2) 1) (integer) 32692
2) (integer) 1593763337
3) (integer) 5044
4) 1) "GET"
2) "book_price_1000"
...
复制代码
通过查看慢日志记录,就可以知道在什么时间实行哪些下令比力耗时,如果服务哀求量并不大,但Redis实例的CPU使用率很高,很有可能就是使用了复杂度高的下令导致的。
好比常常使用O(n)以上复杂度的下令,由于Redis是单线程实行下令,因此这种环境Redis处理数据时就会很耗时。比方
sort:对列表(list)、聚集(set)、有序聚集(sorted set)中的元素进行排序。在最简单的环境下(没有权重、没有模式、没有
LIMIT
),
SORT
下令的时间复杂度近似于
O(n*log(n))
sunion:用于计算两个或多个聚集的并集。时间复杂度可以描述为
O(N)
,其中 **N **是全部参与运算聚集的元素总数。如果有多个聚集,每个聚集有不同数量标元素参与运算,那么复杂度会是全部这些聚集元素数量标总和。
zunionstore:用于计算一个或多个有序聚集的并集,并将结果存储到一个新的有序聚集中。在最简单的环境下,
ZUNIONSTORE
下令的时间复杂度是
O(N*log(N))
,其中 N 是全部参与计算的聚集中元素的总数。
keys * :获取全部的 key 操作;复杂度
O(n)
,数据量越大实行速度越慢;可以使用
scan
下令替代
Hgetall:返回哈希表中全部的字段和;
smembers:返回聚集中的全部成员;
解决方案就是,不使用这些复杂度较高的下令,而且一次不要获取太多的数据,每次只管操作少量的数据,让Redis可以实时处理返回
存储大key
如果查询慢日志发现,并不是复杂度较高的下令导致的,比方都是SET、DELETE操作出如今慢日志记录中,那么就要猜疑是否存在Redis写入了大key的环境。
多大才算大
如果一个 key 对应的 value 所占用的内存比力大,那这个 key 就可以看作是 bigkey。
String 类型的 value 超过 1MB
复合类型(List、Hash、Set、Sorted Set 等)的 value 包含的元素超过 5000 个(不过,对于复合类型的 value 来说,不一定包含的元素越多,占用的内存就越多)。
产生原因
程序筹划不当,好比直接使用 String 类型存储较大的文件对应的二进制数据。
对于业务的数据规模考虑不周到,好比使用聚集类型的时间没有考虑到数据量的快速增长。
未实时清理垃圾数据,好比哈希中冗余了大量的无用键值对。
造成的问题
客户端超时壅闭:由于 Redis 实行下令是单线程处理,然后在操作大 key 时会比力耗时,那么就会壅闭 Redis,从客户端这一视角看,就是很久很久都没有相应。
网络壅闭:每次获取大 key 产生的网络流量较大,如果一个 key 的大小是 1 MB,每秒访问量为 1000,那么每秒会产生 1000MB 的流量,这对于普通千兆网卡的服务器来说是灾难性的。
工作线程壅闭:如果使用 del 删除大 key 时,会壅闭工作线程,这样就没办法处理后续的下令。
持久化壅闭(磁盘IO):对
AOF 日志
的影响
使用Always 计谋的时间,主线程在实行完下令后,会把数据写入到 AOF 日志文件,然后会调用 fsync() 函数,将内核缓冲区的数据直接写入到硬盘,等到硬盘写操作完成后,该函数才会返回。因此当使用 Always 计谋的时间,如果写入是一个大 Key,主线程在实行 fsync() 函数的时间,壅闭的时间会比力久,因为当写入的数据量很大的时间,数据同步到硬盘这个过程是很耗时的。
别的两种计谋都不影响主线程
大 key 造成的壅闭问题还会进一步影响到主从同步和集群扩容。
怎样发现 bigkey?
使用 Redis 自带的 --bigkeys 参数来查找:这个下令会扫描(Scan) Redis 中的全部 key ,会对 Redis 的性能有一点影响,最好选择在从节点上实行该下令,因为主节点上实行时,会
壅闭
主节点。而且,这种方式只能找出每种数据结构 top 1 bigkey(占用内存最大的 String 数据类型,包含元素最多的复合数据类型)。然而,一个 key 的元素多并不代表占用内存也多,必要我们根据具体的业务环境来进一步判断。
Redis 自带的 SCAN 下令:SCAN 下令可以按照一定的模式和数量返回匹配的 key。获取了 key 之后,可以利用 STRLEN、HLEN、LLEN等下令返回其长度或成员数量。
借助开源工具分析 RDB 文件:这种方案的条件是Redis 采取的是 RDB 持久化。网上有现成的工具:
redis-rdb-tools:Python 语言写的用来分析 Redis 的 RDB 快照文件用的工具
rdb_bigkeys:Go 语言写的用来分析 Redis 的 RDB 快照文件用的工具,性能更好。
怎样处理 bigkey?
删除大 key:删除大 key 时建议采取分批次删除和异步删除的方式进行;
因为删除大 key释放内存只是第一步,为了更加高效地管理内存空间,在应用程序释放内存时,操作体系必要把释放掉的内存块插入一个空闲内存块的链表,以便后续进行管理和再分配。这个过程本身必要一定时间,而且会壅闭当前释放内存的应用程序。
所以,如果一下子释放了大量内存,空闲内存块链表操作时间就会增长,相应地就会造成 Redis 主线程的壅闭,如果主线程发生了壅闭,其他全部哀求可能都会超时,超时越来越多,会造成 Redis 连接耗尽,产生各种异常。
分割 bigkey:将一个 bigkey 分割为多个小 key。比方,将一个含有上万字段数量标 Hash 按照一定计谋(好比二次哈希)拆分为多个 Hash。
手动清理:Redis 4.0+ 可以使用 UNLINK 下令来异步删除一个或多个指定的 key。Redis 4.0 以下可以考虑使用 SCAN 下令结合 DEL 下令来分批次删除。
采取合适的数据结构:比方,文件二进制数据不使用 String 保存、使用 HyperLogLog 统计页面 UV、Bitmap 保存状态信息(0/1)。
开启 lazy-free(惰性删除/耽误释放) :lazy-free 特性是 Redis 4.0 开始引入的,指的是让 Redis 采取异步方式耽误释放 key 使用的内存,将该操作交给单独的子线程处理,制止壅闭主线程。
集中过期
Redis的过期计谋采取 定期过期+懒惰过期两种计谋:
定期过期:Redis内部维护一个定时任务,默认每秒进行10次(也就是每隔100毫秒一次)过期扫描,从过期字典中随机取出20个key,删除过期的key,如果过期key的比例还超过25%,则继续获取20个key,删除过期的key,循环往复,直到过期key的比例降落到25%或者这次任务的实行耗时超过了25毫秒,才会退出循环
懒惰过期:只有当访问某个key时,才判断这个key是否已过期,如果已经过期,则从实例中删除
Redis的定期删除计谋是在Redis
主线程
中实行的,也就是说如果在实行定期删除的过程中,出现了必要大量删除过期key的环境,那么在业务访问时,必须等这个定期删除任务实行结束,才可以处理业务哀求。此时就会出现,业务访问延时增大的问题,最大耽误为25毫秒。
为了只管制止这个问题,在设置过期时间时,可以给过期时间设置一个随机范围,制止同一时刻过期。
伪代码可以这么写:
# 在过期时间点之后的5分钟内随机过期掉
redis.expireat(key, expire_time + random(300))
复制代码
实例内存达到上限
生产中会给内存设置上限maxmemory,当数据内存达到 maxmemory 时,便会触发redis的内存镌汰计谋
那么当实例的内存达到了maxmemory后,就会发现之后每次写入新的数据,就似乎变慢了。导致变慢的原因是,当Redis内存达到maxmemory后,每次写入新的数据之前,会先根据内存镌汰计谋先踢出一部门数据,让内存维持在maxmemory之下。
而内存镌汰计谋就决定这个踢出数据的时间长短:
最常使用的一般是allkeys-lru或volatile-lru计谋,Redis 内存镌汰时,会使用随机采样的方式来镌汰数据,它是随机取 5 个值 (此值可配置) ,然后镌汰一个最少访问的key,之后把剩下的key暂存到一个池子中,继续随机取出一批key,并与之前池子中的key比力,再镌汰一个最少访问的key。以此循环,直到内存降到maxmemory之下。
如果使用的是allkeys-random或volatile-random计谋,那么就会快很多,因为是随机镌汰,那么就少了比力key访问频率时间的消耗了,随机拿出一批key后直接镌汰即可,因此这个计谋要比上面的LRU计谋实行快一些。
但以上这些镌汰计谋的逻辑都是在访问Redis时,真正下令实行之前实行的,也就是它会影响真正必要实行的下令。
别的,如果此时Redis实例中有存储大key,那么在
镌汰大key释放内存时,这个耗时会更加久,耽误更大
AOF持久化
同步持久化
当 Redis 直接记录 AOF 日志时,如果有大量的写操作,而且配置为
同步持久化
appendfsync always
复制代码
即每次发生数据变更会被立即记录到磁盘,而且Always写回计谋是由
主进程
实行的,而写磁盘比力耗时,性能较差,所以偶然会壅闭主线程。
AOF重写
fork 出一条子线程来将文件重写,在实行 BGREWRITEAOF 下令时,Redis 服务器会维护一个 AOF 重写缓冲区,该缓冲区会在子线程创建新 AOF 文件期间,记录服务器实行的全部写下令。
当子线程完成创建新 AOF 文件的工作之后,服务器会将重写缓冲区中的全部内容追加到新 AOF 文件的末尾,使得新的 AOF 文件保存的数据库状态与现有的数据库状态同等。
最后,服务器用新的 AOF 文件替换旧的 AOF 文件,以此来完成 AOF 文件重写操作。
壅闭就是出如今第2步的过程中,将缓冲区中新数据写到新文件的过程中会产生
壅闭
。
fork耗时
生成RDB和AOF重写都必要父进程fork出一个子进程进行数据的持久化,在fork实行过程中,父进程必要拷贝内存页表给子进程,如果整个实例内存占用很大,那么必要拷贝的内存页表会比力耗时,此过程会消耗大量的CPU资源,在完成fork之前,整个实例会被壅闭住,无法处理任何哀求,如果此时CPU资源告急,那么fork的时间会更长,甚至达到秒级。这会严重影响Redis的性能。
Redis 在进行 RDB 快照的时间,会调用体系函数 fork() ,创建一个子线程来完成临时文件的写入,而触发条件正是配置文件中的 save 配置。当达到配置时,就会触发 bgsave 下令创建快照,这种方式是不会
壅闭主线程
的,而手动实行 save 下令会在主线程中实行,壅闭主线程。
除了因为备份的原因生成RDB之外,在【主从复制】第一次建立连接全量复制时,主节点也会生成RDB文件给从节点进行一次全量同步,这时也会对Redis产生性能影响。
要想制止这种环境,必要规划好数据备份的周期,建议在
从节点上实行备份
,而且最好放在低峰期实行。如果对于丢失数据不敏感的业务,那么不建议开启AOF和AOF重写功能。
集群扩容
Redis 集群可以进行节点的动态扩容缩容,这一过程目前还处于
半自动
状态,必要人工介入。
在扩缩容的时间,必要进行数据迁徙。而 Redis 为了包管迁徙的同等性,迁徙全部操作都是
同步
操作。
实行迁徙时,两端的 Redis 均会进入时长不等的
壅闭
状态,对于小Key,该时间可以忽略不计,但如果一旦 Key 的内存使用过大,严重的时间会触发集群内的故障转移,造成不必要的切换。
总结
使用复杂度高的下令,实行下令时就会耗时
存储大key:如果一个key写入的数据非常大,Redis在分配内存、删除大key时都会耗时,而且持久化AOF的写回计谋是always时会影响Redis性能
集中过期:Redis的自动过期的定时任务,是在Redis
主线程
中实行的,最差的环境下会有25ms的壅闭
实例内存达到上限时,镌汰计谋的逻辑都是在访问Redis时,真正下令实行之前实行的,也就是它会影响真正必要实行的下令。
fork耗时:生成RDB和AOF重写都必要父进程fork出一个子进程进行数据的持久化,如果整个实例内存占用很大,那么必要拷贝的内存页表会比力耗时
额外总结大key的影响:
如果一个key写入的数据非常大,Redis在分配内存、删除大key时都会耗时。
当实例内存达到上限时,在镌汰大key释放内存时,内存镌汰计谋的耗时会更加久,耽误更大
AOF持久化时,使用always机制,这个操作是在
主线程
中实行的,如果写入是一个大 Key,主线程在实行 fsync() 函数的时间,壅闭的时间会更久。
生成RDB和AOF重写时会fork出一个子进程进行数据的持久化,父进程必要拷贝内存页表给子进程,如果整个实例内存占用很大,那么必要拷贝的内存页表会比力耗时。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
本帖子中包含更多资源
您需要
登录
才可以下载或查看,没有账号?
立即注册
x
回复
使用道具
举报
0 个回复
倒序浏览
返回列表
快速回复
高级模式
B
Color
Image
Link
Quote
Code
Smilies
您需要登录后才可以回帖
登录
or
立即注册
本版积分规则
发表回复
回帖并转播
回帖后跳转到最后一页
发新帖
回复
海哥
金牌会员
这个人很懒什么都没写!
楼主热帖
makefile简单脚本编写和Linux调试器gdb ...
【第90题】JAVA高级技术-网络编程9(简 ...
CMOS图像传感器——黑电平校正 ...
什么是精准卫星授时?什么是NTP网络时 ...
姚凯大学生创业导论课后答案2022 ...
如何使用 FlowUs 、Notion 等笔记软件 ...
Linux【实操篇】—— 日志管理 ...
TCP 时间戳妙用
gRPC入门
C++ 深度解析教程(十七)C 语言异常处 ...
标签云
挺好的
服务器
快速回复
返回顶部
返回列表