Redis源码分析(十八)--- db.c内存数据库操作

我们知道Redis数据库作为一个内存数据库,与memcached比较类似,基本的操作都是存储在内存缓冲区中,等到缓冲区中数据满后,在持久化到磁盘中。今天,我主要研究了对于redis中对于内存数据库的操作。与普通的数据操作比较,并没有什么特别多的其他的一些操作。下面是我分类出的一些API:

/*-----------------------------------------------------------------------------
 * C-level DB API
 *----------------------------------------------------------------------------*/
robj *lookupKey(redisDb *db, robj *key) /* 从db中获取key代表的值 */
robj *lookupKeyRead(redisDb *db, robj *key) /* 寻找某个key的值,与lookupKey方法的区别是多了过期检查 */
robj *lookupKeyWrite(redisDb *db, robj *key) /* 与lookupKeyRead一样,只是少了命中刷的统计 */
robj *lookupKeyReadOrReply(redisClient *c, robj *key, robj *reply) /* 有回复的读擦操作 */
robj *lookupKeyWriteOrReply(redisClient *c, robj *key, robj *reply) /* 有回复的写操作 */
void dbAdd(redisDb *db, robj *key, robj *val) /* 往内存数据库中添加值,如果key已经存在,则操作无效 */
void dbOverwrite(redisDb *db, robj *key, robj *val) /* db  key value覆盖操作,如果不存在此key,操作失效 */
void setKey(redisDb *db, robj *key, robj *val) /* 高级设置操作,如果不存在的直接添加,存在的就覆盖 */
int dbExists(redisDb *db, robj *key) /* db是否存在此key */
robj *dbRandomKey(redisDb *db) /* 随机返回没有过期的key */
int dbDelete(redisDb *db, robj *key) /* db删除操作 */
robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o) /* 解除key的共享,之后就可以进行修改操作 */
long long emptyDb(void(callback)(void*)) /* 将server中的所有数据库清空,回调函数作为参数传入 */
int selectDb(redisClient *c, int id) /* 客户端选择服务端的某个db */
void signalModifiedKey(redisDb *db, robj *key) /* 每当key被修改时,就会调用此方法,touchWatchedKey(db,key)方法,就把此key对应的客户端锁住了 */
void signalFlushedDb(int dbid) /* 把dbid中的key都touch一遍 */
void flushdbCommand(redisClient *c) /* 刷新client所在的db命令 */
void flushallCommand(redisClient *c) /* 刷新所有的server中的数据库 */
void delCommand(redisClient *c) /* 根据Client的命令参数删除数据库 */
void existsCommand(redisClient *c) /* 某个key是否存在命令 */
void selectCommand(redisClient *c) /* Client客户端选择数据库命令 */
void randomkeyCommand(redisClient *c) /* 获取随机key指令 */
void keysCommand(redisClient *c) /* 向客户端回复key obj命令 */
void scanCallback(void *privdata, const dictEntry *de) /* type scan扫描出key,val */
int parseScanCursorOrReply(redisClient *c, robj *o, unsigned long *cursor) /* 判断scan Cursor是否有效 */
void scanGenericCommand(redisClient *c, robj *o, unsigned long cursor) /* 3: Filter elements.(过滤元素)4: Reply to the client.(回复客户端) */
void scanCommand(redisClient *c) /* 扫描命令 */
void dbsizeCommand(redisClient *c) /* 客户端所用的db的字典总数 */
void lastsaveCommand(redisClient *c) /* 服务端最后一次保存的操作 */
void typeCommand(redisClient *c) /* 客户端查询的key的type类型 */
void shutdownCommand(redisClient *c) /* shutdown终止命令,服务端要做最后的保存操作 */
void renameGenericCommand(redisClient *c, int nx) /*为key重命名操作 */
void renameCommand(redisClient *c) /* 重命名可能会覆盖原值命令 */
void renamenxCommand(redisClient *c) /* 重命名时不覆盖原来的值 */
void moveCommand(redisClient *c) /* 将源db中的key移到目标db上 */
int removeExpire(redisDb *db, robj *key) /* 移除过期的key */
void setExpire(redisDb *db, robj *key, long long when) /* 设置过期的key,操作为将主要的dict中key移入expire的dict中,并对此key设置时间 */
long long getExpire(redisDb *db, robj *key) /*  获取key的过期时间*/
void propagateExpire(redisDb *db, robj *key)
int expireIfNeeded(redisDb *db, robj *key) /* 判断此key是否过期,2个条件,1是否存在expire的key里没有就不过期
2.在expire里面了,判断when时间有没有超过当前时间,没有超过也不算过期 */
void expireGenericCommand(redisClient *c, long long basetime, int unit)
void expireCommand(redisClient *c)
void expireatCommand(redisClient *c)
void pexpireCommand(redisClient *c)
void pexpireatCommand(redisClient *c)
void ttlGenericCommand(redisClient *c, int output_ms) /* 返回key的ttl生存时间 ,下面的一些方法是时间单位的不同,默认为秒*/
void ttlCommand(redisClient *c)
void pttlCommand(redisClient *c)
void persistCommand(redisClient *c) /* key是否存在的命令 */
int *getKeysUsingCommandTable(struct redisCommand *cmd,robj **argv, int argc, int *numkeys)
int *getKeysFromCommand(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags)
void getKeysFreeResult(int *result)
int *noPreloadGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags)
int *renameGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags)
int *zunionInterGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags)

