redis4.0的时间事件与expire

简介

时间事件和文件事件有着相似的接口,他们都在aeProcessEvents中被调用。不同的是文件事件底层委托给 select,epoll等多路复用接口。而时间事件通过每个tick检查时间事件的触发时间是否已经到期。redis4.0版本中只注册了一个时间事件serverCron,它在initServer中注册,在每次aeProcessEvents函数末尾被调用。上文已经提到aeMain函数是redis的事件主循环,它会不断地调用aeProcessEvents

expire指令在server->expires字典dict中插入sds内部数据类型的key值和到期时间,并触发键空间事件。在serverCron中的databaseCron函数中调用activeExpireCycle(ACTIVE_EXPIRE_CYCLE_SLOW)随机抽取expires中的键值,如果过期,则在server->dict中删除对应的键值。

正文

时间事件注册

首先我们观察一下时间事件的结构体,虽然结构体中有许多成员,但可以说实际用到的就when_secwhen_mstimeProc3个成员还有timeProc的返回值。我们可以观察到aeTimeProc会返回一个int类型的值,如果不为-1,会作为下次调用的间隔时间。

/* Time event structure */
typedef struct aeTimeEvent {
long long id; /* time event identifier. */
long when_sec; /* seconds */
long when_ms; /* milliseconds */
aeTimeProc *timeProc;
aeEventFinalizerProc *finalizerProc;
void *clientData;
struct aeTimeEvent *prev;
struct aeTimeEvent *next;
} aeTimeEvent;
typedef int aeTimeProc(struct aeEventLoop *eventLoop, long long id, void *clientData);

注册的函数位于initServer中,aeCreateTimeEvent函数会生成一个aeTimeEvent对象,并将其赋值给eventLoop->timeEventHead

    if (aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {
serverPanic("Can't create event loop timers.");
exit(1);
}

时间事件触发

真正处理时间事件的函数是processTimeEvents,但我们回到aeProcessEvents中学习redis中的一个小技巧。aeSearchNearestTimer会找到距离最近的时间事件。如果有(正常情况下肯定会有一个serverCron函数),那么会将距离下一次时间事件的间隔事件写入tvp参数,在aeApiPoll参数中会传入tvp,如果一直没有文件事件触发,那么aeApiPoll函数会等待恰当的时间返回,函数返回后刚好可以处理时间事件。

int aeProcessEvents(aeEventLoop *eventLoop, int flags)
{
int processed = 0, numevents;
int j;
aeTimeEvent *shortest = NULL;
struct timeval tv, *tvp; shortest = aeSearchNearestTimer(eventLoop);
if (shortest) {
long now_sec, now_ms;
aeGetTime(&now_sec, &now_ms);
tvp = &tv; /* How many milliseconds we need to wait for the next
* time event to fire? */
long long ms =
(shortest->when_sec - now_sec)*1000 +
shortest->when_ms - now_ms; if (ms > 0) {
tvp->tv_sec = ms/1000;
tvp->tv_usec = (ms % 1000)*1000;
} else {
tvp->tv_sec = 0;
tvp->tv_usec = 0;
}
} numevents = aeApiPoll(eventLoop, tvp); for (j = 0; j < numevents; j++) {
//process file events
} /* Check time events */
if (flags & AE_TIME_EVENTS)
processed += processTimeEvents(eventLoop); return processed; /* return the number of processed file/time events */
}

processTimeEvents函数中,会遍历之前注册的函数,如果时间条件满足,则会调用对应的函数。如果函数返回的值不是-1,意味着函数将会利用返回值作为下一次调用函数的间隔时间。ServerCron的频率定义在server.hz,表示一秒钟调用几次serverCron函数,默认是10次/秒。

expire命令

在了解expire命令之前,我们先回顾一下前文的内容,在 文件事件处理过程中,redis会将querybuf中的内容转化为client->argcclient->argv,方式是通过createStringObject转化为对应的字符串类型的对象,因此,argvredisObject的编码类型只可能是embstr或者是raw

#define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44
robj *createStringObject(const char *ptr, size_t len) {
if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT)
return createEmbeddedStringObject(ptr,len);
else
return createRawStringObject(ptr,len);
}

如果传递的time to live参数是负数,那么exipre指令会被转化为del指令,直接删除对应的键值。

否则在server->expire内部数据类型dict中添加对应的到期时间。

