DCache 分布式存储系统|K-K-Row 缓存模块的创建与使用

义公
• 阅读 2559

DCache 分布式存储系统|K-K-Row 缓存模块的创建与使用

作者 | Eaton

导语 | 随着微服务与云的发展,分布式架构的需求变得越来越普遍,传统的 SQL 结构化存储方案已经跟不上脚步,于是 NoSQL 出现了。DCache 作为基于 TARS 的分布式 NoSQL 缓存系统,完美支持 TARS 服务。前一篇文章中,我们介绍了怎么创建并使用 KV 模块,本文将继续介绍如何创建和使用 DCache 中的 K-K-Row 缓存模块。

系列文章

目录

  • K-K-Row 模块简介
  • 创建 K-K-Row 缓存模块
  • 获取 DCache 接口文件
  • 创建缓存服务代理
  • 调用缓存模块服务

    • K-K-Row 模块读写操作
    • 运行示例
  • 总结

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

我们在文章 Key-Value 缓存模块的创建与使用 中介绍了 key-value 类型的使用,也提到了其在结构化数据存储上的缺点。而 k-k-row 类型就是一种结构化存储方案。

K-K-Row 模块简介

k-k-row,与 key-value 相似,但这里 value 不是字符串,而是相当于一张表,能够存储结构化数据。k-k-rowkey key row,指通过两个 key,即主索引/主键(Main Key)和联合索引(Union Key),能够唯一确定一条记录 row,如下

DCache 分布式存储系统|K-K-Row 缓存模块的创建与使用

不难看出,k-k-row 的存储结构和 SQL 数据库很像,主键相当于表名,映射到 Value。既不需要重复存储数据,也不会带来序列化和并发修改控制的问题,很好的解决了问题。

与 KV 模块相似,我们只需完成以下步骤即可在服务中使用 k-k-row 缓存服务

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

本文将继续基于 TestDemo 介绍如何创建 K-K-Row 缓存模块,以及怎么在 TARS 服务中调用该服务来缓存数据。

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

创建 K-K-Row 缓存模块

在文章 Key-Value 缓存模块的创建与使用 中,我们已经介绍过如何创建 Key-Value 缓存模块,各类型缓存模块创建流程是相似的,这部分不再赘述,仅介绍不同的部分。

这里我们将缓存模块服务命名为 TestDemoKKRowcache 类型 选择 k-k-row(MKVCache),如下

DCache 分布式存储系统|K-K-Row 缓存模块的创建与使用

K-K-Row 为多键值类型,配置字段时可以新增多个联合索引或数据字段,点击 添加,如下

DCache 分布式存储系统|K-K-Row 缓存模块的创建与使用

确认好已配置信息后,点击 安装发布 即可完成发布。

到这里,我们就可以在其它服务中使用该缓存模块来缓存 K-K-Row 数据了。

获取 DCache 接口文件

DCache 是基于 TARS 开发的,因此使用上和 TARS 服务一样,也是通过 .tars 接口文件来调用对应缓存服务的接口。

我们复制 DCache/src/TarsComm 下的 CacheShare.tars, ProxyShare.tarsDCache/src/Proxy 下的 Proxy.tars 到自己项目目录下即可。

本文 Demo 获取 DCache 接口文件后的项目文件结构如下

DCacheDemo
├── CacheShare.tars
├── ProxyShare.tars
├── Proxy.tars
├── config.conf
├── main.cpp
└── makefile

创建缓存服务代理

前一篇文章我们提到过,创建一个应用后会自动创建一个路由服务和代理服务,并通过 TestDemo 介绍了如何创建缓存服务代理来调用服务。

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

// main.cpp
#include <iostream>
#include <map>
#include "servant/Communicator.h"
#include "servant/ServantProxy.h"
#include "Proxy.h"

using namespace std;
using namespace tars;

// TestDemo 代理服务对象名
static string DCacheTestDemoObj = "DCache.TestDemoProxyServer.ProxyObj";

