专栏首页程序员升级之路从一次线上故障来看redis删除机制

从一次线上故障来看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上查询,如果压力较大可以通过集群方案多堆些机器。

本文分享自微信公众号 - 程序员升级之路(gh_1fab42db66cb),作者:刘江华

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-02-08

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • redis-port支持前缀迁移

    redis-port是一款redis数据迁移工具,用来将数据从一个redis迁移到另一个redis实例/redis集群中 ,以下是官方地址:

    心平气和
  • Redis源码之常用数据结构和函数

    上一篇 扩展Redis:增加Redis命令 讲了如何动手编写一个命令,但没有具体讲代码的细节,今天讲下Redis代码中的常用数据结构和函数,看完这篇文章希望大家...

    心平气和
  • FastDFS不同步怎么破

    FastDFS是一款开源的分布式文件系统,具体介绍就不说了,有兴趣的可以自行百度下。

    心平气和
  • Redis键过期策略

    JavaEdge
  • [享学Eureka] 五、Eureka核心概念:应用(Application)和注册表(Applications)

    代码下载地址:https://github.com/f641385712/netflix-learning

    YourBatman
  • 关于Redis的几件小事 | Redis的数据类型/过期策略/内存淘汰

    这个是类似map的一种结构,这个一般就是可以将结构化的数据,比如一个对象(前提是这个对象没嵌套其他的对象)给缓存在redis里,然后每次读写缓存的时候,可以就操...

    王知无
  • 关于Redis的几件小事 | Redis的数据类型/过期策略/内存淘汰

    这个是类似map的一种结构,这个一般就是可以将结构化的数据,比如一个对象(前提是这个对象没嵌套其他的对象)给缓存在redis里,然后每次读写缓存的时候,可以就操...

    暴走大数据
  • 通用设计:指挥调度系统——复杂行动的大脑与神经

    对于B端或G端用户,我们描述了数据共享平台(用户数据的采集与整理),信息展示系统(数据的展示)的设计思路,在这篇文章中将侧重于描述信息的传递与应用。这里指的不是...

    物流IT圈
  • 数字营销新时代,看粤财汇如何演绎不一样的互金营销

    论金融品牌的营销方式,最常见的莫过于传统硬广、TVC投放,又或者是以各种行业会议亮相。不少互联网金融平台的营销传播看似声势浩大却不一定接地气。

    曾响铃
  • LeetCode 702. 搜索长度未知的有序数组(二分查找)

    给定一个升序整数数组,写一个函数搜索 nums 中数字 target。 如果 target 存在,返回它的下标,否则返回 -1。注意,这个数组的大小是未知的。...

    Michael阿明

扫码关注云+社区

领取腾讯云代金券