zoukankan      html  css  js  c++  java
  • Redis数据库

    1 Redis数据库简介

      redis的所有数据库都是保存在redisServer结构体的db数组中,db数组中的每个元素都是redisDb结构体代表一个数据库。db.c主要是封装了数据库的底层操作实现,操作dictexpires两个字典。所有数据库的键值队都是保存在dict字典(即内存中),而expires用来保存设置过期时间的键。redis用rdb和aof来进行数据的持久化,仅仅是内存数据备份,当内存不足时,通过内存策略删除部分键值对,不会持久化要删除的的键值队。

    struct redisServer {  
        ……  
        redisDb *db;//redis server的所有数据裤  
        int dbnum;//redis server的数据库的个数  
        ……  
    };  
    /* Redis database representation. There are multiple databases identified
     * by integers from 0 (the default database) up to the max configured
     * database. The database number is the 'id' field in the structure. */
    typedef struct redisDb {
    
        // 数据库键空间,保存着数据库中的所有键值对
        dict *dict;                 /* The keyspace for this DB */
    
        // 键的过期时间,字典的键为键,字典的值为过期事件 UNIX 时间戳
        dict *expires;              /* Timeout of keys with a timeout set */
    
        // 正处于阻塞状态的键
        dict *blocking_keys;        /* Keys with clients waiting for data (BLPOP) */
    
        // 可以解除阻塞的键
        dict *ready_keys;           /* Blocked keys that received a PUSH */
    
        // 正在被 WATCH 命令监视的键,即事务中被监视的key
        dict *watched_keys;         /* WATCHED keys for MULTI/EXEC CAS */
    
        struct evictionPoolEntry *eviction_pool;    /* Eviction pool of keys */
    
        // 数据库号码
        int id;                     /* Database ID */
    
        // 数据库的键的平均 TTL ,统计信息
        long long avg_ttl;          /* Average TTL, just for stats */
    
    } redisDb;

      下图是一个RedisDb的示例,该数据库存放有五个键值对,分别是sRedis,INums,hBooks,SortNum和sNums,它们各自都有自己的值对象,另外,其中有三个键设置了过期时间,当前数据库是服务器的第0号数据库。

    2 Redis数据库的实现

    2.1 Redis数据库的切换

    每一个数据库的结构体都有一个id用来标识该数据库的编号,Redis的配置文件redis.conf中提供了如下参数来控制Redis在初始化的时候需要创建多少个数据库。

    databases 16  // 表示该服务器需要创建16个数据库

    Redis提供了SELECT命令,来选择当前使用的数据库。其操作如下:

    127.0.0.1:6379> select 1  // 初始为0号数据库,此时选择编码为1的数据库
    OK
    127.0.0.1:6379[1]> select 2 // [1]代表当前数据库编号,此时选择数据库2
    OK
    127.0.0.1:6379[2]> // [2]代表当前数据库编号为2

     客户端redisClient结构体的db属性记录了客户端的当前目标数据库,如果客户端的目标数据库为1号数据库,则将指向redisServer中db[1].   

    /* selectDb source code
     * 将客户端的目标数据库切换为 id 所指定的数据库
     */
    int selectDb(redisClient *c, int id) {
    
        // 确保 id 在正确范围内
        if (id < 0 || id >= server.dbnum)
            return REDIS_ERR;
    
        // 切换数据库(更新客户端数据库指针)
        c->db = &server.db[id];
    
        return REDIS_OK;
    }

    2.2 Redis 键空间的操作(redisDb.dict)

    Redis数据库中存放的数据都是以键值对形式存在,其充分利用了字典结构的高效索引特性,其中:

    • 字典的键:通常是一个字符串对象
    • 字典的值:可是是字符串,哈希,链表,集合和有序集合

    Redis为数据库的键空间操作提供了下列操作函数:

    /* 从数据库中取出指定键对应的值对象,如不存在则返回NULL */ 
    robj *lookupKey(redisDb *db, robj *key, int flags);
    /* 先删除过期键,再从数据库中取出指定键对应的值对象,如不存在则返回NULL 
     * 底层调用lookupKey函数
     */
    robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags);
    /* 先删除过期键,以读操作的方式从数据库中取出指定键对应的值对象
     * 如不存在则返回NULL,底层调用lookupKey函数
     */
    robj *lookupKeyRead(redisDb *db, robj *key);
    /* 先删除过期键,以写操作的方式从数据库中取出指定键对应的值对象
     * 如不存在则返回NULL,底层调用lookupKeyReadWithFlags函数
     */
    robj *lookupKeyWrite(redisDb *db, robj *key);
    /* 先删除过期键,以读操作的方式从数据库中取出指定键对应的值对象
     * 如不存在则返回NULL,底层调用lookupKeyRead函数
     * 此操作需要向客户端回复
     */
    robj *lookupKeyReadOrReply(client *c, robj *key, robj *reply);
    /* 先删除过期键,以写操作的方式从数据库中取出指定键对应的值对象
     * 如不存在则返回NULL,底层调用lookupKeyWrite函数
     * 此操作需要向客户端回复
     */
    robj *lookupKeyWriteOrReply(client *c, robj *key, robj *reply) ;
    /* 添加元素到指定数据库 */
    void dbAdd(redisDb *db, robj *key, robj *val);
    /* 重写指定键的值 */
    void dbOverwrite(redisDb *db, robj *key, robj *val);
    /* 设定指定键的值 */
    void setKey(redisDb *db, robj *key, robj *val);
    /* 判断指定键是否存在 */
    int dbExists(redisDb *db, robj *key);
    /* 随机返回数据库中的键 */
    robj *dbRandomKey(redisDb *db);
    /* 删除指定键 */
    int dbDelete(redisDb *db, robj *key);
    /* 清空所有数据库,返回键值对的个数 */
    long long emptyDb(void(callback)(void*));

    2.3 数据库过期键空间的操作(db->expires)

    (1)过期键删除策略

    如果一个键设置了删除时间,那么面临的问题是以怎样的策略去删除该键。我们很容易理解下面三个删除策略:

    • 定时删除:如果一个键设置了过期时间,就为其创建一个定时器,在定时器结束时,立刻对该键执行删除操作
    • 惰性删除:在访问该键时,判断其过期时间是否到了,如果已过期,则执行删除操作
    • 定期删除:每个一段时间,对数据库中的键进行一次遍历,删除其中的一些过期键,至于删除多少过期键以及检查检查多少个数据库,则由算法决定

      其中,定时删除可以及时的删除过期键,但它为每一个设定了过期时间的键都开了一个定时器,使得CPU的负载变高,从而导致服务器的响应时间和吞吐量收到影响。

      惰性删除有效的克服了定时删除对CPU的影响,但是,如果一个键长时间没有被访问,且这个键已经过期很久了,显然,大量的过期键会占用内存,从而导致内存上的消耗过大。

      定期删除可以算是上述两种策略的折中。设定一个定时器,每隔一段时间遍历数据库,删除其中的过期键,有效的缓解了定时删除对CPU的占用以及惰性删除对内存的占用。

      Redis采用了惰性删除和定期删除两种策略来对过期键进行处理,在上面的lookupKeyWrite等函数中就利用到了惰性删除策略,定时删除策略则是在根据服务器的例行处理程序serverCron来执行删除操作,该程序每100ms调用一次。

    (2)惰性删除

    惰性删除由expireIfNeeded函数实现,其源码如下:

    /* 检查key是否已经过期,如果是的话,将它从数据库中删除 
     * 并将删除命令写入AOF文件以及附属节点(主从复制和AOF持久化相关)
     * 返回0代表该键还没有过期,或者没有设置过期时间
     * 返回1代表该键因为过期而被删除
     */
    int expireIfNeeded(redisDb *db, robj *key) {
        // 获取该键的过期时间
        mstime_t when = getExpire(db,key);
        mstime_t now;
        // 该键没有设定过期时间
        if (when < 0) return 0;
        // 服务器正在加载数据的时候,不要处理
        if (server.loading) return 0;
        // lua脚本相关
        now = server.lua_caller ? server.lua_time_start : mstime();
        // 主从复制相关,附属节点不主动删除key
        if (server.masterhost != NULL) return now > when;
        // 该键还没有过期
        if (now <= when) return 0;
        // 删除过期键
        server.stat_expiredkeys++;
        // 将删除命令传播到AOF文件和附属节点
        propagateExpire(db,key);
        // 发送键空间操作时间通知
        notifyKeyspaceEvent(NOTIFY_EXPIRED,
            "expired",key,db->id);
        // 将该键从数据库中删除
        return dbDelete(db,key);
    }
    int removeExpire(redisDb *db, robj *key);//从expires中删除key的信息  
    void setExpire(redisDb *db, robj *key, long long when);//设置key的过期时间  
    long long getExpire(redisDb *db, robj *key);//获取key的过期时间  

    //过期健传播 void propagateExpire(redisDb *db, robj *key, int lazy) { robj *argv[2]; argv[0] = lazy ? shared.unlink : shared.del; argv[1] = key; incrRefCount(argv[0]); incrRefCount(argv[1]); if (server.aof_state != AOF_OFF) //将过期健以删除命令写入aof文件 feedAppendOnlyFile(server.delCommand,db->id,argv,2); //传播过期健给slave replicationFeedSlaves(server.slaves,db->id,argv,2); decrRefCount(argv[0]); decrRefCount(argv[1]); }

    (3)定期删除

      Redis定义了一个例行处理程序serverCron,该程序每隔100ms执行一次,在其执行过程中会调用databasesCron函数,这个函数里面才会调用真正的定期删除函数activeExpireCycle。该函数每次执行时遍历指定个数的数据库,然后从expires字典中随机取出一个带过期时间的键,检查它是否过期,如过期直接删除。每隔100ms处理数据库的个数由CRON_DBS_PER_CALL参数决定,该参数的默认值如下:

    #define CRON_DBS_PER_CALL 16  // 每次处理16个数据库

    删除过期键的操作由activeExpireCycleTryExpire函数执行,其源码如下:

    /* 检查键的过期时间,如过期直接删除*/
    int activeExpireCycleTryExpire(redisDb *db, dictEntry *de, long long now) {
        // 获取过期时间
        long long t = dictGetSignedIntegerVal(de);
        if (now > t) {
            // 执行到此说明过期
            // 创建该键的副本
            sds key = dictGetKey(de);
            robj *keyobj = createStringObject(key,sdslen(key));
            // 将删除命令传播到AOF和附属节点
            propagateExpire(db,keyobj);
            // 在数据库中删除该键
            dbDelete(db,keyobj);
            // 发送事件通知
            notifyKeyspaceEvent(NOTIFY_EXPIRED,
                "expired",keyobj,db->id);
            // 临时键对象的引用计数减1
            decrRefCount(keyobj);
            // 服务器的过期键计数加1
            // 该参数影响每次处理的数据库个数
            server.stat_expiredkeys++;
            return 1;
        } else {
            return 0;
        }
    }

     参考:
    Redis源码剖析--数据库db

    redis db.c数据库底层操作的源码分析

  • 相关阅读:
    HDU 5273 Dylans loves sequence 暴力递推
    HDU 5285 wyh2000 and pupil 判二分图+贪心
    HDU 5281 Senior's Gun 贪心
    HDU 5651 xiaoxin juju needs help 逆元
    HDU 5646 DZY Loves Partition
    HDU 5366 The mook jong
    HDU 5391Z ball in Tina Town 数论
    HDU 5418 Victor and World 允许多次经过的TSP
    HDU 5642 King's Order dp
    抽屉原理
  • 原文地址:https://www.cnblogs.com/harvyxu/p/7545257.html
Copyright © 2011-2022 走看看