在API的后半部分API都是一些函数封装的一些命令操作。开放给系统调用。在上面的API中,比较典型的就是,read,write等API,

/* 从db中获取key代表的值 */
robj *lookupKey(redisDb *db, robj *key) {
	//从db的dict字典中查找
    dictEntry *de = dictFind(db->dict,key->ptr);
    if (de) {
        robj *val = dictGetVal(de);

        /* Update the access time for the ageing algorithm.
         * Don't do it if we have a saving child, as this will trigger
         * a copy on write madness. */
        if (server.rdb_child_pid == -1 && server.aof_child_pid == -1)
            val->lru = server.lruclock;
        return val;
    } else {
        return NULL;
    }
}

但是真正调用的时候,不会直接调用此方法,会加一些限制,会过滤掉过期的key,还有缓冲区命中数的统计:

/* 寻找某个key的值,与lookupKey方法的区别是多了过期检查 */
robj *lookupKeyRead(redisDb *db, robj *key) {
    robj *val;

    expireIfNeeded(db,key);
    val = lookupKey(db,key);
    if (val == NULL)
    	//命中数减一
        server.stat_keyspace_misses++;
    else
    	//命中数递增1
        server.stat_keyspace_hits++;
    return val;
}

可以有效调整缓冲区。下面给出一个修改内存数据库的操作:

/* High level Set operation. This function can be used in order to set
 * a key, whatever it was existing or not, to a new object.
 *
 * 1) The ref count of the value object is incremented.
 * 2) clients WATCHing for the destination key notified.
 * 3) The expire time of the key is reset (the key is made persistent). */
/* 高级设置操作,如果不存在的直接添加,存在的就覆盖 */
void setKey(redisDb *db, robj *key, robj *val) {
    if (lookupKeyWrite(db,key) == NULL) {
        dbAdd(db,key,val);
    } else {
        dbOverwrite(db,key,val);
    }
    //对此key增加引用计数
    incrRefCount(val);
    removeExpire(db,key);
    signalModifiedKey(db,key);
}

我们看到其实在每次更改数据库操作的时候,都会出现signalModifiedKey(db,key)这个方法,大致意思就是提示要改变key所对应的值了,里面执行的操作到底是什么呢,这个方法的实现就在db.c中:

/*-----------------------------------------------------------------------------
 * Hooks for key space changes.
 *
 * Every time a key in the database is modified the function
 * signalModifiedKey() is called.
 *
 * Every time a DB is flushed the function signalFlushDb() is called.
 *----------------------------------------------------------------------------*/
/* 每当key被修改时,就会调用此方法,touchWatchedKey(db,key)方法,就把此key对应的客户端锁住了 */
void signalModifiedKey(redisDb *db, robj *key) {
    touchWatchedKey(db,key);
}

调用的就是touch -key方法了,就是把监听此key的Client列表进行设置,只能让一个客户端操作执行成功,客户端的其他操作无效,达到同步。当内存数据渐渐满的时候,会定期的刷新到磁盘中:

