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

作者 | Eaton

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

系列文章

目录

  • Set 与 ZSet 模块简介
  • 创建 Set/ZSet 缓存模块
  • 调用 Set/ZSet 缓存模块服务

    • Set 模块读写操作
    • ZSet 模块读写操作
    • 实例
  • 其它 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)

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

对于步骤 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);
    ...
}

执行结果如下

可以看到,尽管我们插入了两次 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/bu...

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

或扫码获取:

QR

2020年3月10日,Linux基金会正式宣布旗下的TARS开源项目将成立TARS基金会。TARS基金会是一个专注于微服...

16 声望
7 粉丝
0 条评论
推荐阅读
花了几个月时间把 MySQL 重新巩固了一遍,梳理了一篇几万字 “超硬核” 的保姆式学习教程!(持续更新中~)
MySQL 是最流行的关系型数据库管理系统,在 WEB 应用方面 MySQL 是最好的 RDBMS(Relational Database Management System:关系数据库管理系统)应用软件之一。

民工哥14阅读 2k

封面图
初学后端,如何做好表结构设计?
这篇文章介绍了设计数据库表结构应该考虑的4个方面,还有优雅设计的6个原则,举了一个例子分享了我的设计思路,为了提高性能我们也要从多方面考虑缓存问题。

王中阳Go4阅读 1.7k评论 2

封面图
Vue+Express+Mysql全栈项目之增删改查、分页排序导出表格功能
本文记录一下实现一个全栈项目,前端使用vue框架、后端使用express框架、数据库使用mysql。此项目的意义不仅仅有助于我们复习nodejs相关知识、更有助于带前端新人,使其快速从整体全局角度中,理解常规后台管理系...

水冗水孚4阅读 2.6k

MySQL百万数据深度分页优化思路分析
一般在项目开发中会有很多的统计数据需要进行上报分析,一般在分析过后会在后台展示出来给运营和产品进行分页查看,最常见的一种就是根据日期进行筛选。这种统计数据随着时间的推移数据量会慢慢的变大,达到百万...

一个程序员的成长7阅读 927

封面图
深入理解MySQL索引底层数据结构
在日常工作中,我们会遇见一些慢SQL,在分析这些慢SQL时,我们通常会看下SQL的执行计划,验证SQL执行过程中有没有走索引。通常我们会调整一些查询条件,增加必要的索引,SQL执行效率就会提升几个数量级。我们有没...

京东云开发者3阅读 592

封面图
Laravel入门及实践,快速上手ThinkSNS+二次开发
【摘要】自从ThinkSNS+不使用ThinkPHP框架而使用Laravel框架之后,很多人都说技术门槛抬高了,其实你与TS+的距离仅仅只是学习一个新框架而已,所以,我们今天来说说Laravel的入门。

ThinkSNS1阅读 2.4k

一文了解MySQL中的多版本并发控制
作者:京东零售  李泽阳最近在阅读《认知觉醒》这本书,里面有句话非常打动我:通过自己的语言,用最简单的话把一件事情讲清楚,最好让外行人也能听懂。也许这就是大道至简,只是我们习惯了烦琐和复杂。希望借助...

京东云开发者2阅读 517

封面图

2020年3月10日,Linux基金会正式宣布旗下的TARS开源项目将成立TARS基金会。TARS基金会是一个专注于微服...

16 声望
7 粉丝
宣传栏