DCache 分布式存储系统|Set, ZSet 缓存模块的创建与使用

DCache 分布式存储系统|Set, ZSet 缓存模块的创建与使用

作者 | Eaton

导语 | 在之前的系列文章中,我们介绍了 DCache 及其 KV, K-K-Row 和 List 缓存模块的使用,本文将继续介绍如何使用 DCache 中的集合类型缓存模块 —— Set 和 ZSet 缓存模块。

系列文章

目录

DCache 是一个基于 TARS 框架开发的分布式 NoSQL 存储系统,支持多种数据结构,包括了 key-value(键值对),k-k-row(多键值),list(列表),set(集合),zset(有序集合)等,满足多种业务需求。

在前面的文章中,我们介绍过 key-value, k-k-rowlist 两种类型缓存模块的使用方式,本文将继续介绍集合类型,setzset 缓存模块的使用。

Set 与 ZSet 模块简介

set 即集合,与 list 类似,以列表形式存储数据。不同的地方在于 set 是会对添加的数据进行排重的。如果你需要存储一个列表数据,又不希望出现重复数据时,set 是一个很好的选择。

zset 为有序集合,使用场景与 set 类似,但 set 并不是自动有序的。在 zset 中,提供了一个的参数 score 来为数据成员排序。当你需要一个有序的并且不重复的集合列表,那么可以选择 zset 数据结构。比如微信朋友圈可以以发表时间作为 score 来存储,这样获取时就是自动按时间排好序的。

set 相比,zset 关联了一个 double 类型权重参数 score,使得集合中的元素能够按 score 进行有序排列。

同样地,与其它模块相似,使用 setzset 缓存服务的步骤如下

  1. 创建 Set/ZSet 缓存模块
  2. 获取 DCache 接口文件
  3. 创建缓存服务代理
  4. 调用 Set/ZSet 缓存模块服务

接下来将继续基于 TestDemo 介绍如何创建和使用 Set/ZSet 缓存模块。

本文使用的示例可以在 GitHub 仓库 DCacheDemo 中查看。

创建 Set/ZSet 缓存模块

前面的文章我们已经介绍过缓存模块的创建,各类型缓存模块创建流程是相似的。这里 Set 缓存服务命名为 TestDemoSetcache 类型 选择 Set(MKVCache)

DCache 分布式存储系统|Set, ZSet 缓存模块的创建与使用

新建 ZSet 缓存服务命名为 TestDemoZSetcache 类型 选择 Zset(MKVCache)

DCache 分布式存储系统|Set, ZSet 缓存模块的创建与使用

对于步骤 2 和 3,我们已经在前面的系列文章中介绍过,本文不再赘述。还不了解的朋友请移步 Key-Value 缓存模块的创建与使用

调用缓存模块服务

本部分将通过简单示例,介绍 setzset 类型缓存模块部分接口的使用。关于其它接口的信息,参见 Proxy 接口指南

我们继续使用 TestDemo,新增模块名 ModuleTestDemoSetModuleTestDemoZSet,值为我们前面创建的模块名 TestDemoSetTestDemoZSet,用于之后通过代理调用这两个模块,如下。

// main.cpp

...

static string ModuleTestDemoSet   = "TestDemoSet";
static string ModuleTestDemoZSet  = "TestDemoZSet";

...

接口调用流程与 TARS 服务接口调用流程一致。如果你还不清楚 TARS 服务的调用方式和流程,可以阅读文章 TARS RPC 通信框架|提供多种远程调用方式 了解 TARS 服务的调用方式。

后面的示例中,会使用到三个工具函数,定义如下

// 构建 UpdateValue
DCache::UpdateValue genUpdateValue(DCache::Op op, const string &value)
{
    DCache::UpdateValue updateValue;
    updateValue.op = op;
    updateValue.value = value;
    return updateValue;
}

// 打印 map<string, string> 类型数据
void printMapData(const map<string, string> &data)
{
    map<string, string>::const_iterator it = data.begin();
    while (it != data.end())
    {
        cout << "|" << it->first << ":" << it->second;
        ++it;
    }
    cout << endl;
}

// 打印 vector<map> 数据
void printVectorMapData(const vector<map<string, string>> &data)
{
    for (auto item : data)
    {
        printMapData(item);
    }
}

那么接下来,我们来看看怎么使用 DCache 的 Set/ZSet 缓存模块。

Set 模块读写操作

Set 为集合缓存模块。这里介绍写接口 addSet 和读接口 getSet,其它接口用法相似。

向集合添加值

接口 addSet 用于向特定集合添加值,定义如下

int addSet(const AddSetReq &req)

其中结构 AddSetReq 及其嵌套结构 AddSetKeyValue 的定义如下

struct AddSetReq
{
  1 require string moduleName;     // 模块名
  2 require AddSetKeyValue value;  // 待写入数据
};

struct AddSetKeyValue
{
  1 require string mainKey;  // 主key
  2 require map<string, UpdateValue> data;  // 其他字段数据
  3 require int expireTime;  // 过期时间
  4 require bool dirty = true;  // 是否设置为脏数据
};