void expireGenericCommand(client *c, long long basetime, int unit) {
robj *key = c->argv[1], *param = c->argv[2];
long long when; /* unix time in milliseconds when the key will expire. */ if (getLongLongFromObjectOrReply(c, param, &when, NULL) != C_OK)
return; if (unit == UNIT_SECONDS) when *= 1000;
when += basetime; /* No key, return zero. */
if (lookupKeyWrite(c->db,key) == NULL) {
addReply(c,shared.czero);
return;
} if (when <= mstime() && !server.loading && !server.masterhost) {
robj *aux; int deleted = dbSyncDelete(c->db,key);
serverAssertWithInfo(c,key,deleted);
server.dirty++; aux = shared.del;
rewriteClientCommandVector(c,2,aux,key);
signalModifiedKey(c->db,key);
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",key,c->db->id);
addReply(c, shared.cone);
return;
} else {
setExpire(c,c->db,key,when);
addReply(c,shared.cone);
signalModifiedKey(c->db,key);
notifyKeyspaceEvent(NOTIFY_GENERIC,"expire",key,c->db->id);
server.dirty++;
return;
}
}

删除过期键值

删除过期键值的方式有3种:定时删除,定期删除,被动删除。redis结合使用了定期删除和被动删除。

被动删除

在客户端向服务端发送getexpire等请求时,会调用expireIfNeeded(c->db,c->argv[j]);函数删除过期的键值。令人好奇的是del请求也会调用expireIfNeeded,也就是有可能调用2次dbSyncDelete函数。

主动删除/定期删除

在前文提到的时间事件serverCron函数中,如果不是从库并且开启了active_expire_enabled(默认开启),则会调用activeExpireCycle函数主动清理过期的键值。

默认情况下,CRON_DBS_PER_CALL的值为16,也是dbnum的值,意味着activeExpireCycle一次会处理16个数据库。而且如果上次调用超时,也会按照一次处理dbnum的数据库处理。

并且对每个数据库至少会进行一轮处理,一轮处理中抽取20个样本,如果样本过期,则删除该键。而且如果样本中过期的键超过25%并且没有超时,则会继续迭代,再进行一轮处理。

timelimit的单位是微秒,如果对当前db处理的过程中超时,那么处理之后的db只进行一轮处理。

所以定期删除并不会将所有的过期键删除,在服务器正常运行的情况下,过期键会维持在25%以内。

void activeExpireCycle(int type) {
//静态全局变量
static unsigned int current_db = 0; /* Last DB tested. */
static int timelimit_exit = 0; /* Time limit hit in previous call? */ int j, iteration = 0;
int dbs_per_call = CRON_DBS_PER_CALL;
long long start = ustime(), timelimit; //一次处理多少db
if (dbs_per_call > server.dbnum || timelimit_exit)
dbs_per_call = server.dbnum; //时间限制,如果总的时间超过限制,则只处理一轮当前的db
timelimit = 1000000*ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC/server.hz/100;
timelimit_exit = 0;
if (timelimit <= 0) timelimit = 1; if (type == ACTIVE_EXPIRE_CYCLE_FAST)
timelimit = ACTIVE_EXPIRE_CYCLE_FAST_DURATION; /* in microseconds. */ for (j = 0; j < dbs_per_call && timelimit_exit == 0; j++) {
int expired;
redisDb *db = server.db+(current_db % server.dbnum);
current_db++; do {
unsigned long num, slots;
long long now, ttl_sum;
int ttl_samples;
iteration++; if ((num = dictSize(db->expires)) == 0) {
db->avg_ttl = 0;
break;
}
slots = dictSlots(db->expires);
now = mstime(); expired = 0; if (num > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP)
num = ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP; while (num--) {
dictEntry *de; if ((de = dictGetRandomKey(db->expires)) == NULL) break;
if (activeExpireCycleTryExpire(db,de,now)) expired++;
}
total_ += expired; 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;
}
} } while (expired > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP/4);
}
}

参考文献

redis 文档

《Redis设计与实现》

