一个keys命令引发的惨案
创始人
2025-05-28 07:55:53
0

      最近公司是做广告投放业务的,每秒钟三个服务的QPS是3000+,通过redis集群缓存了大量的key,接近4000万,你没看错这个数字。一个老代码使用了keys命令,导致CPU直接100%,业务挂逼。所以特定回来对redis的一些坑进行了整理。刚好,下面这个案例跟我们公司类似,大伙借鉴一下,代码中千万别任性使用keys命令。

第一次宕机

20180913某个点,公司某服务化项目的RDS实例连接飙升,CPU升到100%,拒绝了其他应用的所有请求服务整个过程如下:

1. 监控报警,显示RDS的CPU使用率达到80%以上,DBA介入,准备KILL慢SQL

2. 1分钟内,没有发现明显阻塞的SQL,CPU持续上升到99%

3 .5分钟内,大量应用报警,并且拒绝服务,RDS的监控显示出现大量慢SQL,联系服务器数据库提供商进行协助

4. 8分钟内,进行数据库主备切换(业务会受损,但是也没办法,没有定位到问题)

5. 9分钟内,部分业务恢复,但是一些业务订单的回调消息堆积超过20w,备库的CPU使用率也持续上升

6. 15分钟内,备库CPU使用率超过97%,业务再次中断,进行切回主库,并进行限流

7. 20分钟内,关闭一些次要应用的流量入口

8. 25分钟内,主库CPU使用率恢复正常

9. 30分钟内,逐步开启关闭的限流应用

10. 35分钟内,所有应用恢复正常

11. 接下来就是与服务器数据库提供商成立应急小组紧急优化可能出现的慢SQL,虽然说可能解决了一些慢SQL,但此次并没有定位到具体的问题,也就为几天后再次发生宕机事件埋下了伏笔

事故影响

某服务化项目服务不可用几十分钟,造成订单数减少几十万笔,损失百万资金

原因分析

当时是没有定位到具体的原因的,但是下面的原因也是一部分可能引起宕机的情况。某服务化项目的业务增速非常快,在高峰期,数据库QPS突破35000,系统处于高负荷状态。在高峰期如果同时执行几个全表扫描的SQL,会造成数据库压力急剧上升,应用超时增多,前端应用超时,用户重试,流量飙升,形成了雪崩效应。主要原因在与一些老项目的SQL查询性能较差,并且使用的主库,对数据库影响较大。数据库QPS太高,但是缓存方案因为人手原因一直没有落地,慢SQL的问题处理优先级应该提升

改进方案

1. 针对每个应用建一个数据库账号,严格按照规范使用

2. 缓存优化方案即时落地,慢SQL问题优先处理,集中处理目前已经发现的慢SQL(查询时间超过1S)

3. 升级数据库配置

4. 迁移非核心业务到新的RDS实例中去

第二次宕机

由于上一次的宕机原因未找到,所以此次的宕机是可以预见的20180919,还是一样的"配方",还是原来的"味道"。同一个RDS,CPU飙升至100%,接下来就是拒绝服务,宕机。当然,有了第一次的经验,直接主从切换,在几十秒左右就恢复了所有业务,但还是严重影响了公司的业务和形象

原因分析

恢复业务后,公司紧急召开了紧急事故研究会议,当然,我的级别是参与不了的。公司的高管,高层技术架构、DBA、各个项目的主负责人一起进行了会议。在此次会议中,经过查看各个项目的日志,后台的监控数据,发现在那台RDS数据库CPU飙升时,有一台Redis数据库内存将近100%,然后急剧下降。联系第一次的宕机情况,也是类似的。接下来就是联系服务器数据库提供商,将那台Redis最近一周的命令全部调用出来,最后发现,在那个时间点运行了一条 keys *...*命令。公司的一个工程师执行keys模糊的匹配命令是为了清理没用的键,但是没有考虑到keys * 进行模糊匹配引发Redis锁,造成Redis锁住,CPU飙升,引起了所有调用链路的超时并且卡住,等Redis锁的那几秒结束,所有的请求流量全部请求到RDS数据库中,使数据库产生了雪崩,使数据库宕机。

改进方案

1. 所有线上操作,全部要经过运维通过后方可执行,运维部门逐步快速收回各项权限

2. 新增Redis实例,进行分离

3. 如果有使用类似keys正则命令需求,使用scan命令代替

总结

