SSDB --- zset实现

来源:互联网 发布:徐老师淘宝店地址链接 编辑:程序博客网 时间:2024/06/11 16:20

zset

zset是一种很有用的数据结构,你可能听说过set,但什么是zset呢?zset就是sorted_set(排序集),STL里有set,底层是用红黑树实现的,并没有提供sorted_set的实现,要实现一个zset并不难,比如SSDB的作者就用map和set实现了一个简单的zset。代码在ssdb/src/util/sorted_set.h sorted_set.cpp中。
sorted_set.h

class SortedSet{public:    bool empty() const{        return size() == 0;    }    int size() const;    int add(const std::string &key, int64_t score);    // 0: not found, 1: found and deleted    int del(const std::string &key);    // the first item is copied into key if SortedSet not empty    int front(std::string *key, int64_t *score=NULL) const;    int back(std::string *key, int64_t *score=NULL) const;    int64_t max_score() const;    int pop_front();    int pop_back();};

sorted_set.cpp

#include "sorted_set.h"int SortedSet::size() const{    return (int)sorted_set.size();}int SortedSet::add(const std::string &key, int64_t score){    int ret;    std::map<std::string, std::set<Item>::iterator>::iterator it;    it = existed.find(key);    if(it == existed.end()){        // new item        ret = 1;    }else{        ret = 0;        std::set<Item>::iterator it2 = it->second;        const Item &item = *it2;        if(item.score == score){            // not updated            return 0;        }        // remove existing item        sorted_set.erase(it2);    }    Item item;    item.key = key;    item.score = score;    std::pair<std::set<Item>::iterator, bool> p = sorted_set.insert(item);    existed[key] = p.first;    return ret;}int SortedSet::del(const std::string &key){    int ret;    std::map<std::string, std::set<Item>::iterator>::iterator it;    it = existed.find(key);    if(it == existed.end()){        // new item        ret = 0;    }else{        ret = 1;        sorted_set.erase(it->second);        existed.erase(it);    }    return ret;}int SortedSet::front(std::string *key, int64_t *score) const{    std::set<Item>::iterator it2 = sorted_set.begin();    if(it2 == sorted_set.end()){        return 0;    }    const Item &item = *it2;    *key = item.key;    if(score){        *score = item.score;    }    return 1;}int SortedSet::back(std::string *key, int64_t *score) const{    std::set<Item>::reverse_iterator it2 = sorted_set.rbegin();    if(it2 == sorted_set.rend()){        return 0;    }    const Item &item = *it2;    *key = item.key;    if(score){        *score = item.score;    }    return 1;}int64_t SortedSet::max_score() const{    int64_t score = 0;    std::string key;    this->back(&key, &score);    return score;}int SortedSet::pop_front(){    if(sorted_set.empty()){        return 0;    }    std::set<Item>::iterator it = sorted_set.begin();    const Item &item = *it;    existed.erase(item.key);    sorted_set.erase(it);    return 1;}int SortedSet::pop_back(){    if(sorted_set.empty()){        return 0;    }    std::set<Item>::iterator it = sorted_set.end();    it --;    const Item &item = *it;    existed.erase(item.key);    sorted_set.erase(it);    return 1;}

很简单是不是,sorted_set 根据score排序而不是key,但今天要说的zset实现不是上面的代码,上面的简单实现只是用来处理ttl的。
下面来说说基于leveldb的zset实现。

zset实现分析

zset的实现在 src/ssdb/t_zset.h t_zset.cpp 下。
zset的接口如下:

    /* zset */    virtual int zset(const Bytes &name, const Bytes &key, const Bytes &score, char log_type=BinlogType::SYNC);    virtual int zdel(const Bytes &name, const Bytes &key, char log_type=BinlogType::SYNC);    // -1: error, 1: ok, 0: value is not an integer or out of range    virtual int zincr(const Bytes &name, const Bytes &key, int64_t by, int64_t *new_val, char log_type=BinlogType::SYNC);    //int multi_zset(const Bytes &name, const std::vector<Bytes> &kvs, int offset=0, char log_type=BinlogType::SYNC);    //int multi_zdel(const Bytes &name, const std::vector<Bytes> &keys, int offset=0, char log_type=BinlogType::SYNC);    virtual int64_t zsize(const Bytes &name);    /**     * @return -1: error; 0: not found; 1: found     */    virtual int zget(const Bytes &name, const Bytes &key, std::string *score);    virtual int64_t zrank(const Bytes &name, const Bytes &key);    virtual int64_t zrrank(const Bytes &name, const Bytes &key);    virtual ZIterator* zrange(const Bytes &name, uint64_t offset, uint64_t limit);    virtual ZIterator* zrrange(const Bytes &name, uint64_t offset, uint64_t limit);    /**     * scan by score, but won't return @key if key.score=score_start.     * return (score_start, score_end]     */    virtual ZIterator* zscan(const Bytes &name, const Bytes &key,            const Bytes &score_start, const Bytes &score_end, uint64_t limit);    virtual ZIterator* zrscan(const Bytes &name, const Bytes &key,            const Bytes &score_start, const Bytes &score_end, uint64_t limit);    virtual int zlist(const Bytes &name_s, const Bytes &name_e, uint64_t limit,            std::vector<std::string> *list);    virtual int zrlist(const Bytes &name_s, const Bytes &name_e, uint64_t limit,            std::vector<std::string> *list);

实现如下:
只看插入 即zset:

int SSDBImpl::zset(const Bytes &name, const Bytes &key, const Bytes &score, char log_type){    Transaction trans(binlogs);    int ret = zset_one(this, name, key, score, log_type);    if(ret >= 0){        if(ret > 0){            if(incr_zsize(this, name, ret) == -1){                return -1;            }        }        leveldb::Status s = binlogs->commit();        if(!s.ok()){            log_error("zset error: %s", s.ToString().c_str());            return -1;        }    }    return ret;}

主要调用了 zset_one:

// returns the number of newly added itemsstatic int zset_one(SSDBImpl *ssdb, const Bytes &name, const Bytes &key, const Bytes &score, char log_type){    if(name.empty() || key.empty()){        log_error("empty name or key!");        return 0;        //return -1;    }    if(name.size() > SSDB_KEY_LEN_MAX ){        log_error("name too long!");        return -1;    }    if(key.size() > SSDB_KEY_LEN_MAX){        log_error("key too long!");        return -1;    }    std::string new_score = filter_score(score);    std::string old_score;    int found = ssdb->zget(name, key, &old_score);//先查询是否有旧的值,有的话记录old值    if(found == 0 || old_score != new_score){ //需要更新或插入新值        std::string k0, k1, k2;        if(found){            // delete zscore key            k1 = encode_zscore_key(name, key, old_score);//将三个值编码成底层leveldb 操作的key。也就是旧的score            ssdb->binlogs->Delete(k1);//删掉        }        // add zscore key        k2 = encode_zscore_key(name, key, new_score);        ssdb->binlogs->Put(k2, "");//插入新的score        // update zset        k0 = encode_zset_key(name, key);        ssdb->binlogs->Put(k0, new_score); //提交leveldb        ssdb->binlogs->add_log(log_type, BinlogCommand::ZSET, k0); //写log        return found? 0 : 1;    }    return 0;}

问题来了,新的记录插入,如何排序呢?
靠的是 encode_zscore_key函数,这个函数要确保 两个score 编码前后 大小关系一致,也就是说 如果编码前score1> score2,则编码后 e_score1 > e_score2 依然成立。

static inline                                       std::string encode_zscore_key(const Bytes &key, const Bytes &val, const Bytes &score){//zset_one 调用此函数时 传入的参数分别是:name、key、score//例如 client 输入 命令 zset 'z' 1 100//再输入 第二条  zset 'z' 2 99// 'z' 对应 name    2 对应key    99 对应score// 传入到encode 函数后 'z' 对应 key, 2 对应 value , 99对应 score。//如何保证编码以score决定大小,而排除key的影响呢?    std::string buf;    buf.append(1, DataType::ZSCORE); //这个append 都一样    buf.append(1, (uint8_t)key.size()); //这个 append 也都一样  都是  'z' 的size    buf.append(key.data(), key.size());//都是 'z'    int64_t s = score.Int64();     if(s < 0){        buf.append(1, '-');    }else{        buf.append(1, '=');    }    s = encode_score(s);    buf.append((char *)&s, sizeof(int64_t));//接着就append 了score 99   到这里已经比出了大小 string 只比较第一个不一样的 字符。    buf.append(1, '=');    buf.append(val.data(), val.size()); //最后才append 2     return buf;}

为什么传入leveldb的key 可比较大小,就实现了soreted_set 呢?
这是因为 leveldb的内存实现是skiplist,它是排序的。磁盘文件sst也是排序的.
zget 很简单就不讲了。

0 0
原创粉丝点击