由于工作原因,现在已经很少能长篇大论介绍Redis的相关技术,但日常工作以及脑子中经常想整理和总结一些Redis的点点滴滴,既能帮助自己记录问题,又能帮助他人减少碰到类似问题,于是有个想法,准备写一个小的系列:Redis小功能大用处。

我也不立下flag了,有空抽一小时写一篇(本篇完成时间50分钟),希望有时间多写一点。

本文将介绍Redis 4后一个新的统计项stat_expired_time_cap_reached_count。

一、遇到的问题

某个业务客户端耗时周期性增加,如下图:

Redis小功能大用处(1)-stat_expired_time_cap_reached_count_java

这类问题处理起来也不难,依据经验:周期性慢查询、周期性redis异常行为(譬如aof写入、aof重写异常、全量复制异常、”备份“异常)

查了一圈均没问题:上述指标正常,但是观察到一个和这个周期性相关的,就是过期:

Redis小功能大用处(1)-stat_expired_time_cap_reached_count_java_02周期性过期

但业务并不认为有问题。

但当我们拿 stat_expired_time_cap_reached_count 来说明后,业务也无话可说,默默去改过期时间。

Redis小功能大用处(1)-stat_expired_time_cap_reached_count_java_03

二、stat_expired_time_cap_reached_count做什么用的

1.含义

Redis对于过期键的删除策略有主动和被动两种,被动详细介绍网上有很多,大概思路是:定期(hz=10,1秒10次)采样过期键的dict(expire dict),如果发现过期键就将他删除,但每次删除也要有25毫秒的超时时间(因为这个工作也是在主线程执行的,简单认为得留着时间干正事),如果超时,stat_expired_time_cap_reached_count会自增,这就是它的含义,用来记录被动过期键删除超时次数,如果每次都超时,意味着Redis要拿出四分之一(25毫秒 * 10)的时间来干这个事情。

2.怎么查(定期收集做差值)

redis-cli > info stats
# Stats
......
expired_time_cap_reached_count:182710
......

3.源码

每16次迭代检查一次是否超时(16个是因为默认是16个数据库),并完成记录,同时更新timelimit_exit=1,后续会开启fast-expire模式,这里就不多做说明了。

expire.c

/* We can't block forever here even if there are many keys to
 * expire. So after a given amount of milliseconds return to the
 * caller waiting for the other active expire cycle. */
if ((iteration & 0xf) == 0) { /* check once every 16 iterations. */
    elapsed = ustime()-start;
    if (elapsed > timelimit) {
        timelimit_exit = 1;
        server.stat_expired_time_cap_reached_count++;
        break;
    }
}

三、经验

1.对于Redis设置过期时间是一个好习惯(前提是正确的生命周期),但集中过期始终不是什么好事情。

(1) 瞬间缓存穿透到底层数据源(譬如数据库,第三方API)等,对其造成非预期压力

(2) 超时:Redis在集中过期期间花”大力气“处理过期键的删除。

2.单个Redis实例的过期键值数不宜过多(千万以内比较合理),尤其是生命周期时间较多。

3.把监控做好非常的重要!!

Redis小功能大用处(1)-stat_expired_time_cap_reached_count_java_04