/* 刷新所有的server中的数据库 */
void flushallCommand(redisClient *c) {
    signalFlushedDb(-1);
    server.dirty += emptyDb(NULL);
    addReply(c,shared.ok);
    if (server.rdb_child_pid != -1) {
        kill(server.rdb_child_pid,SIGUSR1);
        rdbRemoveTempFile(server.rdb_child_pid);
    }
    if (server.saveparamslen > 0) {
        /* Normally rdbSave() will reset dirty, but we don't want this here
         * as otherwise FLUSHALL will not be replicated nor put into the AOF. */
        int saved_dirty = server.dirty;
        //在这里重新保存rdb了
        rdbSave(server.rdb_filename);
        server.dirty = saved_dirty;
    }
    server.dirty++;
}

rdbSave操作时重点。在db.c还提到了一个概念,expire过期的概念,也就是说,存在key过期的概念,在内存数据库中,频繁的操作比如会引起许多过期的键值对的存在,所以在db中,维护了一个db->expires的东西,所有过期的可以都存在于db->expires里面,定期会进行移除操作,所以在最早的那个函数中,往内存数据库取值的时候,要判断是否过期

/* 判断此key是否过期,2个条件,1是否存在expire的key里没有就不过期
	2.在expire里面了,判断when时间有没有超过当前时间,没有超过也不算过期 */
int expireIfNeeded(redisDb *db, robj *key) {
    mstime_t when = getExpire(db,key);
    mstime_t now;

    if (when < 0) return 0; /* No expire for this key */

    /* Don't expire anything while loading. It will be done later. */
    if (server.loading) return 0;

    /* If we are in the context of a Lua script, we claim that time is
     * blocked to when the Lua script started. This way a key can expire
     * only the first time it is accessed and not in the middle of the
     * script execution, making propagation to slaves / AOF consistent.
     * See issue #1525 on Github for more information. */
    now = server.lua_caller ? server.lua_time_start : mstime();

    /* If we are running in the context of a slave, return ASAP:
     * the slave key expiration is controlled by the master that will
     * send us synthesized DEL operations for expired keys.
     *
     * Still we try to return the right information to the caller,
     * that is, 0 if we think the key should be still valid, 1 if
     * we think the key is expired at this time. */
    if (server.masterhost != NULL) return now > when;

    /* Return when this key has not expired */
    if (now <= when) return 0;

    /* Delete the key */
    server.stat_expiredkeys++;
    propagateExpire(db,key);
    notifyKeyspaceEvent(REDIS_NOTIFY_EXPIRED,
        "expired",key,db->id);
    return dbDelete(db,key);
}

每个expire的key有个ttl的概念,就是"Time To Live"生存时间:

/* 返回key的ttl生存时间 */
void ttlGenericCommand(redisClient *c, int output_ms) {
    long long expire, ttl = -1;

    /* If the key does not exist at all, return -2 */
    if (lookupKeyRead(c->db,c->argv[1]) == NULL) {
        addReplyLongLong(c,-2);
        return;
    }
    /* The key exists. Return -1 if it has no expire, or the actual
     * TTL value otherwise. */
    expire = getExpire(c->db,c->argv[1]);
    if (expire != -1) {
    	//如果已被移入过期的key,计算过期时间里当前时间还差多远,ttl就是当前的生存时间单位为ms
        ttl = expire-mstime();
        if (ttl < 0) ttl = 0;
    }
    if (ttl == -1) {
        addReplyLongLong(c,-1);
    } else {
        addReplyLongLong(c,output_ms ? ttl : ((ttl+500)/1000));
    }
}

用来判断是否过期的时候用。

时间: 2024-10-20 19:19:31

Redis源码分析(十八)--- db.c内存数据库操作的相关文章

jQuery 源码分析(十八) ready事件详解

ready事件是当DOM文档树加载完成后执行一个函数(不包含图片,css等),因此它的触发要早于load事件.用法: $(document).ready(fun) ;fun是一个函数,这样当DOM树加载完毕后就会执行该匿名函数了 ready有一个简写,可以直接传入$(fun)即可,这是因为在jQuey内部也定义了一个$(document)的jQuery对象,和我们在上面的写法是一样的 ready事件和window的onload区别: ready事件 ;等dom树载完毕后就可以执行 onload事

Redis源码分析(八)--- t_hash哈希转换

