这里主要讲的Redis是怎么样设置过时键的,能够算做后续"Redis过时键的删除策略"的前篇或者说预备知识。java
在了解过时键问题前咱们首先须要对redis的数据库和数据库键空间有必定的了解:redis
struct redisServer {
// ...
// 一个数组,保存着服务器中的全部数据库
redisDb *db;数据库
//服务器的数据库数量,dbnum属性的值由服务器配置的database选项决定,默认状况下,该选项的值为16
int dbnum;数组
// ...
};服务器
在服务器内部,客户端状态redisClient结构的db属性记录了客户端当前的目标数据库,这个属性是一个指向redisDb结构的指针:函数
typedef struct redisClient {
// ...
// 记录客户端当前正在使用的数据库
redisDb *db;
// ...
} redisClient;学习
如今咱们再来看看redisDb 结构,redisDb结构的dict字典保存了数据库中的全部键值对,咱们将这个字典称为键空间(key space):this
typedef struct redisDb {
// ...
//
数据库键空间,保存着数据库中的全部键值对
dict *dict;
// ...
} redisDb;spa
下面是一个例子:翻译
根据这个键空间,执行相关的添加、删除、更新等操做的即可以比较容易理解,咱们此处也忽略不讲了。
下面咱们再来介绍一下redisDb结构中的另一个字典expires,这个字典保存了数据库中全部键的过时时间,咱们称这个字典为过时字典(注意这里面只保存着键的过时时间,可不是说这个字典里面的键都是过时的):
有了上面的知识咱们下载即可以来看看四个命令:expire、pexpire、expireat、pexpireat的实现过程。
四个命令的使用是比较简单的:EXPIRE <key> <seconds> 如:EXPIRE book 100
PEXPIRE <key> <millionseconds>
EXPIREAT <key> <timestamp>
PEXPIREAT <key> <timestamp> 如:PEXPIREAT book 1388556000000(2014年1月1日零时)其实我也不知道这是怎么算出来的!!!
注意:利用PERSIST命令能够移除一个键的过时时间。
如:redis> PEXPIREAT message 1391234400000
(integer) 1
其余的命令相似。不过值得一提的是:EXPIRE、EXPIREAT、PEXPIRE所有是转换成PEXPIREAT来实现的。下面来看看每一个命令的实现函数:
void expireCommand(redisClient *c) {
expireGenericCommand(c,mstime(),UNIT_SECONDS);
}
void expireatCommand(redisClient *c) {
expireGenericCommand(c,0,UNIT_SECONDS);
}
void pexpireCommand(redisClient *c) {
expireGenericCommand(c,mstime(),UNIT_MILLISECONDS);
}
void pexpireatCommand(redisClient *c) {
expireGenericCommand(c,0,UNIT_MILLISECONDS);
}
他们都调用了expireGenericCommand()函数进行实现,那咱们如今就来分析一下expireGenericCommand函数是怎么实现的:
/*----------------------------------------------------------------------------- * Expires Commands *----------------------------------------------------------------------------*/ /* This is the generic command implementation for EXPIRE, PEXPIRE, EXPIREAT * and PEXPIREAT. Because the commad second argument may be relative or absolute * the "basetime" argument is used to signal what the base time is (either 0 * for *AT variants of the command, or the current time for relative expires). * * 这个函数是 EXPIRE 、 PEXPIRE 、 EXPIREAT 和 PEXPIREAT 命令的底层实现函数。 * 命令的第二个参数多是绝对值,也多是相对值。 * 当执行 *AT 命令时, basetime 为 0 ,在其余状况下,它保存的就是当前的绝对时间。 * * unit is either UNIT_SECONDS or UNIT_MILLISECONDS, and is only used for * the argv[2] parameter. The basetime is always specified in milliseconds. * * unit 用于指定 argv[2] (传入过时时间)的格式, * 它能够是 UNIT_SECONDS 或 UNIT_MILLISECONDS , * basetime 参数则老是毫秒格式的。 */ void expireGenericCommand(redisClient *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. */ // 取出param中的整数值或者尝试将param中的数据尽量转换成整数值存在when中,成功返回REDIS_OK失败则返回REDIS_ERR if (getLongLongFromObjectOrReply(c, param, &when, NULL) != REDIS_OK) return; // 若是传入的过时时间是以秒为单位的,那么将它转换为毫秒 if (unit == UNIT_SECONDS) when *= 1000; when += basetime; /* No key, return zero. */ // 查询一下该键是否存在 if (lookupKeyRead(c->db,key) == NULL) { addReply(c,shared.czero); return; } /* EXPIRE with negative TTL, or EXPIREAT with a timestamp into the past * should never be executed as a DEL when load the AOF or in the context * of a slave instance. * * 在载入AOF数据时,或者服务器为附属节点时, * 即便 EXPIRE 的 TTL 为负数,或者 EXPIREAT 提供的时间戳已通过期, * 服务器也不会主动删除这个键,而是等待主节点发来显式的 DEL 命令。 * * Instead we take the other branch of the IF statement setting an expire * (possibly in the past) and wait for an explicit DEL from the master. * * 程序会继续将(一个可能已通过期的 TTL)设置为键的过时时间, * 而且等待主节点发来 DEL 命令。 */ if (when <= mstime() && !server.loading && !server.masterhost) { // when 提供的时间已通过期,服务器为主节点(注意主服务器的masterhost==NULL),而且没在载入数据 robj *aux; //删除该键 redisAssertWithInfo(c,key,dbDelete(c->db,key)); server.dirty++; /* Replicate/AOF this as an explicit DEL. */ // 传播 DEL 命令到AOF或者从服务器 aux = createStringObject("DEL",3); //修改客户端的参数数组 rewriteClientCommandVector(c,2,aux,key); decrRefCount(aux); //信号:键值已经改变了。调用touchWatchedKey(db,key) signalModifiedKey(c->db,key); //发送键空间通知和键事件通知 notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",key,c->db->id); addReply(c, shared.cone); return; } else { // 设置键的过时时间 // 若是服务器为附属节点,或者服务器正在载入, // 那么这个 when 有可能已通过期的 setExpire(c->db,key,when); addReply(c,shared.cone); signalModifiedKey(c->db,key); notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"expire",key,c->db->id); server.dirty++; return; } }
下面我将对本身在看代码时里面不太熟悉的几个函数进行说明:
getLongLongFromObjectOrReply(redisClient *c,robj *o,long long *target,const char *msg)
函数目的:尝试从对象o中取出整数值,或者尝试将对象o中的值换成整数值,并将获得的值保存在target中。同时若是转换取出/转成功的话,返回REDIS_OK,不然返回REDIS_ERR,并向客户端发送一条出错回复。
大体实现过程: getLongLongFromObjectOrReply——>getLongLongFromObject——>stroll()最后主要看看stoll函数的实现过程就OK了。
lookupKeyRead(redisDb *db,robj *key)
函数目的:为执行读取操做而取出键key在数据库中的值。并根据是否成功找到值,更新服务器中的命中和不命中信息。找搞则返回值,没找到则返回NULL
函数实现
rewriteClientCommandVector(redisClient *c,int argc,...)
函数目的:修改客户端的参数数组。这其中涉及到C语言可变参数的运用,能够自行学习。
函数实现:若是理解了C语言的可变参数的大体运用的话,函数的实现过程已经大体理解了。其中须要注意的是lookupCommandOrOriginal()函数,lookupCommandOrOriginal()目的是在命令被改名以后,将改名后正确的redisCommand进行返回。
signalModifiedKey(c->db,key)键值已经改变了的信号。调用touchWatchedKey(db,key)(“触碰”一个键,若是这个键在某个客户端watch下,那这个客户端执行的EXEC时事务将失败)
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",key,c->db->id) 发送键空间通知和键事件通知(该部分知识从此会提到)
上面问题补:
redis> PEXPIREAT message 1388556000000(2014年1月1日零时)
(integer) 1
猜想:1388556000000应该是表示1997年1月1日零时——2014年1月1日零时的毫秒数,后面经过查询Java知识小小的写了一个程序验证本身的猜想。
下面是Java程序:
import java.util.Date; import java.text.*; public class demo1 { public static void main(String[] args) throws ParseException { // TODO Auto-generated method stub SimpleDateFormat dateFormat=new SimpleDateFormat("MM-dd-yyyy"); String txtDate="1-01-2014"; Date date=dateFormat.parse(txtDate); //System.out.println(date); System.out.println(date.getTime()); } }
结果:1388505600000
最终结果和本身预期的相差了50400000秒,简单计算一下也就是14小时。好了推测一下,由于数据1388556000000是借用了《Redis设计与实现》书上的实例,而这本书是翻译过来的,那么不难推测1388556000000应该表示的是美国时间2014年1月1日零时。因此猜想是对的。