这里主要讲的Redis是怎么样设置过期键的,可以算作后续"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):

typedef struct redisDb {
    // ...
    // 
数据库键空间,保存着数据库中的所有键值对
    dict *dict;
    // ...
} redisDb;

  • 键空间的键也就是数据库的键,每个键都是一个字符串对象;
  • 键空间的值也就是数据库的值,每个值可以是字符串对象、列表对象、哈希表对象、集合对象和有序集合对象中的任意一种Redis对象。

下面是一个例子:

根据这个键空间,执行相关的添加、删除、更新等操作的便可以比较容易理解,我们此处也忽略不讲了。

下面我们再来介绍一下redisDb结构中的另外一个字典expires,这个字典保存了数据库中所有键的过期时间,我们称这个字典为过期字典(注意这里面只保存着键的过期时间,可不是说这个字典里面的键都是过期的):

  • 过期字典的键是一个指针,这个指针指向键空间中的某个键对象(也即是某个数据库键);
  • 过期字典的值是一个long long类型的整数,这个整数保存了键所指向的数据库键的过期时间——一个毫秒精度的UNIX时间戳;

有了上面的知识我们下载便可以来看看四个命令: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日零时。所以猜测是对的。

redis学习笔记——expire、pexpire、expireat、pexpireat的执行过程的更多相关文章

  1. Redis学习笔记4-Redis配置详解

    在Redis中直接启动redis-server服务时, 采用的是默认的配置文件.采用redis-server   xxx.conf 这样的方式可以按照指定的配置文件来运行Redis服务.按照本Redi ...

  2. Redis学习笔记一:数据结构与对象

    1. String(SDS) Redis使用自定义的一种字符串结构SDS来作为字符串的表示. 127.0.0.1:6379> set name liushijie OK 在如上操作中,name( ...

  3. Redis学习笔记(二)-key相关命令【转载】

    转自 Redis学习笔记(二)-key相关命令 - 点解 - 博客园http://www.cnblogs.com/leny/p/5638764.html Redis支持的各种数据类型包括string, ...

  4. Redis学习笔记4-Redis配置具体解释

    在Redis中直接启动redis-server服务时, 採用的是默认的配置文件.採用redis-server   xxx.conf 这种方式能够依照指定的配置文件来执行Redis服务. 依照本Redi ...

  5. redis学习笔记(详细)——高级篇

    redis学习笔记(详细)--初级篇 redis学习笔记(详细)--高级篇 redis配置文件介绍 linux环境下配置大于编程 redis 的配置文件位于 Redis 安装目录下,文件名为 redi ...

  6. redis 学习笔记(6)-cluster集群搭建

    上次写redis的学习笔记还是2014年,一转眼已经快2年过去了,在段时间里,redis最大的变化之一就是cluster功能的正式发布,以前要搞redis集群,得借助一致性hash来自己搞shardi ...

  7. Redis学习笔记~目录

    回到占占推荐博客索引 百度百科 redis是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表).set(集合). ...

  8. Redis学习笔记7--Redis管道(pipeline)

    redis是一个cs模式的tcp server,使用和http类似的请求响应协议.一个client可以通过一个socket连接发起多个请求命令.每个请求命令发出后client通常会阻塞并等待redis ...

  9. Redis学习笔记之ABC

    Redis学习笔记之ABC Redis命令速查 官方帮助文档 中文版本1 中文版本2(反应速度比较慢) 基本操作 字符串操作 set key value get key 哈希 HMSET user:1 ...

随机推荐

  1. 590. N叉树的后序遍历

    给定一个 N 叉树,返回其节点值的后序遍历. 例如,给定一个 3叉树 : 返回其后序遍历: [5,6,3,2,4,1]. 说明: 递归法很简单,你可以使用迭代法完成此题吗? /* // Definit ...

  2. 洛谷——P2009 跑步

    P2009 跑步 题目背景 跑步是一项有意思的运动,尤其是可以开发人的脑筋.常神牛很喜欢跑步. 题目描述 常神牛跑步的场地是一个多边形(边数≤20,每个顶点用英文大写字母表示),并且在这个多边形内部, ...

  3. Flask实战第55天:cms轮播图上传到七牛功能完成

    登录七牛云,进入“对象存储”, 新建存储空间(Bucket), 我创建的空间命名为flask-bbs 创建完Bucket,七牛会给我们提供一个测试域名,生产环境中,我们需要绑定自己的域名 在个人面板中 ...

  4. 【set】【Splay】【pb_ds】bzoj1208 [HNOI2004]宠物收养所

    每次来的如果是人,且宠物数不为零,就从宠物中选出一个与其差距最小的,累加答案:若为零,就把他放入另一个集合里. 如果是宠物,则同上. 各种平衡树都可过,我蛋疼地用了pb_ds. Code: #incl ...

  5. 【2-SAT(最小字典序/暴力染色)】HDU1814-Peaceful Commission

    [题目大意] 和平委员会每个党派有2个人,只能派出其中1个,其中有一些人之间互相讨厌不能同时派出.求出派遣方案,如果有多种方案输出字典序最小的方案. [思路] 最小字典序只能用暴力染色.初始时均没有染 ...

  6. Problem F: 加密程序2

    #include<stdio.h> int main() { int i; ]; while(gets(a)!=NULL) { ;a[i]!='\0';i++) if('A'<=a[ ...

  7. InetAddress之域名解析

    1. getByName public static InetAddress getByName(String host) throws UnknownHostException { return I ...

  8. linux命令查看开放哪些端口

    netstat -nupl (UDP类型的端口)netstat -ntpl (TCP类型的端口) a 表示所有 n表示不查询dns t表示tcp协议 u表示udp协议 p表示查询占用的程序 l表示查询 ...

  9. SONY新的圈铁耳机

    最近IFA大会上推出的新旗舰XBA-Z5,镁合金外壳.镀银OFC线,做工比当前的圈铁旗舰XBA-H3上明显好了不止一个档次,让人心动不已.不知道有没有改善XBA-H3的佩戴舒适度的问题.本次并没有发布 ...

  10. http://www.pass.org

    http://www.pass.org/Learning/Recordings/Listing.aspx?category=conferences