使用示例如下

void testAddSet(const string &mainKey, const map<string, string> &data, DCache::ProxyPrx prx)
{
    cout << SUBTEST_PREFIX << "addSet ";

    // 构造请求
    DCache::AddSetReq req;
    req.moduleName = ModuleTestDemoSet;
    req.value.mainKey = mainKey;
    req.value.expireTime = time(NULL) + 60 * 60 * 24;

    map<string, string>::const_iterator it = data.begin();
    while (it != data.end())
    {
        req.value.data[it->first] = genUpdateValue(DCache::SET, it->second);
        ++it;
    }

    int ret = prx->addSet(req);

    if (ret == DCache::ET_SUCC)
    {
        printMapData(data);
        return;
    }
    cout << "ret:" << ret << endl;
}

获取集合

接口 getSet 用于获取集合中的数据,定义如下

int getSet(const GetSetReq &req, BatchEntry &rsp)

其中请求消息结构 GetSetReq 和返回消息结构 BatchEntry 定义如下

struct GetSetReq
{
  1 require string moduleName;  //模块名
  2 require string mainKey;  //主key
  3 require string field;  //需要查询的字段集,多个字段用','分隔如 "a,b", "*"表示所有
  4 require string idcSpecified = "";  //idc区域
};

struct BatchEntry
{
  1 require vector<map<string, string>> entries;  //查询结果集合
};

使用示例如下

void testGetSet(const string &mainKey, DCache::ProxyPrx prx)
{
    cout << SUBTEST_PREFIX << "getSet " << endl;

    // 构造请求
    DCache::GetSetReq req;
    req.moduleName = ModuleTestDemoSet;
    req.mainKey = mainKey;
    req.field = "*";

    DCache::BatchEntry rsp;
    int ret = prx->getSet(req, rsp);

    if (ret == DCache::ET_SUCC)
    {
        // 打印返回值
        printVectorMapData(rsp.entries);
        return;
    }
    cout << "ret:" << ret << endl;
}

ZSet 模块读写操作

ZSet 即有序集合缓存模块,这里介绍写接口 addZSet 和读接口 getZSetByPos,其它接口用法类似。

向集合添加值和权重

接口 addZSet 用于向集合添加数据值及其权重,定义如下

int addZSet(const AddZSetReq &req)

其中请求消息结构体 AddZSetReq 及其嵌套结构体 AddSetKeyValue 的定义如下

AddZSetReq
{
  1 require string moduleName;  //模块名
  2 require AddSetKeyValue value;  //待写入数据
  3 require double score;  //待写入数据分值
};

struct AddSetKeyValue
{
  1 require string mainKey;  //主key
  2 require map<string, UpdateValue> data; //其他字段数据
  3 require int expireTime;  //数据过期时间
  4 require bool dirty = true;  //是否设置为脏数据
};

使用示例如下

void testAddZSet(const string &mainKey, const map<string, string> &data, const double &score, DCache::ProxyPrx prx)
{
    cout << SUBTEST_PREFIX << "addZSet ";

    // 构造请求
    DCache::AddZSetReq req;
    req.moduleName = ModuleTestDemoZSet;
    req.value.mainKey = mainKey;
    req.score = score;

    map<string, string>::const_iterator it = data.begin();
    while (it != data.end())
    {
        req.value.data[it->first] = genUpdateValue(DCache::SET, it->second);
        ++it;
    }

    int ret = prx->addZSet(req);

    if (ret == DCache::ET_SUCC)
    {
        printMapData(data);
        return;
    }
    cout << "ret:" << ret << endl;
}

获取集合

接口 getZSetByPos 用于获取集合中指定索引区间内的数据,定义如下

int getZSetByPos(const GetZsetByPosReq &req, BatchEntry &rsp)

其中请求消息结构体 GetZsetByPosReq 的定义如下

struct GetZsetByPosReq
{
  1 require string moduleName;  //模块名
  2 require string mainKey;  //主key
  3 require string field;  //需要查询的字段集,多个字段用','分隔如 "a,b", "*"表示所有
  4 require long start;  //开始索引
  5 require long end;  //结束索引
  6 require bool positiveOrder = true; //true表示返回的结果按递增排序,false表示递减
  7 require string idcSpecified = "";  //idc区域
};

struct BatchEntry
{
  1 require vector<map<string, string>> entries;  //查询结果数据集合
};

使用示例如下

void testGetZSet(const string &mainKey, DCache::ProxyPrx prx)
{
    cout << SUBTEST_PREFIX << "getZSet " << endl;

    // 构造请求
    DCache::GetZsetByPosReq req;
    req.moduleName = ModuleTestDemoZSet;
    req.mainKey = mainKey;
    req.field = "*";
    req.start = 0;
    req.end = 3;

    DCache::BatchEntry rsp;
    int ret = prx->getZSetByPos(req, rsp);

    if (ret == DCache::ET_SUCC)
    {
        // 打印返回值
        printVectorMapData(rsp.entries);
        return;
    }
    cout << "ret:" << ret << endl;
}

实例

