• Redis 3.0.4 数据库


      今天来更新下数据库。。。。

      1.Redis中的数据库,Redis服务器将所有的数据库都保存在服务器状态都保存在redisServer的结构中。

        1.db数组的每一项是一个redisDb结构,代表一个数据库;

        2.dbnum是表示数据库数目,有redis.conf里的配置决定。

    typedef struct redisDb {
        dict *dict;                 /* The keyspace for this DB */
        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 */
        dict *watched_keys;         /* WATCHED keys for MULTI/EXEC CAS */
        struct evictionPoolEntry *eviction_pool;    /* Eviction pool of keys */
        int id;                     /* Database ID */
        long long avg_ttl;          /* Average TTL, just for stats */
    } redisDb;

      2.切换数据库

        Redis客户端用select方法进行数据库的切换。

        redis中redisClient结构中的db属性记录客户端当前在那个数据库中,是一个指向redisDb结构的指针。

        如果执行select 2,则redisClient里db的指针指向db[2]数据库,而实现数据库的切换。

      2.数据库的键操作

        Redis是一个键值对数据库服务器,所有的键值对都是保存在字典(dict)这种数据结构中的。数据库redisDb定义在redis.h中。redisDb结构如上。

        2.1 查找键值对  

    //从数据库db中 取出key的值对象 
    robj *lookupKey(redisDb *db, robj *key) {
        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 = LRU_CLOCK();
            return val;
        } else {
            return NULL;
        }
    }
    // 查找key
    dictEntry *dictFind(dict *d, const void *key)
    {
        dictEntry *he;
        unsigned int h, idx, table;
    
        if (d->ht[0].size == 0) return NULL; /* We don't have a table at all */
        if (dictIsRehashing(d)) _dictRehashStep(d);
        h = dictHashKey(d, key);
        for (table = 0; table <= 1; table++) {
            idx = h & d->ht[table].sizemask;
            he = d->ht[table].table[idx];
            while(he) {
                if (dictCompareKeys(d, key, he->key))
                    return he;
                he = he->next;
            }
            if (!dictIsRehashing(d)) return NULL;
        }
        return NULL;
    }
    
    

          查找key,找到则更新使用时间,没找到返回null。

        2.2 删除键

          删除数据库中的一个键,实际上就是在键空间里面删除键所对应的键值对对象。

    int dbDelete(redisDb *db, robj *key) {
        /* Deleting an entry from the expires dict will not free the sds of
         * the key, because it is shared with the main dictionary. */
        if (dictSize(db->expires) > 0) dictDelete(db->expires,key->ptr);
        if (dictDelete(db->dict,key->ptr) == DICT_OK) {
            if (server.cluster_enabled) slotToKeyDel(key);
            return 1;
        } else {
            return 0;
        }
    }
    //找到key  并且根据nofree标志是否free
    static int dictGenericDelete(dict *d, const void *key, int nofree)
    {   
        unsigned int h, idx;
        dictEntry *he, *prevHe;
        int table;
        
        if (d->ht[0].size == 0) return DICT_ERR; /* d->ht[0].table is NULL */
        if (dictIsRehashing(d)) _dictRehashStep(d);
        h = dictHashKey(d, key);
        
        for (table = 0; table <= 1; table++) {
            idx = h & d->ht[table].sizemask;
            he = d->ht[table].table[idx];
            prevHe = NULL;
            while(he) {
                if (dictCompareKeys(d, key, he->key)) {
                    /* Unlink the element from the list */
                    if (prevHe)
                        prevHe->next = he->next;
                    else
                        d->ht[table].table[idx] = he->next;
                    if (!nofree) {
                        dictFreeKey(d, he);
                        dictFreeVal(d, he);
                    }
                    zfree(he);
                    d->ht[table].used--;
                    return DICT_OK;
                }
                prevHe = he;
                he = he->next;
            }
            if (!dictIsRehashing(d)) break;
        }
        return DICT_ERR; /* not found */
    }
    //删除key并释放
    int dictDelete(dict *ht, const void *key) {
        return dictGenericDelete(ht,key,0);
    }
    //删除key 不释放
    int dictDeleteNoFree(dict *ht, const void *key) {
        return dictGenericDelete(ht,key,1);
    }

        2.3 更新键

          对一个数据库键进行更新,实际上就是对键空间里面键所对应的值对象进行更新,根据值对象的类型不同,更新的具体方法也会不同。

    /* Add the key to the DB. It's up to the caller to increment the reference
     * counter of the value if needed.
     *
     * The program is aborted if the key already exists. */
    void dbAdd(redisDb *db, robj *key, robj *val) {
        sds copy = sdsdup(key->ptr);
        int retval = dictAdd(db->dict, copy, val);
    
        redisAssertWithInfo(NULL,key,retval == REDIS_OK);
        if (val->type == REDIS_LIST) signalListAsReady(db, key);
        if (server.cluster_enabled) slotToKeyAdd(key);
     }

        2.4 键的生存时间或过期时间

          通过expire命令或者pexpire命令,客户端可以以秒或者毫秒精度为数据库中的某个键设置生存时间,更过指定的秒或者毫秒之后,服务器就会自动删除生存时间为0的键。

        redis删除key的方式有三种

        • 定时删除:在设置键的过期时间的同时,创建一个定时器,该定时器在键的过期时间来临时,立即执行对键的删除操作。

        • 惰性删除:放任键过期不管,每次从键空间中访问键时,都检查该键是否过期,如果过期的话,则删除该键。

        • 定期删除:每隔一段时间,程序就对数据库进行一次检查,删除其中的过期键。至于要删除多少过期键,以及要检查多少个数据库,则由算法决定。

           1.定时删除策略是对内存最友好的:通过使用定时器,定时删除策略可以保证过期键会尽可能快的被删除,并释放过期键所占用的内存。另一方面,定时删除策略的缺点是,他对cpu时间是最不友好的,在过期键比较多的情况下,删除过期键这一行为可能会占用相当一部分cpu时间,在内存不紧张但是cpu时间金正的情况下,将cpu时间用在删除和当前任务无关的过期键上,无疑会对服务器的响应时间和吞吐量造成影响。

                 2.惰性删除策略对CPU时间来说是最友好的:程序只会在访问键时才对键进行过期检查,这可以保证删除过期键的操作只会在非做不可的情况下进行,并且删除的目标仅限于当前处理的键,这个策略不会在删除其他无关的过期键上花费任何CPU时间。惰性删除策略的缺点是,它对内存是最不友好的:如果数据库中有非常多的过期键,而这些过期键又恰好没有被访问到的话,那么它们也许永远也不会被删除,这就类似于内存泄漏的情况了。

            3.定期删除策略是前两种策略的一种折中:该策略每隔一段时间执行一次删除过期键操作,并通过限制删除操作执行的时长和频率来减少删除操作对CPU时间的影响。另一方面,定期删除策略有效地减少了因为过期键而带来的内存浪费。

             Redis服务器实际使用的是惰性删除和定期删除两种策略,通过配合使用这两种删除策略,服务器可以很好地在合理使用CPU时间和避免浪费内存空间之间取得平衡。

        设置了过期时间的数据库:图片来源

      

        3..数据库相关实现

          1.scan

            scan,sscan,hscan,zsan统一的入口函数:scanGenericCommand;

     

    
    

      //计算第一个参数的下标

      // scan 0 

      // sscan myset 0 mastch *

    //scan一类命令的实现
    void scanGenericCommand(redisClient *c, robj *o, unsigned long cursor) {
        int i, j;
        list *keys = listCreate();
        listNode *node, *nextnode; //创建一个list
        long count = 10;
        sds pat;
        int patlen, use_pattern = 0;
        dict *ht;
    
        /* Object must be NULL (to iterate keys names), or the type of the object
         * must be Set, Sorted Set, or Hash. */
        //输入类型的检查 要么迭代键名 要么当前集合对象  要么迭代哈希对象 要么迭代有序集合对象
        redisAssert(o == NULL || o->type == REDIS_SET || o->type == REDIS_HASH ||
                    o->type == REDIS_ZSET);
    
        /* Set i to the first option argument. The previous one is the cursor. */
        //计算第一个参数的下标
        // scan 0 
        // sscan myset 0 mastch *
        // i指向count的下标
        i = (o == NULL) ? 2 : 3; /* Skip the key argument if needed. */
    
    
        /* Step 1: Parse options. */
        //解析选项
        while (i < c->argc) {
            j = c->argc - i;
            //count值
            if (!strcasecmp(c->argv[i]->ptr, "count") && j >= 2) {
                //获取count选项 是从第二个值开始的
                if (getLongFromObjectOrReply(c, c->argv[i+1], &count, NULL)
                    != REDIS_OK)
                {
                    goto cleanup;
                }
    
                if (count < 1) {
                    addReply(c,shared.syntaxerr);
                    goto cleanup;
                }
    
                i += 2;
            } else if (!strcasecmp(c->argv[i]->ptr, "match") && j >= 2) {
                //match 选项
                pat = c->argv[i+1]->ptr;  //获取到匹配规则
                patlen = sdslen(pat);
    
                /* The pattern always matches if it is exactly "*", so it is
                 * equivalent to disabling it. */
                //如果匹配模式是 * 贪婪匹配 则match不生效
                use_pattern = !(pat[0] == '*' && patlen == 1);
    
                i += 2;
            } else {
                addReply(c,shared.syntaxerr);
                goto cleanup;
            }
        }
    
        /* Step 2: Iterate the collection.
         *
         * Note that if the object is encoded with a ziplist, intset, or any other
         * representation that is not a hash table, we are sure that it is also
         * composed of a small number of elements. So to avoid taking state we
         * just return everything inside the object in a single call, setting the
         * cursor to zero to signal the end of the iteration. */
    
        /* Handle the case of a hash table. */
        //
        //如果对象是ziplist intset或者其他而不是哈希表  那么这些类型只是包含少量的元素
        //一次将所有的元素全部返回给调用者 并设置cursor为0 标识scan迭代完成
        ht = NULL;
        if (o == NULL) {
            ht = c->db->dict;
        } else if (o->type == REDIS_SET && o->encoding == REDIS_ENCODING_HT) {
            ht = o->ptr;
        } else if (o->type == REDIS_HASH && o->encoding == REDIS_ENCODING_HT) {
            ht = o->ptr;
            count *= 2; /* We return key / value for this type. */
        } else if (o->type == REDIS_ZSET && o->encoding == REDIS_ENCODING_SKIPLIST) {
            zset *zs = o->ptr;
            ht = zs->dict;
            count *= 2; /* We return key / value for this type. */
        }
    
        if (ht) {
            void *privdata[2];
            /* We set the max number of iterations to ten times the specified
            * COUNT, so if the hash table is in a pathological state (very
             * sparsely populated) we avoid to block too much time at the cost
             * of returning no or very few elements. */
            //最大迭代次数是 10 * count
            long maxiterations = count*10;
    
            /* We pass two pointers to the callback: the list to which it will
             * add new elements, and the object containing the dictionary so that
             * it is possible to fetch more data in a type-dependent way. */
            //回调的两个指针  一个是将向其中添加新元素的列表
            //另一个是包含dictionary的对象,一遍能够以类型相关的方式获取更过数据
            privdata[0] = keys;
            privdata[1] = o;
            do {
                cursor = dictScan(ht, cursor, scanCallback, privdata);
            } while (cursor &&
                  maxiterations-- &&
                  listLength(keys) < (unsigned long)count);
        } else if (o->type == REDIS_SET) {  //如果是set  则将set里面的数据全部返回
            int pos = 0;
            int64_t ll;
    
            while(intsetGet(o->ptr,pos++,&ll))
                listAddNodeTail(keys,createStringObjectFromLongLong(ll));
            cursor = 0;
        } else if (o->type == REDIS_HASH || o->type == REDIS_ZSET) {  // ziplist 或者hash 字符串标识的数据结构 
            unsigned char *p = ziplistIndex(o->ptr,0);
            unsigned char *vstr;
            unsigned int vlen;
            long long vll;
    
            while(p) {
                ziplistGet(p,&vstr,&vlen,&vll);
                listAddNodeTail(keys,
                    (vstr != NULL) ? createStringObject((char*)vstr,vlen) :
                                     createStringObjectFromLongLong(vll));
                p = ziplistNext(o->ptr,p);
            }
            cursor = 0;
        } else {
            redisPanic("Not handled encoding in SCAN.");
        }
    
        /* Step 3: Filter elements. */
        //过滤元素 遍历第二步构造的list
        node = listFirst(keys);  //链表首节点地址
        while (node) {
            robj *kobj = listNodeValue(node);  // key 
            nextnode = listNextNode(node); // next node 
            int filter = 0;
    
            /* Filter element if it does not match the pattern. */
             //不匹配 则过滤掉
            if (!filter && use_pattern) {
                if (sdsEncodedObject(kobj)) {
                    if (!stringmatchlen(pat, patlen, kobj->ptr, sdslen(kobj->ptr), 0))
                        filter = 1;
                } else {
                    char buf[REDIS_LONGSTR_SIZE];
                    int len;
    
                    redisAssert(kobj->encoding == REDIS_ENCODING_INT);
                    len = ll2string(buf,sizeof(buf),(long)kobj->ptr);
                    if (!stringmatchlen(pat, patlen, buf, len, 0)) filter = 1;
                }
            }
    
            /* Filter element if it is an expired key. */
            //删除掉过期key
            if (!filter && o == NULL && expireIfNeeded(c->db, kobj)) filter = 1;
    
            /* Remove the element and its associted value if needed. */
            if (filter) {
                decrRefCount(kobj);
                listDelNode(keys, node);
            }
    
            /* If this is a hash or a sorted set, we have a flat list of
             * key-value elements, so if this element was filtered, remove the
             * value, or skip it if it was not filtered: we only match keys. */
            if (o && (o->type == REDIS_ZSET || o->type == REDIS_HASH)) {
                node = nextnode;
                nextnode = listNextNode(node);
                if (filter) {
                    kobj = listNodeValue(node);
                    decrRefCount(kobj);
                    listDelNode(keys, node);
                }
            }
            node = nextnode;
        }
    
        /* Step 4: Reply to the client. */
        addReplyMultiBulkLen(c, 2);
        addReplyBulkLongLong(c,cursor);
    
        addReplyMultiBulkLen(c, listLength(keys));
        while ((node = listFirst(keys)) != NULL) {
            robj *kobj = listNodeValue(node);
            addReplyBulk(c, kobj);
            decrRefCount(kobj);
            listDelNode(keys, node);
        }
    
    cleanup:
            listSetFreeMethod(keys,decrRefCountVoid);
            listRelease(keys);
    }

          代码略长,首先是解析命令,解析count和match参数,如果没有指定count,则默认返回10条数据,返回的索引是下次查询的下标。

          开始迭代集合,如果是key保存为ziplist或者intset,则一次性返回所有数据,没有游标(游标值直接返回0).由于redis设计只有数据量比较小的时候才会保存为ziplist或者intset,,然后根据根据match参数过滤返回值,并且如果这个键已经过期也会直接过滤掉(redis中键过期之后并不会立即删除),最后返回结果给客户端。

          dictScan代码如下:      

    unsigned long dictScan(dict *d,
                           unsigned long v,
                           dictScanFunction *fn,
                           void *privdata)
    {
        dictht *t0, *t1;
        const dictEntry *de;
        unsigned long m0, m1;
    
        if (dictSize(d) == 0) return 0;
        //是否在进行rehash 
        if (!dictIsRehashing(d)) { //没有进行rehash
            t0 = &(d->ht[0]);
            m0 = t0->sizemask;
    
            /* Emit entries at cursor */  //遍历同一个bucket上后面的链表
            de = t0->table[v & m0];
            while (de) {
                fn(privdata, de);
                de = de->next;
            }
    
        } else { //在进行rehash
            t0 = &d->ht[0];
            t1 = &d->ht[1];
    
            /* Make sure t0 is the smaller and t1 is the bigger table */
            if (t0->size > t1->size) {
                t0 = &d->ht[1];
                t1 = &d->ht[0];
            }
    
            m0 = t0->sizemask;
            m1 = t1->sizemask;
    
            /* Emit entries at cursor */
            de = t0->table[v & m0];
            while (de) {  //遍历同一个bucket上面链接的链表
                fn(privdata, de);
                de = de->next;
            }
    
            /* Iterate over indices in larger table that are the expansion
             * of the index pointed to by the cursor in the smaller table */
            do {
                /* Emit entries at cursor */
                de = t1->table[v & m1];
                while (de) {
                    fn(privdata, de);
                    de = de->next;
                }
                /* Increment bits not covered by the smaller mask */
                v = (((v | m0) + 1) & ~m0) | (v & m0);   //下一个游标的取值
    
                /* Continue while bits covered by mask difference is non-zero */
            } while (v & (m0 ^ m1));   //判断条件  
        }
    
        /* Set unmasked bits so incrementing the reversed cursor
         * operates on the masked bits of the smaller table */
        v |= ~m0;
    
        /* Increment the reverse cursor */
        v = rev(v);
        v++;
        v = rev(v);
    
        return v;
    }

     推荐博客:Redis scan命令原理将scan讲的很详细

  • 相关阅读:
    程序开发
    主方法
    日志
    node.js
    二维互换
    前台打断点
    具体的后台断点快捷键
    Jenkins
    断点
    循环
  • 原文地址:https://www.cnblogs.com/chenyang920/p/13227847.html
Copyright © 2020-2023  润新知