该事件中出现的两次事故,完全是由于人为操作引起的,如果那位工程师,看过Redis的开发规范,会发现是建议禁用keys命令的。另外,有线上的命令操作,一定要经过运维评估后方可进行操作,估计那个工程师是老员工吧,有权限,然后直接就进行操作了另外,公司的业务发展确实很快,技术跟不上,这是非常非常危险的,极大的增加了宕机的概率在业务量不大的情况下,那位工程师的操作是完全没什么问题的,毕竟并发也不大,但是现在,随着公司的发展,业务量的成倍成倍增加,技术的扩展却没有随着增长那么快公司的技术人手不足也是一方面,绝大多数人都是边维护老项目边做新功能,但是对于项目的重构优化,人手却少了很多,项目优化的优先级不高,这也是很大的一个原因,极有可能出现类似的情况,新服务化构建迫在眉睫

最后的最后,线上操作的任何一条命令,再小心也不为过因为由于你的一个符号而引起的事故可能是你所承担不起的

Redis开发建议

最后附上Redis的一些开发规范和建议

1.冷热数据分离,不要将所有数据全部都放到Redis中

虽然Redis支持持久化,但是Redis的数据存储全部都是在内存中的,成本昂贵。建议根据业务只将高频热数据存储到Redis中【QPS大于5000】,对于低频冷数据可以使用MySQL/ElasticSearch/MongoDB等基于磁盘的存储方式,不仅节省内存成本,而且数据量小在操作时速度更快、效率更高!

2.不同的业务数据要分开存储

不要将不相关的业务数据都放到一个Redis实例中,建议新业务申请新的单独实例。因为Redis为单线程处理,独立存储会减少不同业务相互操作的影响,提高请求响应速度;同时也避免单个实例内存数据量膨胀过大,在出现异常情况时可以更快恢复服务! 在实际的使用过程中,redis最大的瓶颈一般是CPU,由于它是单线程作业所以很容易跑满一个逻辑CPU,可以使用redis代理或者是分布式方案来提升redis的CPU使用率。

3.存储的Key一定要设置超时时间

如果应用将Redis定位为缓存Cache使用,对于存放的Key一定要设置超时时间!因为若不设置,这些Key会一直占用内存不释放,造成极大的浪费,而且随着时间的推移会导致内存占用越来越大,直到达到服务器内存上限!另外Key的超时长短要根据业务综合评估,而不是越长越好!

4.对于必须要存储的大文本数据一定要压缩后存储

对于大文本【+超过500字节】写入到Redis时,一定要压缩后存储!大文本数据存入Redis,除了带来极大的内存占用外,在访问量高时,很容易就会将网卡流量占满,进而造成整个服务器上的所有服务不可用,并引发雪崩效应,造成各个系统瘫痪!

5.线上Redis禁止使用Keys正则匹配操作

Redis是单线程处理,在线上KEY数量较多时,操作效率极低【时间复杂度为O(N)】,该命令一旦执行会严重阻塞线上其它命令的正常请求,而且在高QPS情况下会直接造成Redis服务崩溃!如果有类似需求,请使用scan命令代替!

6.可靠的消息队列服务

Redis List经常被用于消息队列服务。假设消费者程序在从队列中取出消息后立刻崩溃,但由于该消息已经被取出且没有被正常处理,那么可以认为该消息已经丢失,由此可能会导致业务数据丢失,或业务状态不一致等现象发生。为了避免这种情况,Redis提供了RPOPLPUSH命令,消费者程序会原子性的从主消息队列中取出消息并将其插入到备份队列中,直到消费者程序完成正常的处理逻辑后再将该消息从备份队列中删除。同时还可以提供一个守护进程,当发现备份队列中的消息过期时,可以重新将其再放回到主消息队列中,以便其它的消费者程序继续处理。

7.谨慎全量操作Hash、Set等集合结构

在使用HASH结构存储对象属性时,开始只有有限的十几个field,往往使用HGETALL获取所有成员,效率也很高,但是随着业务发展,会将field扩张到上百个甚至几百个,此时还使用HGETALL会出现效率急剧下降、网卡频繁打满等问题【时间复杂度O(N)】,此时建议根据业务拆分为多个Hash结构;或者如果大部分都是获取所有属性的操作,可以将所有属性序列化为一个STRING类型存储!同样在使用SMEMBERS操作SET结构类型时也是相同的情况!

8.根据业务场景合理使用不同的数据结构类型

目前Redis支持的数据库结构类型较多:字符串(String),哈希(Hash),列表(List),集合(Set),有序集合(Sorted Set), Bitmap, HyperLogLog和地理空间索引(geospatial)等,需要根据业务场景选择合适的类型。常见的如:String可以用作普通的K-V、计数类;Hash可以用作对象如商品、经纪人等,包含较多属性的信息;List可以用作消息队列、粉丝/关注列表等;Set可以用于推荐;Sorted Set可以用于排行榜等!

9.命名规范

