这周五周六经历一次严重的redis宕机导致数据丢失的严重生产数据,并导致了10小时的停服。算是工作以来经历的最大生产事故,今天做一个全面总结,也算是吃一堑长一智。
随着我们业务量不断增加,我们线上环境的redis内存逐渐吃紧。然后(2022-07-22)周五的时候,运维说他们要进行redis升级,他们先升级从库,以前为了提升主库性能,他们把redis的数据rdb备份放在了从库。因为放在主库的话需要消耗主库的CPU和内存空间,从而会影响主库的性能。而从库升级内存需要需要停机,所以他们临时把rdb备份临时放到了主库。结果从库升级完后,回来发现主库因为内存不足,rdb备份都是是不成功的,而从库升级这段时间主库都是有的业务数据写入进来的,所以从从库停机开始往后的数据都没有备份到磁盘。
这时摆在我们面前就两条路,一是舍弃这几分钟的数据,直接主从切换,让从库继续运转。二是让主库继续运行,然后想办法后续把增量数据同步到从库里去。经过评估,这丢失几分钟的数据,存在一些关键数据,比如盲盒的奖池,比如水浒当前轮次的押注数据,这些数据都是无法接受几分钟的数据丢失的。所以在当时我们认为选择方案二更稳妥。
基于方案二,我们有讨论了几种落地方案:
最终由于方案一和方案二的无法落地,我们只能选择方案三,所以根业务部门沟通后我们在次日(2022-07-23)凌晨六点到七点进行停服维护。
我们把原本凌晨6点到7点之间的脚本,提前执行,然后写了一个redis基于内存迁移数据的python脚本:
## 安装redis库的命令: sudo pip3 install redis -i https://pypi.doubanio.com/simple
## -i 表示指定下载镜像地址,如果不指定的花,redis这个库在国外,可能下载不成功
import redis
import time
old_conn = redis.from_url('redis://:xxxxx@xxxx-xxxx.m.com:111')
new_conn = redis.from_url('redis://:xxxx@xxxxx.xxx.xxx.xxx:111')
## 限制数量(为0表示不做限制)
limit_cnt = 0
debug_console = True
## 开始游标
start_cursor = 0
total_cnt = 0
start_time = time.time()
print("开始同步")
while(start_cursor >= 0 and (limit_cnt <= 0 or total_cnt < limit_cnt)):
## 获取key
info = old_conn.scan(start_cursor,"*",500)
## 获取key列表
items = info[1]
for it in info[1]:
if(limit_cnt > 0 and total_cnt >= limit_cnt):
break
## 获取key原本的过期时间
ttl = old_conn.pttl(it)
if(ttl == -1):
## -1表示这个key没有过期时间
ttl = 0
elif(ttl == -2):
print("上一瞬间过期的key:", it)
continue
if(debug_console):
print("迁移key:", it)
## 读取二进制数据
value = old_conn.dump(it)
## 删除新库中的老数据
new_conn.delete(it)
## 写入到新的redis中去
new_conn.restore(it,ttl,value)
total_cnt= total_cnt + 1
new_cursor = info[0]
if(new_cursor == 0):
## 表示已经结束了
start_cursor = -1
else:
start_cursor = new_cursor
print("迁移完成,累计迁移key数量:",total_cnt,"。累计耗时:", time.time() - start_time, "ms")
但是后来运维同学发现了阿里云提供的一个迁移工具。所以最终这个脚本没有用到。然后次日凌晨我们按时停服维护,然后运维开始用阿里云工具迁移数据。结果该工具过于吃内存(可能是因为读取大key导致的)导致redis主库内存吃紧,最后系统把redis主库的进程给杀了。从而导致没有备份的数据彻底丢失了。
之后我们就开始了漫长的恢复数据的时间,我们只能去分析业务,然后根据mongo里的数据,把一些能恢复的核心的数据进行了恢复,原本预计七点重启服务的,结果一直到了下午四点才把服务再次起气来,导致了一次10小时停服的严重生产事故。服务重启后因为还有一些数据没有恢复,所以不断地有用户反馈问题,进行后续的问题处理。并且写数据恢复脚本的过程中,盲盒业务只考虑到了用户在丢失数据时间内购买的盲盒卡,而没有考虑到用户使用的盲盒卡,最后恢复盲盒卡数据的时候,多发了用户盲盒卡,虽然最后及时关停盲盒功能,追回了部分损失,但是仍然产生了不少额外的经济损失。