// 缓存模块名
static string ModuleTestDemoKV    = "TestDemoKV";
static string ModuleTestDemoKKRow = "TestDemoKKRow";

int main(int argc, char *argv[])
{
    CommunicatorPtr comm = new Communicator();
    try
    {
        TC_Config conf;
        // 解析配置文件
        conf.parseFile("config.conf");
        // 加载配置
        comm->setProperty(conf);
        // 生成代理
        auto prx = comm->stringToProxy<DCache::ProxyPrx>(DCacheTestDemoObj);

        // TODO: 调用 DCache 缓存服务
    }
    catch (exception &e)
    {
        cerr << "error: " << e.what() << endl;
    }
    catch (...)
    {
        cerr << "Unknown Error" << endl;
    }
}

调用 K-K-Row 缓存模块服务

通过 TestDemo 代理服务的代理对象和模块名 TestDemoKKRow,我们就能够调用前面创建的K-K-Row 缓存模块的接口了。

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

接口调用流程与 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);
    }
}

genUpdateValue 用于构建 DCache::UpdateValue 结构,该结构用于存储插入或更新的数据值,在其它类型模块的接口中,经常会用到。printMapDataprintVectorMapData 用于方便打印返回的数据。

那么接下来,我们来看看怎么使用 K-K-Row 缓存模块。

K-K-Row 模块读写操作

K-K-Row 即多键值模块,一个主键可以对应多条记录。这里我们仅介绍写接口 insertMKV 和读接口 getMKV,其它接口类似。

插入数据

接口 insertMKV 用于插入键值对数据,定义如下

int insertMKV(const InsertMKVReq &req)

其中结构 InsertMKVReq 及其嵌套结构 InsertKeyValue 的定义如下

struct InsertMKVReq
{
  1 require string moduleName;    // 模块名
  2 require InsertKeyValue data;  // 待写入数据
};

struct InsertKeyValue
{
  1 require string mainKey;  // 主key
  2 require map<string, UpdateValue> mpValue;  // 除主key外的其他字段数据
  3 require byte ver = 0;           // 版本号
  4 require bool dirty = true;      // 是否设置为脏数据
  5 require bool replace = false;   // 如果记录已存在且replace为true时则覆盖旧记录
  6 require  int  expireTimeSecond = 0;  // 数据过期时间
};

使用示例如下

void testInsertMKV(const string &mainKey, const map<string, string> &data, DCache::ProxyPrx prx)
{
    cout << "\t-- " << "insertMKV ";
    // 打印准备插入的数据
    printMapData(data);

    // 构造插入数据
    DCache::InsertKeyValue insertData;
    insertData.mainKey = mainKey;

    map<string, string>::const_iterator it = data.begin();
    while (it != data.end())
    {
        // 构造 UpdateValue
        insertData.mpValue[it->first] = genUpdateValue(DCache::SET, it->second);
        ++it;
    }

    // 构造请求
    DCache::InsertMKVReq insertReq;
    insertReq.moduleName = ModuleTestDemoKKRow;
    insertReq.data = insertData;

    prx->insertMKV(insertReq);
}

获取数据

接口 getMKV 用于根据主键获取主键对应的键值对,定义如下

int getMKV(const GetMKVReq &req, GetMKVRsp &rsp)

请求消息结构 GetMKVReq 及返回消息结构 GetMKVRsp 的定义如下

struct GetMKVReq
{
  1 require string moduleName;  // 模块名
  2 require string mainKey;     // 主key
  3 require string field;       // 需要查询的字段集,多个字段用','分隔如 "a,b", "*"表示所有
  4 require vector<Condition> cond;   // 查询条件集合,除主Key外的其他字段,多个条件直间为And关系
  5 require bool retEntryCnt = false; // 是否返回主key下的总记录条数
  6 require string idcSpecified = ""; // idc区域
};