自顶向下redis4.0(4)时间事件与expire的更多相关文章

  1. 自顶向下redis4.0(5)持久化

    redis4.0的持久化 目录 redis4.0的持久化 简介 正文 rdb持久化 save命令 bgsave命令 rdb定期保存数据 进程结束保存数据 aof持久化 数据缓冲区 刷新数据到磁盘 ap ...

  2. 自顶向下redis4.0(2)文件事件与客户端

    redis4.0的文件事件与客户端 目录 redis4.0的文件事件与客户端 简介 正文 准备阶段 接受客户端连接 处理数据 返回数据结果 参考文献 简介 文件事件的流程大概如下: 在服务器初始化时生 ...

  3. 自顶向下redis4.0(1)启动

    redis4.0的启动流程 目录 redis4.0的启动流程 简介 正文 全局server对象 初始化配置 初始化服务器 事件主循环 参考文献 简介 redis 在接收客户端连接之前,大概做了以下几件 ...

  4. 自顶向下redis4.0(3)命令与dict

    redis4.0的命令 简介 目录 redis4.0的命令 简介 正文 redisCommand与redisCommandTable 初始化命令 执行命令 set指令与字典 参考文献 正文 redis ...

  5. redis-4.0.8 配置文件解读

    # Redis configuration file example.## Note that in order to read the configuration file, Redis must ...

  6. Redis4.0 主从复制(PSYN2.0)

    Redis4.0版本相比原来3.x版本,增加了很多新特性,如模块化.PSYN2.0.非阻塞DEL和FLUSHALL/FLUSHDB.RDB-AOF混合持久化等功能.尤其是模块化功能,作者从七年前的re ...

  7. redis4.0.10安装与常用命令

    ----------- redis安装 ------------------------------------------- 安装reids:https://redis.io/download (4 ...

  8. Redis4.0支持的新功能说明

    本文以华为云DCS for Redis版本为例,介绍Redis4.0的新功能.文章转载自华为云帮助中心. 与Redis3.x版本相比,DCS的Redis4.x以上版本,除了开源Redis增加的特性之外 ...

  9. Redis4.0新特性(一)-Memory Command

    Redis4.0版本增加了很多诱人的新特性,在redis精细化运营管理中都非常有用(猜想和antirez加入redislabs有很大关系):此系列几篇水文主要介绍以下几个新特性的使用和效果. Redi ...

随机推荐

  1. 历时两年零三个月,从刚毕业的外包到现在的阿里P7offer,我只做了这几件事

    前言 最近,金九银十在即,很多人都在准备面试,特别给大家总结了 Java 程序员面试必备题,这份面试清单是我从 去年开始收集的,一方面是给公司招聘用,另一方面是想用它来挖掘我在 Java 技术栈中的技 ...

  2. PowerPoint无法正常加载MathType的解决方法

    MathType是一款十分便捷的数学公式编辑器,可以和很多办公软件和网站兼容使用,我们日常用的比较多的也就是Office和WPS,更具体的说是Word\Excel\PPT等等一系列办公常用软件. 不过 ...

  3. 从这三方面优化你的电脑,保持Mac运行流畅

    使用着Mac系统的用户都知道,Mac OS的各方面性能都很好,特别是流畅性,有人说不用清理垃圾也能流畅地使用Mac,但这的确是夸张了.电脑使用的时间长了,它的性能总会越来越退步,这其中有着系统垃圾拖累 ...

  4. Linux安装MySQL5.7(CentOS)

    1.下载解压 1.1 MySql 5.7.26下载地址: https://dev.mysql.com/downloads/mysql/5.7.html#downloads 1.2 解压 tar -xv ...

  5. codeforces 1424J,为了过这题,我把祖传的C++都用上了!

    大家好,我们选择的是Bubble Cup比赛Div2场次的J题,不用问我Bubble Cup是什么比赛,我也不清楚.总之是一场算法比赛就是了.可能是这个比赛知名度比较低吧,参与的人数也不是很多,我们选 ...

  6. java导出excel并且压缩成zip上传到oss,并下载,使用字节流去存储,不用文件流保存文件到本地

    最近项目上要求实现导出excel并根据条数做分割,然后将分割后的多个excel打包成压缩包上传到oss服务器上,然后提供下载方法,具体代码如下:这里只展示部分代码,获取数据的代码就不展示了 ByteA ...

  7. 15_Android文件读写操作

    1. 文件的基本操作 File类的相关技巧和操作:文件的创建.重命名和删除,文件夹的创建和删除等操作. 1 package control; 2 3 import java.io.File; 4 5 ...

  8. 「刷题笔记」哈希,kmp,trie

    Bovine Genomics 暴力 str hash+dp 设\(dp[i][j]\)为前\(i\)组匹配到第\(j\)位的方案数,则转移方程 \[dp[i][j+l]+=dp[i-1][j] \] ...

  9. 基于CefSharp开发(四)浏览器文件下载

    一.CefSharp文件下载分析 查看ChromiumWebBrowser类发现cef数据下载处理在IDownloadHandler中进行,但并未找到相应的实现类,故我们需要自己实现DownloadH ...

  10. Django+Nginx+uWSGI生产环境部署

    生产环境中的数据流 参考文档: wsgi详解:https://blog.csdn.net/li_101357/article/details/52748323 wsgi协议介绍(萌新版):https: ...