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

AlgoHorizonPro
• 阅读 2215

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)

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/bu...

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

或扫码获取:

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

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Wesley13 Wesley13
4年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Jacquelyn38 Jacquelyn38
4年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Easter79 Easter79
4年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
4年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
4年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
4年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
为什么mysql不推荐使用雪花ID作为主键
作者:毛辰飞背景在mysql中设计表的时候,mysql官方推荐不要使用uuid或者不连续不重复的雪花id(long形且唯一),而是推荐连续自增的主键id,官方的推荐是auto_increment,那么为什么不建议采用uuid,使用uuid究
Python进阶者 Python进阶者
2年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这