struct GetMKVRsp
{
  1 require vector<map<string, string> > data;  //查询结果
};

使用示例如下

void testGetMKV(const string &key, DCache::ProxyPrx prx)
{
    cout << "\t-- " << "getMKV    " << '\n';

    // 构造请求
    DCache::GetMKVReq req;
    req.moduleName = ModuleTestDemoKKRow;
    req.mainKey = key;
    req.field = "*";

    DCache::GetMKVRsp rsp;
    prx->getMKV(req, rsp);

    // 打印返回数据
    printVectorMapData(rsp.data);
}

运行示例

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

我们通过 testKKRow 测试上节提到的模块读写接口,我们向同一主键插入两条记录,UID 分别为 test1, test2,如下

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

    string mainKey = "Key";
    map<string, string> data;
    data["UID"] = "test1";
    data["VALUE"] = "hello";
    testInsertMKV(mainKey, data, prx);

    data["UID"] = "test2";
    data["VALUE"] = "hey";
    testInsertMKV(mainKey, data, prx);

    testGetMKV(mainKey, prx);

    cout << END << " testKKRow" << endl;
}

接着,在 main 函数中执行

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

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

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

编译构建并运行示例,结果如下

DCache 分布式存储系统|K-K-Row 缓存模块的创建与使用

可以看到,getMKV 返回了两条记录。以上就是DCache缓存模块的具体使用流程。到此,我们成功调用了 DCache 的 K-K-Row 缓存服务。

K-K-Row 缓存模块服务接口

除了设置键值接口 insertMKV 和读取键值接口 getMKV,DCache 中还提供了丰富的 K-K-Row 操作接口,包括批量插入(insertMKVBatch), 删除(delMKV), 更新(updateMKV) 等,如下

// 按主key查询,支持'and'条件匹配
int getMKV(GetMKVReq req, out GetMKVRsp rsp);
// 按主key批量数据查询,给定多个主key,用统一的条件进行匹配查询
int getMKVBatch(MKVBatchReq req, out MKVBatchRsp rsp);
// 按主键批量查询
int getMUKBatch(MUKBatchReq req, out MUKBatchRsp rsp);
// 按主key批量查询,针对每个主key支持'and','or'复杂条件匹配
int getMKVBatchEx(MKVBatchExReq req, out MKVBatchExRsp rsp);
// 获取主key下的记录数,返回值为正数时,为主key下记录数
int getMainKeyCount(MainKeyReq req);
// 获取cache中所有的主key,不包含落地db的key
int getAllMainKey(GetAllKeysReq req, out GetAllKeysRsp rsp);
// 插入一条记录到Cache
int insertMKV(InsertMKVReq req);
// 插入批量数据到Cache
int insertMKVBatch(InsertMKVBatchReq req, out MKVBatchWriteRsp rsp);
// 批量更新接口。只支持指定联合key的更新
int updateMKVBatch(UpdateMKVBatchReq req, out MKVBatchWriteRsp rsp);
// 更新Cache记录,更新接口不能更新联合key字段。
int updateMKV(UpdateMKVReq req);
// 原子更新接口。适用于对数据做自增自减操作,多线程操作能保证数据原子更新。
int updateMKVAtom(UpdateMKVAtomReq req);
// 删除 Cache记录
int eraseMKV(MainKeyReq req);
// 删除Cache和Db记录
int delMKV(DelMKVReq req);
// 批量删除, rsp.rspData中存储了每个删除请求的结果
int delMKVBatch(DelMKVBatchReq req, out MKVBatchWriteRsp rsp);

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

总结

本文通过使用示例,介绍了 DCache 中 K-K-Row 缓存模块的创建和使用方式,满足开发者对结构化缓存数据的需求。

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 分布式存储系统|K-K-Row 缓存模块的创建与使用

点赞
收藏
评论区
推荐文章
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
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
7个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
皕杰报表之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
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
3年前
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
3年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这