我们来实际运行一下上面的使用示例。完整的使用示例可以在 GitHub 仓库 DCacheDemo 中获取。

我们通过 testSettestZSet 测试上节提到的接口,分别向 Set 和 ZSet 缓存服务中依次添加值 hello, hello, hi, test;并且向 ZSet 服务添加的值附带权重,如下

void testSet(DCache::ProxyPrx prx)
{
    cout << START << " testSet" << endl;

    string mainKey = "testKey";
    map<string, string> data1, data2, data3, data4;
    data1["VALUE"] = "hello";
    data2["VALUE"] = "hello";
    data3["VALUE"] = "hi";
    data4["VALUE"] = "test";

    testAddSet(mainKey, data1, prx);
    testAddSet(mainKey, data2, prx);
    testAddSet(mainKey, data3, prx);
    testAddSet(mainKey, data4, prx);

    testGetSet(mainKey, prx);

    cout << END << " testSet" << endl;
}

void testZSet(DCache::ProxyPrx prx)
{
    cout << START << " testZSet" << endl;

    string mainKey = "testKey";
    map<string, string> data1, data2, data3, data4;
    double score1, score2, score3, score4;
    data1["VALUE"] = "hello";
    score1 = 0.1;
    data2["VALUE"] = "hello";
    score2 = 0.1;
    data3["VALUE"] = "hi";
    score3 = 0.8;
    data4["VALUE"] = "test";
    score4 = 0.5;

    testAddZSet(mainKey, data1, score1, prx);
    testAddZSet(mainKey, data2, score2, prx);
    testAddZSet(mainKey, data3, score3, prx);
    testAddZSet(mainKey, data4, score4, prx);

    testGetZSet(mainKey, prx);

    cout << END << " testZSet" << endl;
}

接着,在 main 函数中执行

int main(int argc, char *argv[])
{
    ...

        auto prx = comm->stringToProxy<DCache::ProxyPrx>(DCacheTestDemoObj);

        // 调用 DCache 缓存服务
        testSet(prx);
        testZSet(prx);
    ...
}

执行结果如下

DCache 分布式存储系统|Set, ZSet 缓存模块的创建与使用

可以看到,尽管我们插入了两次 hello,但是并没有重复的数据。同时相较于 Set,ZSet 中返回的数据都是根据 score 的权重来排序的。

其它 Set 与 ZSet 缓存模块服务接口

除了前面提到的向集合添加和获取数据,DCache 中还提供了丰富的集合操作接口,如下

/**************** Set ****************/
// 查询集合数据
int getSet(GetSetReq req, out BatchEntry rsp);
// 向集合添加数据
int addSet(AddSetReq req);
// 删除指定的一条集合数据
int delSet(DelSetReq req);

/**************** ZSet ****************/
// 根据指定条件,查询某条记录的 score 值
int getZSetScore(GetZsetScoreReq req, out double score);
// 根据指定条件,查询某条记录在已排序列表的索引位置
int getZSetPos(GetZsetPosReq req, out long pos);
// 查询集合内指定索引区间[start, end]内的数据
int getZSetByPos(GetZsetByPosReq req, out BatchEntry rsp);
// 查询分值区间[minScore, maxScore]内的数据
int getZSetByScore(GetZsetByScoreReq req, out BatchEntry rsp);
// 将带有给定分值的数据添加到有序集合中,如果数据已存在,则重置 score 值
int addZSet(AddZSetReq req);
// 修改有序集合中某条记录的分值,若数据不存在,则新建一条数据
int incScoreZSet(IncZSetScoreReq req);
// 删除有序集合中符合指定条件的某条数据
int delZSet(DelZSetReq req);
// 从有序集合中删除分值在区间[minScore, maxScore)的数据
int delZSetByScore(DelZSetByScoreReq req);
// 根据指定条件更新有序集合的某条数据
int updateZSet(UpdateZSetReq req);

接口的使用方式与前面介绍的类似,关于接口的具体入参和出参结构可以参考 Proxy 接口指南

总结

本文简要介绍了 DCache 中的 setzset 缓存模块的原理和使用流程,同时通过具体实例对部分接口的使用进行了详细介绍,帮助读者理解并能够快速上手使用 setzset 缓存模块。

TARS 可以在考虑到易用性和高性能的同时快速构建系统并自动生成代码,帮助开发人员和企业以微服务的方式快速构建自己稳定可靠的分布式应用,从而令开发人员只关注业务逻辑,提高运营效率。多语言、敏捷研发、高可用和高效运营的特性使 TARS 成为企业级产品。

TARS微服务助您数字化转型,欢迎访问:

TARS官网:https://TarsCloud.org

TARS源码:https://github.com/TarsCloud

Linux基金会官方微服务免费课程:https://www.edx.org/course/building-microservice-platforms-with-tars

获取《TARS官方培训电子书》:https://wj.qq.com/s2/7849909/01b0/

或扫码获取:

DCache 分布式存储系统|Set, ZSet 缓存模块的创建与使用

上一篇:Redis的ZSet数据结构


下一篇:Redis基本数据类型