虽然说Redis支持多个数据库(默认32个,可以配置更多),但是除了默认的0号库以外,其它的都需要通过一个额外请求才能使用。所以用前缀作为命名空间可能会更明智一点。另外,在使用前缀作为命名空间区隔不同key的时候,最好在程序中使用全局配置来实现,直接在代码里写前缀的做法要严格避免,这样可维护性实在太差了。如:系统名:业务名:业务数据:其他但是注意,key的名称不要过长,尽量清晰明了,容易理解,需要自己衡量

10.线上禁止使用monitor命令

禁止生产环境使用monitor命令,monitor命令在高并发条件下,会存在内存暴增和影响Redis性能的隐患

11.禁止大string

核心集群禁用1mb的string大key(虽然redis支持512MB大小的string),如果1mb的key每秒重复写入10次,就会导致写入网络IO达10MB;

12.redis容量

单实例的内存大小不建议过大,建议在10~20GB以内。redis实例包含的键个数建议控制在1kw内,单实例键个数过大,可能导致过期键的回收不及时。

13 可靠性

需要定时监控redis的健康情况:使用各种redis健康监控工具,实在不行可以定时返回redis 的 info信息。客户端连接尽量使用连接池(长链接和自动重连)

相关内容

热门资讯

写赞颂梅花的诗句 写赞颂梅花的诗句  梅花美,却把美留给了洁白无瑕的天地;梅花香,却又有谁知道“梅花香自苦寒来”的艰辛...
冰心的最短的现代诗 冰心的最短的现代诗(通用15首)  众所周知,相信大家对我国现代作家、诗人冰心都格外的了解,冰心是中...
唐诗《瑶池》 唐诗《瑶池》  《瑶池》  年代: 唐 作者: 李商隐  瑶池阿母绮窗开,黄竹歌声动地哀。  八骏日...
离别的诗词 关于离别的诗词  离别是无言的痛,离别是经年的伤;离别是涩涩的苦,离别是深藏的泪;离别是情深缘浅的无...
夏至的著名诗句 夏至的著名诗句  无论在学习、工作或是生活中,大家都接触过比较经典的诗句吧,诗句具有精炼含蓄的特点,...
表达委婉相思的诗句 表达委婉相思的诗句(精选170句)  他生莫作有情痴,人间无地著相思。下面是小编收集的有关表达委婉相...
海子诗集经典诗句 海子诗集经典诗句  1、风后面是风,天空上面是天空,道路前面还是道路。——海子《四姐妹》  2、过程...
王维《送别》古诗赏析 王维《送别》古诗赏析  王维,字摩诘,号摩诘居士。河东蒲州(今山西永济)人,祖籍山西祁县。唐朝诗人、...
描写柳树的诗句 描写柳树的诗句(精选100句)  柳树属于广生态幅植物,对环境的'适应性很广,喜光,喜湿,耐寒,是中...
月圆相思的诗句 月圆相思的诗句大全  身边中是有着许多的诗句,有些也是描写相思的。下面是小编为大家整理的'关于月圆相...
形容亲人离别的诗句 形容亲人离别的诗句  离恨恰如春草,更行更远还生,下面是一些表达与形容亲人离别的诗句,欢迎大家阅读!...
今我来思,雨雪霏霏 “今我来思,雨雪霏霏。”出处 出自 先秦 佚名 的《采薇》“今我来思,雨雪霏霏。”全诗《采薇》 先秦...
时光流逝的诗句 时光流逝的诗句  在平平淡淡的日常中,大家一定都接触过一些使用较为普遍的诗句吧,诗句一般饱含丰富的想...
激厉努力奋斗的诗句 激厉努力奋斗的诗句(精选70句)  在日常学习、工作和生活中,大家都看到过许多经典的诗句吧,诗句具有...
描写北方的冬天的诗句 描写北方的冬天的诗句  北方的.冬天,意味着寒冷,意味着萧条,意味着不方便。接下来小编搜集了描写北方...
描写下雨的诗句古诗 描写下雨的诗句古诗  中国最早得诗句为律诗结构,格律要求严格,比如先秦时期得诗一般每句四言律诗,见于...
使至塞上经典诗句解读 使至塞上经典诗句赏析  这两句诗充实表现了诗中有画的特征,非常讲求景物的画面感。首先,它有画一样的构...
描写日落的诗句 描写日落的诗句描写日落的诗句1  落霞与孤鹜齐飞,秋水共长天一色  夕阳无限好,只是近黄昏  大漠孤...
忆昔霍将军,连年此征讨 “忆昔霍将军,连年此征讨。”出处 出自 唐代 高适 的《登百丈峰二首》“忆昔霍将军,连年此征讨。”全...
描写棋的优美诗句 描写棋的优美诗句  棋声花院闭,幡影石坛高。  满目山川似势棋,况当秋雁正斜飞。金门若召羊玄保,赌取...