从一次线上故障来看redis删除机制
一、问题及背景
公司去年上线一个抽奖系统,主要用来拉新、提升流量,所有新注册的用户在指定时间都可以抽奖,为了保证安全性,程序中做了频率限制,每个用户30秒只能抽1次,具体做法是以用户id为key,保存在redis中,过期时间为30秒;抽奖时会先读取这个key是否存在,如果存在则认为用户在30秒内已经抽过,返回稍后再试。
因为读多写少,为了提高系统的吞吐量,系统采用了redis读、写分离的架构,即写入的时候往master上写,读取用户是否抽过奖则从slave上读取,redis版本为2.8.6。
这个系统上线后前几天运行比较良好,某天突然报大量的稍后重试的错误,不少用户反馈抽了一次奖后再也无法抽奖。
通过日志分析和数据核对发现某个key过期了,但在slave上还可以读取的到。
复现如下:
在主上设置一个key
set name edward
expire name 5
过了5秒等key过期后再到slave上读取,但get返回不为空
但这个时候如果在master上get1次,再到slave上get,结果就是空了。
二、故障分析
为什么会出现这种情况呢,我们来分析下redis中key过期删除策略,redis中key过期删除策略有二种:主动删除、惰性删除。
1、主动删除
是在服务端的定时任务中执行,相关代码如下:
int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
……
/* Handle background operations on Redis databases. */
databasesCron();
……
}
serverCron函数在redis启动的时候注册到定时器中,执行频率大概为100毫秒1次,具体参考aeCreateTimeEvent函数。
其中databasesCron函数为将过期的key进行随机删除:
if (server.active_expire_enabled && server.masterhost == NULL)
activeExpireCycle(ACTIVE_EXPIRE_CYCLE_SLOW);
这里会判断当前实例是否为主实例,只有主实例并且active_expire_enabled 启用(默认会启用)才会启动删除机制,activeExpireCycle 函数中会随机删除一些过期的key,注意是随机删除一些过期的key,而不是全部删除,因为redis要考虑系统的负载,怕执行时间太长会抢占太多CPU,增加系统负载,这里就不细讲,有兴趣的同学可以细看下代码。
结论:主动删除只有在master上生效。
2、惰性删除
再看get命令:
int getGenericCommand(redisClient *c) {
robj *o;
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL)
return REDIS_OK;
if (o->type != REDIS_STRING) {
addReply(c,shared.wrongtypeerr);
return REDIS_ERR;
} else {
addReplyBulk(c,o);
return REDIS_OK;
}
}
lookupKeyReadOrReply 函数会调用lookupKeyRead函数,后者会调用到expireIfNeeded 先检测是否过期了:
int expireIfNeeded(redisDb *db, robj *key) {
mstime_t when = getExpire(db,key);
mstime_t now;
/* If we are running in the context of a slave, return ASAP:
* the slave key expiration is controlled by the master that will
* send us synthesized DEL operations for expired keys.
*
* Still we try to return the right information to the caller,
* that is, 0 if we think the key should be still valid, 1 if
* we think the key is expired at this time. */
if (server.masterhost != NULL) return now > when;
}
通过代码发现,如果当前实例为slave则直接返回,不会做进一步的处理;作者也做了注释,说slave上过期的key会依赖master发过来的DEL命令来删除。
结论:redis的惰性删除机制是在执行用户请求的时候判断key是否过期,惰性删除也只在master上生效,slave上是不生效的。
我们来总结下:
1、redis中过期key的删除有2种策略:主动删除、惰性删除。
2、主动删除和惰性删除只在master上发生,slave的删除机制依赖于master。
回到上面的问题,是什么原因导致key过期了,而slave上还有值,因为master没有及时将过期的key删除,即没有触发主动删除机制,这时候也没有在master上读取数据,即执行get命令,所以也不会触发master上的惰性删除机制,所以slave上的key没有及时删除。
为什么master的主动删除没有触发呢?,原因有二:
1、redis的定时任务执行有延迟
redis尽量保证按指定时间执行指定任务,不过如果当时CPU抢占的比较厉害,定时任务执行时间可能有很大的延迟,这个期间一些key没有及时删除。
这种情况一般发生在redis实例所在机器cpu负载很高的情况。
2、因为redis的是随机删除的,可能会导致部分过期key没有被及时删除掉
这个只发生在redis中有大量的过期的key的情况下
三、解决方案
好了,问题原因找到了,那我们的解决方案是什么呢?
禁止在slave上查询一些关键信息:像锁、登录信息,这些信息必须从master上查询,如果压力较大可以通过集群方案多堆些机器。
- UWP基础教程 - {x:DeferLoadStrategy}
- UWP基础教程 - 重启应用
- html5打开摄像头
- UWP基础教程 - App多语言支持
- Golang实现Fibonacii的几种算法
- 【译】使用 dotnet watch 开发 ASP.NET Core 应用
- vmware安装ubuntu12.04嵌套安装xen server(实现嵌套虚拟化)
- Golang语言切片slice的线程协程安全问题
- ASP.NET Core 在 Azure 开启 HTTPS
- 算法基础:最大递减数问题(Golang实现)
- 亲身经历的痛--database/sql: Stmt的使用以及坑
- Ubuntu上通过nginx部署Django笔记
- Go学习笔记:golang交叉编译
- Python魔术方法-Magic Method
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 贪心-HDU1789 Doing Homework again(活动安排问题)
- flink实战-实时计算平台通过api停止流任务
- JAVA初级岗面试知识点——基础篇
- flink实战-flink streaming sql 初体验
- flink实战教程-使用set实时计算当天网站uv
- 贪心-HDU3348 coins(钱币问题)
- 归并排序详解 -HDU4911 Inversion(逆序对)
- 数据结构与算法——稀疏数组
- Maven安装配置详细教程
- 数据结构与算法——冒泡排序
- MyBatis Generator逆向工程-你还在手写mapper吗?
- JSR303后端校验详解
- SSM整合开发实战 SSM-MALL
- SQL Server获取元数据所有方法和示例
- 数据结构与算法——选择排序