redis学习笔记——expire、pexpire、expireat、pexpireat的执行过程

摘要:
下面我们再来介绍一下redisDb结构中的另外一个字典expires,这个字典保存了数据库中所有键的过期时间,我们称这个字典为过期字典:过期字典的键是一个指针,这个指针指向键空间中的某个键对象;过期字典的值是一个longlong类型的整数,这个整数保存了键所指向的数据库键的过期时间——一个毫秒精度的UNIX时间戳;有了上面的知识我们下载便可以来看看四个命令:expire、pexpire、expireat、pexpireat的实现过程。如:redis˃PEXPIREATmessage13912344000001其他的命令类似。

这里主要讲的Redis是怎么样设置过期键的,可以算作后续"Redis过期键的删除策略"的前篇或者说预备知识。

在了解过期键问题前我们首先需要对redis的数据库和数据库键空间有一定的了解:

structredisServer{
//...
//一个数组,保存着服务器中的所有数据库
redisDb*db;

//服务器的数据库数量,dbnum属性的值由服务器配置的database选项决定,默认情况下,该选项的值为16
intdbnum;

//...
};

在服务器内部,客户端状态redisClient结构的db属性记录了客户端当前的目标数据库,这个属性是一个指向redisDb结构的指针:

typedefstructredisClient{
//...
//记录客户端当前正在使用的数据库
redisDb*db;
//...
}redisClient;

redis学习笔记——expire、pexpire、expireat、pexpireat的执行过程第1张

现在我们再来看看redisDb 结构,redisDb结构的dict字典保存了数据库中的所有键值对,我们将这个字典称为键空间(key space):

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

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

下面是一个例子:

redis学习笔记——expire、pexpire、expireat、pexpireat的执行过程第2张

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

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

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

redis学习笔记——expire、pexpire、expireat、pexpireat的执行过程第3张

有了上面的知识我们下载便可以来看看四个命令:expire、pexpire、expireat、pexpireat的实现过程。

四个命令的使用是比较简单的:EXPIRE <key> <seconds> 如:EXPIRE book 100

PEXPIRE <key> <millionseconds>

EXPIREAT <key> <timestamp>

PEXPIREAT <key> <timestamp>如:PEXPIREAT book1388556000000(2014年1月1日零时)其实我也不知道这是怎么算出来的!!!

注意:利用PERSIST命令可以移除一个键的过期时间。

如:redis>PEXPIREATmessage1391234400000
(integer)1

redis学习笔记——expire、pexpire、expireat、pexpireat的执行过程第4张

其他的命令类似。不过值得一提的是: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

函数实现

redis学习笔记——expire、pexpire、expireat、pexpireat的执行过程第5张

  • 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>PEXPIREATmessage1388556000000(2014年1月1日零时)
(integer)1

猜测:1388556000000应该是表示1997年1月1日零时——2014年1月1日零时的毫秒数,后面通过查询Java知识小小的写了一个程序验证自己的猜测。

下面是Java程序:

importjava.util.Date;
import java.text.*;
public classdemo1 {
    public static void main(String[] args) throwsParseException {
        //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的执行过程》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇centos7 下安装 mysql5.7Scala 安装 Exception in thread "main" java.lang.VerifyError: Uninitialized object exists on backward branch 96下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

JDBC连接数据库及其执行操作

作者:Alvin 功能:数据库连接与实现增删改查 时间:2019年3月4日08点33分 参考文章:https://www.2cto.com/database/201805/743741.html 一、总结 数据库加载分为以下几个步骤 第一步、加载驱动 MySQL的加载方式 Class.forName("com.mysql.jdbc.Driver"); O...

01-Hive综述(hive的安装、修改配置、基本使用)

 写在前面的话: 该系列博文是我学习《 Hive源码解析与开发实战》视频课程的一个笔记,或者说总结,暂时没有对视频中的操作去做验证,只是纯粹的学习记录。 有兴趣看该视频的博友可以留言,我会共享出来,相互交流学习 ^.^。 ********************************************************************...

常用rides命令

rides使用步骤 1.源代码构建安装 1.下载,Linux下命令wget http://redis.io/download下载redis的包 2.解归档Linux下命令 tar -xvf redis-4.0.11 3.进入解归档后的文件夹cd redis-4.0.11 4.构建安装Linux下命令make && make ins...

Oracle 数据库监听配置

一、监听器(LISTENER)   监听器是Oracle基于服务器端的一种网络服务,主要用于监听客户端向数据库服务器端提出的连接请求。既然是基于服务器端的服务,那么它也只存在于数据库服务器端,进行监听器的设置也是在数据库服务器端完成的。   二、本地服务名(Tnsname)   Oracle客户端与服务器端的连接是通过客户端发出连接请求,由服务器端监听器...

scrapy爬虫成长日记之将抓取内容写入mysql数据库

前面小试了一下scrapy抓取博客园的博客(您可在此查看scrapy爬虫成长日记之创建工程-抽取数据-保存为json格式的数据),但是前面抓取的数据时保存为json格式的文本文件中的。这很显然不满足我们日常的实际应用,接下来看下如何将抓取的内容保存在常见的mysql数据库中吧。 说明:所有的操作都是在“scrapy爬虫成长日记之创建工程-抽取数据-保存为j...

oracle最高账号sys的密码认证模式

CONNECT USERNAME/PASSWORD@SERVERNAME AS SYSDBAconnect 是指连接到username是指用户名password是指密码servername是指服务名as sysdba是指已数据库管理员的身份登录不知道能不能帮到你。 oracle提供的认证模式 1. 操作系统验证(匿名登录 不检验用户名和密码)  conn...