Redis 的 KEYS 命令引发 RDS 数据库雪崩,宕机 2 次,形成几百万损失

最近的互联网线上事故发生比较频繁,9月19日网上爆料出顺丰近期发生了一块儿线上删库事件,在这里就不介绍了。前端

在这里讲述一下最近发生在我公司的事故,以及如何避免,而且如何处理优化。 该宕机的直接缘由是使用 Redis 的 keys * 命令引发的,一共形成了某个服务化项目的两次宕机。程序员

间接缘由还有不少,技术跟不上业务的发展,由每日百万量到千万级是一个大的跨进,公司对于系统优化的处理优先级不高,技术开发人手的短缺。redis

第一次宕机

2018年9月13日的某个点,公司某服务化项目的 RDS 实例链接飙升,CPU 升到 100%,拒绝了其余应用的全部请求服务。数据库

整个过程以下:编程

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

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

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

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

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

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

<!--20分钟内,关闭一些次要应用的流量入口-->

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

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

35分钟内,全部应用恢复正常

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

事故影响缓存

某服务化项目服务不可用几十分钟,形成订单数减小几十万笔,损失百万资金。 缘由分析服务器

当时是没有定位到具体的缘由的,可是下面的缘由也是一部分可能引发宕机的状况。markdown

某服务化项目的业务增速很是快,在高峰期,数据库QPS突破35000,系统处于高负荷状态。网络

在高峰期若是同时执行几个全表扫描的SQL,会形成数据库压力急剧上升,应用超时增多,前端应用超时,用户重试,流量飙升,造成了雪崩效应。数据结构

主要缘由在与一些老项目的SQL查询性能较差,而且使用的主库,对数据库影响较大。数据库QPS过高,可是缓存方案由于人手缘由一直没有落地,慢SQL的问题处理优先级应该提高。 改进方案

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

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

升级数据库配置

迁移非核心业务到新的RDS实例中去
复制代码

第二次宕机

因为上一次的宕机缘由未找到,因此这次的宕机是能够预见的。

20180919,仍是同样的”配方”,仍是原来的”味道”。同一个RDS,CPU飙升至100%,接下来就是拒绝服务,宕机。固然,有了第一次的经验,直接主从切换,在几十秒左右就恢复了全部业务,但仍是严重影响了公司的业务和形象 缘由分析

恢复业务后,公司紧急召开了紧急事故研究会议,固然,个人级别是参与不了的。公司的高管,高层技术架构、DBA、各个项目的主负责人一块儿进行了会议。

在这次会议中,通过查看各个项目的日志,后台的监控数据,发如今那台RDS数据库CPU飙升时,有一台Redis数据库内存将近100%,而后急剧降低。联系第一次的宕机状况,也是相似的。

接下来就是联系服务器数据库提供商,将那台Redis最近一周的命令所有调用出来,最后发现,在那个时间点运行了一条keys *...*命令。公司的一个工程师执行keys模糊的匹配命令是为了清理没用的键,可是没有考虑到keys *进行模糊匹配引起Redis锁,形成Redis锁住,CPU飙升,引发了全部调用链路的超时而且卡住,等Redis锁的那几秒结束,全部的请求流量所有请求到RDS数据库中,使数据库产生了雪崩,使数据库宕机。 改进方案

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

新增Redis实例,进行分离

若是有使用相似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信息。

客户端链接尽可能使用链接池(长连接和自动重连)

以为文章不错能够点赞收藏转发关注,IT编程学习资料和更多技术文章也能够关注公众号:程序员理想哦

声明:推送内容及图片来源于网络,部份内容会有所改动,版权归原做者全部,如来源信息有误或侵犯权益,请联系咱们删除或受权事宜。

原文:http://blog.jobbole.com/114397/复制代码