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. Java的BigDecimal,对运算封装

    添加maven依赖 <dependency> <groupId>com.google.guava</groupId> <artifactId>guava ...

  2. pip install 一个本地包时提示error: Microsoft Visual C++ 14.0 is required.

    错误如下: error: Microsoft Visual C++ 14.0 is required. Get it with "Microsoft Visual C++ Build Too ...

  3. [大雾雾雾雾] 告别该死的 EFCore Fluent API (续)

    朋友们好啊, 我是 .NET 打工人 玩双截棍的熊猫 刚才有个朋友问我 猫猫发生什么事了 我说 怎么回事? 给我发了一张截图 我一看!嗷!原来是zuo天有两个数据库, 一个四十多岁,一个三十多岁 它们 ...

  4. 你也想当流量UP主?那就点开看看吧!

    2009年6月份,哔哩哔哩(B站)在一众期待中诞生,它汇聚了天南海北当时小众的二次元同好,它也存在诸多不足,大家亲切地叫它"小破站". 而如今,它成长为一棵枝繁叶茂的参天大树,成为 ...

  5. 在CorelDRAW2019创建对称绘图模式

    对称绘图模式是CorelDRAW 2018推出的全新功能,在2019的版本中又得到了极大的完善,通过对称绘图模式可以创建平衡.和谐.独一无二的效果,对称在大自然中随处可见,因此设计元素很可能将依靠于它 ...

  6. H5系列之drag拖放

    H5中, 有个属性,draggable="true", 这个属性呢(默认false),需要加在标签上,加上去该标签就可以拖动了, 看下gif图吧 默认的标签,是不能拖动的,但是有两 ...

  7. mysql主从同步上---主从同步原理

    1.主从同步机制   1.1 主从同步介绍和优点   在多台数据服务器中,分为主服务器和从服务器.一台主服务器对应多台从服务器. 主服务器只负责写入数据,从服务器只负责同步主服务器的数据,并让外部程序 ...

  8. 删除list列表中的某一个元素的多种方法

    当我们在处理业务的时候,很多情况下数据都要进行一层层的过滤,最近需要给一个列表中去除不符合条件的元素, 本来觉着挺简单的,Google了下发现很多方法都是旧方法,根本不符合我的需求. 于是参考着网上的 ...

  9. centos7安装Python的虚拟环境

    1. 安装virtualenv.virtualenvwrapper # pip3 install virtualenv # pip3 install virtualenvwrapper 2. 进入.b ...

  10. CSS聚光灯文字(无图片)

    Welcome to my admin site! h1 { font-size: 70px; color: rgba(255, 255, 255, 1); padding: 0; margin: 0 ...