自顶向下redis4.0(4)时间事件与expire
redis4.0的时间事件与expire
简介
时间事件和文件事件有着相似的接口,他们都在aeProcessEvents
中被调用。不同的是文件事件底层委托给 select
,epoll
等多路复用接口。而时间事件通过每个tick检查时间事件的触发时间是否已经到期。redis
4.0版本中只注册了一个时间事件serverCron
,它在initServer
中注册,在每次aeProcessEvents
函数末尾被调用。上文已经提到aeMain
函数是redis
的事件主循环,它会不断地调用aeProcessEvents
。
expire
指令在server->expires
字典dict
中插入sds
内部数据类型的key值和到期时间,并触发键空间事件。在serverCron
中的databaseCron
函数中调用activeExpireCycle(ACTIVE_EXPIRE_CYCLE_SLOW)
随机抽取expires
中的键值,如果过期,则在server->dict
中删除对应的键值。
正文
时间事件注册
首先我们观察一下时间事件的结构体,虽然结构体中有许多成员,但可以说实际用到的就when_sec
,when_ms
,timeProc
3个成员还有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->argc
和client->argv
,方式是通过createStringObject
转化为对应的字符串类型的对象,因此,argv
中redisObject
的编码类型只可能是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
结合使用了定期删除和被动删除。
被动删除
在客户端向服务端发送get
,expire
等请求时,会调用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);
}
}
参考文献
自顶向下redis4.0(4)时间事件与expire的更多相关文章
- 自顶向下redis4.0(5)持久化
redis4.0的持久化 目录 redis4.0的持久化 简介 正文 rdb持久化 save命令 bgsave命令 rdb定期保存数据 进程结束保存数据 aof持久化 数据缓冲区 刷新数据到磁盘 ap ...
- 自顶向下redis4.0(2)文件事件与客户端
redis4.0的文件事件与客户端 目录 redis4.0的文件事件与客户端 简介 正文 准备阶段 接受客户端连接 处理数据 返回数据结果 参考文献 简介 文件事件的流程大概如下: 在服务器初始化时生 ...
- 自顶向下redis4.0(1)启动
redis4.0的启动流程 目录 redis4.0的启动流程 简介 正文 全局server对象 初始化配置 初始化服务器 事件主循环 参考文献 简介 redis 在接收客户端连接之前,大概做了以下几件 ...
- 自顶向下redis4.0(3)命令与dict
redis4.0的命令 简介 目录 redis4.0的命令 简介 正文 redisCommand与redisCommandTable 初始化命令 执行命令 set指令与字典 参考文献 正文 redis ...
- redis-4.0.8 配置文件解读
# Redis configuration file example.## Note that in order to read the configuration file, Redis must ...
- Redis4.0 主从复制(PSYN2.0)
Redis4.0版本相比原来3.x版本,增加了很多新特性,如模块化.PSYN2.0.非阻塞DEL和FLUSHALL/FLUSHDB.RDB-AOF混合持久化等功能.尤其是模块化功能,作者从七年前的re ...
- redis4.0.10安装与常用命令
----------- redis安装 ------------------------------------------- 安装reids:https://redis.io/download (4 ...
- Redis4.0支持的新功能说明
本文以华为云DCS for Redis版本为例,介绍Redis4.0的新功能.文章转载自华为云帮助中心. 与Redis3.x版本相比,DCS的Redis4.x以上版本,除了开源Redis增加的特性之外 ...
- Redis4.0新特性(一)-Memory Command
Redis4.0版本增加了很多诱人的新特性,在redis精细化运营管理中都非常有用(猜想和antirez加入redislabs有很大关系):此系列几篇水文主要介绍以下几个新特性的使用和效果. Redi ...
随机推荐
- ①SpringCloud 实战:引入Eureka组件,完善服务治理
简介 Netflix Eureka 是一款由 Netflix 开源的基于 REST 服务的注册中心,用于提供服务发现功能.Spring Cloud Eureka 是 Spring Cloud Netf ...
- 面试老被问LinkedList源码?看看阿里技术官是怎么深度剖析的吧!
前言 LinkedList底层是基于双向链表,链表在内存中不是连续的,而是通过引用来关联所有的元素,所以链表的优点在于添加和删除元素比较快,因为只是移动指针,并且不需要判断是否需要扩容,缺点是查询和遍 ...
- IDM下载器的队列功能有什么用?
使用IDM下载器中的队列功能,可以帮助大家快速分类下载任务,这样,就可以统一管理有同样下载需求的内容. 一.队列的添加及设置 打开IDM下载器,单击菜单中的"队列",可以看到在左侧 ...
- 对JVM的一个基础了解
1.JVM范围 2.JVM和class文件 (1).JVM和Java语言无关,JVM是一种规范,任何语言只要能编译成class文件格式都能在JVM上运行 3.class文件格式 (1).class文件 ...
- Jmeter(二十八) - 从入门到精通 - Jmeter Http协议录制脚本工具-Badboy1(详解教程)
1.简介 在使用jmeter自动录制脚本时会产生很多无用的请求,所以推荐使用badboy录制脚本之后保存为jmx文件,在jmeter中打开使用.因此宏哥在这里介绍一下Badboy这款工具,本来打算不做 ...
- sentinel--初级使用篇
1.官方资料 github官网地址:https://github.com/alibaba/Sentinel wiki:https://github.com/alibaba/Sentinel/wiki/ ...
- java多线程--【Foam番茄】
进程 是系统资源分配的单位 线程 通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义.线程是cpu调度和执行的单位 注意:很多多线程是模拟出来的,真正的多线程是指有多 ...
- ZAB
ZAB=ZooKeeper Atomic Broadcast ZooKeeper原子消息广播协议,支持崩溃回复的原子广播协议. zk使用一个单一的主进程来接受并处理客户端的所有事务请求,并采用ZAB的 ...
- 【mq读书笔记】消息拉取长轮训机制(Broker端)
RocketMQ并没有真正实现推模式,而是消费者主动想消息服务器拉取消息,推模式是循环向消息服务端发送消息拉取请求. 如果消息消费者向RocketMQ发送消息拉取时,消息未到达消费队列: 如果不启用长 ...
- Django 的缓存机制
一 缓存介绍: 在动态网站中,用户所有的请求,服务器都会去数据库中进行相应的增,删,查,改,渲染模板,执行业务逻辑,最后生成用户看到的页面. 当一个网站的用户访问量很大的时候,每一次的的后台操作,都会 ...