SSDB --- zset实现

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 items
static 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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值