一、问题及背景

公司去年上线一个抽奖系统,主要用来拉新、提升流量,所有新注册的用户在指定时间都可以抽奖,为了保证安全性,程序中做了频率限制,每个用户30秒只能抽1次,具体做法是以用户id为key,保存在redis中,过期时间为30秒;抽奖时会先读取这个key是否存在,如果存在则认为用户在30秒内已经抽过,返回稍后再试。

因为读多写少,为了提高系统的吞吐量,系统采用了redis读、写分离的架构,即写入的时候往master上写,读取用户是否抽过奖则从slave上读取,redis版本为2.8.6。

这个系统上线后前几天运行比较良好,某天突然报大量的稍后重试的错误,不少用户反馈抽了一次奖后再也无法抽奖。

通过日志分析和数据核对发现某个key过期了,但在slave上还可以读取的到。

复现如下:

在主上设置一个key

set name  edwardexpire 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上查询,如果压力较大可以通过集群方案多堆些机器。

删除时存在依赖_从一次线上故障来看redis删除机制相关推荐

  1. idea本地跑如何看gc日志_线上故障如何快速排查?来看这套技巧大全

    简介:有哪些常见的线上故障?如何快速定位问题?本文详细总结工作中的经验,从服务器.Java应用.数据库.Redis.网络和业务六个层面分享线上故障排查的思路和技巧.较长,同学们可收藏后再看. 前言 线 ...

  2. du -sh 如何找到最大的文件夹_线上故障如何快速排查?来看这套技巧大全

    简介:有哪些常见的线上故障?如何快速定位问题?本文详细总结工作中的经验,从服务器.Java应用.数据库.Redis.网络和业务六个层面分享线上故障排查的思路和技巧.较长,同学们可收藏后再看. 前言 线 ...

  3. linux 内存溢出排查_记一次JAVA 线上故障排查完整套路

    JAVA线上故障排查全套路 线上故障主要会包括cpu.磁盘.内存以及网络问题,而大多数故障可能会包含不止一个层面的问题,所以进行排查时候尽量四个方面依次排查一遍.同时例如jstack.jmap等工具也 ...

  4. java 日志乱码_【开发者成长】JAVA 线上故障排查完整套路!

    云栖号资讯:[点击查看更多行业资讯] 在这里您可以找到不同行业的第一手的上云资讯,还在等什么,快来! 线上故障主要会包括 CPU.磁盘.内存以及网络问题,而大多数故障可能会包含不止一个层面的问题,所以 ...

  5. linux 内存溢出排查_【开发者成长】JAVA 线上故障排查完整套路!

    云栖号资讯:[点击查看更多行业资讯] 在这里您可以找到不同行业的第一手的上云资讯,还在等什么,快来! 线上故障主要会包括 CPU.磁盘.内存以及网络问题,而大多数故障可能会包含不止一个层面的问题,所以 ...

  6. java代码读写者问题_一整套Java线上故障排查技巧,爱了!

    点击上方 好好学java ,选择 星标 公众号 重磅资讯.干货,第一时间送达 今日推荐:腾讯推出高性能 RPC 开发框架 个人原创100W+访问量博客:点击前往,查看更多 来源:fredal.xin/ ...

  7. java mysql死锁_记一次线上mysql死锁分析(一)

    记录一次比较诡异的mysql死锁日志.系统运行几个月来,就在前几天发生了一次死锁,而且就只发生了一次死锁,整个排查过程耗时将近一天,最后感谢我们的DBA大神和老大一起分析找到原因. 诊断死锁 借助于我 ...

  8. telephone 为空 唯一索引_记一次线上唯一索引失效没有起效的场景

    背景描述:我们系统A做远程在线接口提供给B系统调用,每次的请求参数中都带有幂等单号用来做幂等校验,幂等单号对应的字段是不可空且唯一的.由于对应的业务表线上已有数据,这个幂等字段数据新加的字段,所以要做 ...

  9. 在线分析mysql死锁详解_记一次线上mysql死锁分析(一)

    记录一次比较诡异的mysql死锁日志.系统运行几个月来,就在前几天发生了一次死锁,而且就只发生了一次死锁,整个排查过程耗时将近一天,最后感谢我们的DBA大神和老大一起分析找到原因. 诊断死锁 借助于我 ...

最新文章

  1. 他24岁,4篇Nature在手,也会关心学不懂C语言怎么办
  2. python scrapy框架原理_Scrapy框架的工作原理是什么?
  3. hive数据倾斜的解决办法
  4. PHP程序员应该掌握的10项技能
  5. linux中怎样进入桌面目录,linux – 如何获得给定用户“桌面路径”
  6. linux注释内容,Linux 中snmptrapd的内容有大虾能帮我注释下吗?谢谢了
  7. win8好用吗_小编告诉你win8好用吗?你有用过吗
  8. 约瑟夫环问题(动态链表操作)n个学生围成一圈,每m个出队,输出所有出队的序列
  9. 激励机制中的经济学和博弈论模型(2)
  10. stata 导出 相关系数表_【BBtime】戏说会计论文---stata简单实操
  11. 如何复制虚拟机中的数据到真实主机
  12. 核定征收的个体户,年营业额不超过120万,还需要缴纳个税吗?
  13. 【Cpp】C和C++混合编程
  14. python进阶day6
  15. Win勒索病毒害惨中国学生!微软:最新Win10很安全
  16. Redis缓存一致性问题解决方案
  17. LG30刷小米系统_闲鱼300块入手小米平板1,性能居然秒杀1099的小米平板4
  18. 如果阿里裁员30%是真的,你拿什么和阿里背景的程序员竞争?
  19. 如何证明凸函数的局部极小值为全局极小值
  20. ggplot2 保存图片字体错误问题

热门文章

  1. R语言ggplot2可视化指定保存到pdf的图像的具体尺寸、保证缩放的一致性:使得绘图元素(文本、点大小等)在设计上都具有相同的绝对大小、设置全局数据点大小、主题格式、设置图像保存的具体尺寸
  2. R语言构建logistic回归模型:WVPlots包PRTPlot函数可视化获取logistic回归模型的最优阈值、优化(precision、enrichment)和recall之间的折衷
  3. R使用glm构建logistic回归模型
  4. 高斯混合模型(GaussianMixture Model, GMM)聚类、可视化最优协方差形式、通过TSNE进行结果可视化分析、抽取核心特征因子
  5. scuttle包对单细胞数据质控
  6. java语言仅支持单重继承_java语言程序设计基础篇习题_复习题_第十一章
  7. KDT 对比 DDT 小栗子
  8. hadoop_入门1
  9. Boost boost_1_63_0安装 gcc4.8 gcc5.4
  10. 论文笔记 Medical Entity Linking using Triplet Network