在上次的zipmap分析完之后,其实关于redis源代码结构体部分的内容其实已经全部结束了,因为下面还有几个和结构体相关的操作类,就页把他们归并到struct包下了.这类的文件有:t_hash.c,z_list,z_set.c,t_string.c,t_zset.c,这些文件的功能其实都差不多,就是用来实现Client和Server之间的命令处理的操作类,通过robj的形式,把dict,ziplist等存入robj中,进行各个转换,实现命令操作.避开了结构体原先的复杂结构,相当于是封装了结构体的

Redis源码分析(十七)--- multi事务操作

redis作为一非关系型数据库,竟然同样拥有与RDBMS的事务操作,不免让我觉得比较惊讶.在redis就专门有文件就是执行事务的相关操作的.也可以让我们领略一下,在Redis的代码中是如何实现事务操作.首先亮出mulic.c下面的一些API. /* ================================ MULTI/EXEC ============================== */ void initClientMultiState(redisClient *c) /* 初始

redis源码分析之内存布局

redis源码分析之内存布局 1. 介绍 众所周知,redis是一个开源.短小.高效的key-value存储系统,相对于memcached,redis能够支持更加丰富的数据结构,包括: 字符串(string) 哈希表(map) 列表(list) 集合(set) 有序集(zset) 主流的key-value存储系统,都是在系统内部维护一个hash表,因为对hash表的操作时间复杂度为O(1).如果数据增加以后,导致冲突严重,时间复杂度增加,则可以对hash表进行rehash,以此来保证操作的常量时

Redis源码剖析(八)--对象系统

对象的类型与编码 在 Redis 中新创建一个键值对时, 我们至少会创建两个对象, 一个对象用作键值对的键(键对象), 另一个对象用作键值对的值(值对象).Redis 中的每个对象都由一个 redisObject 结构表示: typedef struct redisObject { // 类型 unsigned type:4; // 编码 unsigned encoding:4; // 对象最后一次被访问的时间 unsigned lru:REDIS_LRU_BITS; /* lru time (

redis源码分析4---结构体---跳跃表

redis源码分析4---结构体---跳跃表 跳跃表是一种有序的数据结构,他通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的: 跳跃表支持平均O(logN),最坏O(N)复杂度的节点查找,还可以通过顺序性操作来批量处理节点.性能上和平衡树媲美,因为事先简单,常用来代替平衡树. 在redis中,只在两个地方使用了跳跃表,一个是实现有序集合键,另一个是在集群节点中用作内部数据结构. 1 跳跃表节点 1.1 层 层的数量越多,访问其他节点的速度越快: 1.2 前进指针 遍历举例

redis源码分析3---结构体---字典

redis源码分析3---结构体---字典 字典,简单来说就是一种用于保存键值对的抽象数据结构: 注意,字典中每个键都是独一无二的:在redis中,内部的redis的数据库就是使用字典作为底层实现的: 1 字典的实现 在redis中,字典是使用哈希表作为底层实现的,一个hash表里面可以有多个hash表节点,而每个hash表节点就保存了字典中的一个键值对: hash表定义 table属性是一个数组,数组中的每个元素都是一个指向dictEntry结构的指针,每个dictEntry结构保存着一个键值

redis 源码分析(一) 内存管理

一,redis内存管理介绍 redis是一个基于内存的key-value的数据库,其内存管理是非常重要的,为了屏蔽不同平台之间的差异,以及统计内存占用量等,redis对内存分配函数进行了一层封装,程序中统一使用zmalloc,zfree一系列函数,其对应的源码在src/zmalloc.h和src/zmalloc.c两个文件中,源码点这里. 二,redis内存管理源码分析 redis封装是为了屏蔽底层平台的差异,同时方便自己实现相关的函数,我们可以通过src/zmalloc.h 文件中的相关宏定义

Giraph源码分析(八)—— 统计每个SuperStep中参与计算的顶点数目

[题目] 原文: 1.3 Design an algorithm and write code to remove the duplicate characters in a string without using any additional buffer. NOTE: One or two additional variables are fine. An extra copy of the array is not. FOLLOW UP Write the test cases for

redis源码分析之事务Transaction(下)

接着上一篇,这篇文章分析一下redis事务操作中multi,exec,discard三个核心命令. 原文地址:http://www.jianshu.com/p/e22615586595 看本篇文章前需要先对上面文章有所了解: redis源码分析之事务Transaction(上) 一.redis事务核心命令简介 redis事务操作核心命令: //用于开启事务 {"multi",multiCommand,1,"sF",0,NULL,0,0,0,0,0}, //用来执行事