当前位置: 首页 > 工具软件 > STVM > 使用案例 >

STVM 高速缓存数据库

许俊晤
2023-12-01

STVM

STVM(truck of Virtual memory table)是一个开源的使用ANSI C语言编写、支持本地API调用和网络调用,全表数据基于IPC共享内存方式存储,

基于C语言struck结构定义记录行,RB-Tree和hash作为主要算法的内存数据库,是一款介于SQL和NOSQL之间的一款高速缓存数据库。

由于数据全部存储在内存中,相比较其他类型的缓存,运行速度之快可想而知。

定义

stvm是一款其介于介于SQL和NOSQL,拥有以下主要特点

支持SQL基本语法(本版本支持insert、update、delete、select、group order、count,first,游标)功能。

支持序列

支持唯一索引、查询索引和组合索引(组合索引不必考虑索引字段先后)

支持条件动态查询

内置记录点击量

集群、主-子分布同步。

事务功能(假性事务)

数据持久性,进程异常退出,不会导致数据丢失,除非系统宕机。

支持网络API和函数API, 支持多线程、多进程等等

内置数据版本,维护数据一致安全。

多机集群,数据分区

当然也有几点缺陷,索引长度限制目前版本64位,修改长度需编译项目、启动后不能修改表字段(ddl语句),不能联表查询。

stvm是一款高速缓存数据库,表字段定义来源与C语言STRUCK,动态条件赋值只需要对结构体成员赋值即为查询条件。

整个操作提供特定API访问,减少解析SQL和字段的时间。

全表数据分3个区(表头区、索引区、数据区),并且全部存储在一块内存中。

每张表定义一个数据库块,每张表拥有自己读写锁,支持线程。

预留拓展表空间参数,但效率会降低扩展个数倍

 

性能测试

非常抱歉, 由于上次忘记对redis缓存持久化功能关闭和系统参数未调优,造成测试有偏差,本次测试案例

在64位 Ubuntu上测试,机器配置如下:

4G内存,CUP:Intel(R) Core(TM) i3 CPU       M 380  @ 2.53GHz

 

以下以测试fastdb和redis作为测试比较,以常用操作为测试案例(STVM上采用单表操作,主键为单字段)

1、以此新增200W条记录

2、依次删除单笔记录 (200W次 )

3、按主键循环单次获取每条数据(200W次)

4、按主键循环更新单挑数据(200W次 )

5、操作3和操作4同时进行(总共400W次)

对以上操作5次,平均取值,数据如下:

缓存软件设计版本新增(ms)查询(ms)删除(ms)更新(ms)更新/查询(ms)
fastdb-待测试待测试待测试待测试待测试
redis-103,30377,19179,566101,63191,935
stvm(net)V1(C)59,88262,65258,40062,20063,840
stvm(net)V2(JAVA)待测试待测试待测试待测试待测试
stvm(api)-3,3643,5203,4224,06313,317

以上数据仅供参考

 

稍后更新fastdb测试结果

 

支持语言

CC++Java……
支持支持待支持

字段类型

字段类型有LONG,CHAR,DOUB(对应整型、字符型、浮点型)

行记录建议不要超过500KB

为了避免内存的浪费,唯一索引和查询索引最多各一组。

使用场景

    该软件本版本建议用于数据库缓存, 减少对数据的操作,提高应用性能。    

    目前该软件仅本地api功能用于公司服务器上的数据库缓存,已在生产验证。其他功能均为个人测试通过但未生产验证,请悉知。

使用手册

启动环境变量:

TVMDBD=/home/stvm/tvmdb 指向stvm工作目录

TVMCFG=/home/stvm/tvmdb/.tvm.run 启动参数

配置完成后,stvm -w 将采用缺省参数以本机模式启动系统。

如果集群模式请配置 stvm.conf,然后用stvm -c stvm.conf编译配置文件即可。

STVM对外提供2个开发运维工具stvm和调试工具detvm。

stvm -w启动启动

stvm -s停止系统

STVM也提供一个类型sqlpuls类型简单工具,输入:

stvm SQL 进入SQL界面(该工具仅仅用来运维调试使用)。

stvm DOM 进入域的维护

建议运行在64位机器上,因为32位机器最大寻址4G,因此创建单表不能超过4G。

 

开发流程

 

创建表

方式一:

// 定义表序号

#define TBL_USER_INFO 10

必须用C定义STRUCK:

typedef struct  __TBL_USER_INFO
{
   long    acct_id;
   char    user_no[21];
   char    user_type[2];
   char    user_nm[81];
   char    user_addr[161];
}dbUser;

定义创建表函数

CREATE  lCreateUserInfo()
{
   DEFINE(TBL_USER_INFO, "TBL_USER_INFO", "", dbUser)
   FIELD(dbUser,    acct_id,         "acct_id",       FIELD_LONG)
   FIELD(dbUser,    user_no,         "user_no",       FIELD_CHAR)
   FIELD(dbUser,    user_type,       "user_type",     FIELD_CHAR)
   FIELD(dbUser,    user_nm,         "user_nm",       FIELD_CHAR)
   FIELD(dbUser,    user_addr,       "user_addr",     FIELD_CHAR)
 
   CREATE_IDX(NORMAL)        //    创建查询索引
   IDX_FIELD(dbUser, acct_id,       FIELD_LONG)
 
   CREATE_IDX(UNQIUE)        //    创建唯一索引
   IDX_FIELD(dbUser, user_no,       FIELD_CHAR)
   IDX_FIELD(dbUser, user_type,     FIELD_CHAR)
 
   FINISH
}

调用api:

lCreateTable(pstSavm, TBL_USER_INFO, 100000, lCreateUserInfo)  完成对TBL_USER_INFO表的创建。


TBL_USER_INFO 创建的表序号,其中pstSavm为线程句柄

100000初始化内存记录空间条数。

lCreateUserInfo创建表定义函数。

调用成功则返回RC_SUCC, 失败返回RC_FAIL, 失败可调用pstSavm->m_lErrno获取处理失败错误码,sGetTError(pstSavm->m_lErrno)获取错误信息。

方式二:

set TABLE=20
set TABLESPACE=10000

create table TBL_USER_INFO
(
    acct_id     long,
    user_no     char(21),
    user_type   char[2],
    user_nm     char[81],
    user_addr   char[161],
    user_phone  char[31]
);

-- Create indexes
create unique index (user_no, user_type);
create index  (acct_id);

将创建表脚本,保存至文件 tbl_user_info.def, 表字段支持类型char、shor、int、long、llong、double float。

在stvm SQL执行 create tbl_user_info.def来创建该表。

表对应的结构体可以通过 stvm -t table来导出。

操作表(单条查询)

dbUser stUser;
SATvm  *pstSavm = (SATvm *)pGetSATvm();
 
//    初始化TBL_USER_INFO表,每张表都需要初始化一次, 对于表重建后,需要重新初始化一次。
if(RC_SUCC != lInitSATvm(pstSavm, TBL_USER_INFO))
{
    fprintf(stderr, "init failed, err:(%d)(%s)\n", pstSavm->m_lErrno, sGetTError(pstSavm->m_lErrno));
    return ;
}

conditinit(pstSavm, stUser, TBL_USER_INFO);                // 绑定变量
numberset(pstSavm, stUser, acct_id, 10021);                // 查询条件赋值
stringset(pstSavm, stUser, user_no, "20180223");           // 查询条件赋值
 
if(RC_SUCC != lSelect(pstSavm, (void *)&stUser))           // 单条记录查询
{
    fprintf(stderr, "Select错误ep:%d, err:(%d)(%s)\n", pstSavm->m_lEType, pstSavm->m_lErrno,
        sGetTError(pstSavm->m_lErrno));
    return ;
}
 
fprintf(stdout, "acct_id:%ld, user_no:%s, user_type:%s, user_nm:%s, user_addr:%s\n",
    stUser.acct_id, stUser.user_no, stUser.user_type, stUser.user_nm, stUser.user_addr);

操作表(多条查询)

size_t  i = 0, lOut;
dbUser  *pstUser = NULL;
SATvm   *pstSavm = (SATvm *)pGetSATvm();

//  初始化TBL_USER_INFO表,每张表都需要初始化一次, 对于表重建后,需要重新初始化一次。                
if(RC_SUCC != lInitSATvm(pstSavm, TBL_USER_INFO))
{
    fprintf(stderr, "init failed, err:(%d)(%s)\n", pstSavm->m_lErrno, sGetTError(pstSavm->m_lErrno));
    return ;
}
     
conditinit(pstSavm, stUser, TBL_USER_INFO);                   // 绑定变量
numberset(pstSavm, stUser, user_type, "1");                   // 查询条件赋值
stringset(pstSavm, stUser, user_no, "20180223");              // 查询条件赋值
 
 
if(RC_SUCC != lQuery(pstSavm, &lOut, (void **)&pstUser))      // 批量查询查询  
{
    fprintf(stderr, "Select错误ep:%d, err:(%d)(%s)\n", pstSavm->m_lEType, pstSavm->m_lErrno,
        sGetTError(pstSavm->m_lErrno));
    return ;
}
  
for(i = 0; i < lOut; i ++)
{
    fprintf(stdout, "acct_id:%ld, user_no:%s, user_type:%s, user_nm:%s, user_addr:%s\n", 
       pstUser[i].acct_id, pstUser[i].user_no, pstUser[i].user_type, pstUser[i].user_nm, 
       pstUser[i].user_addr);
}

操作表(新增记录)

dbUser  stUser;
SATvm   *pstSavm = (SATvm *)pGetSATvm();

//    初始化TBL_USER_INFO表,每张表都需要初始化一次, 对于表重建后,需要重新初始化一次。                
if(RC_SUCC != lInitSATvm(pstSavm, TBL_USER_INFO))
{
    fprintf(stderr, "init failed, err:(%d)(%s)\n", pstSavm->m_lErrno, sGetTError(pstSavm->m_lErrno));
    return ;
}

stUser.acct_id = 10021;                                        // 对结构体赋值
strcpy(stUser.user_no, "20180223");                            // 对结构体赋值
strcpy(stUser.user_type, "1");                                 // 对结构体赋值
insertinit(pstSavm, stUser, TBL_USER_INFO);                    // 绑定变量

if(RC_SUCC != lInsert(pstSavm))      // 插入记录  
{
    fprintf(stderr, "Select错误ep:%d, err:(%d)(%s)\n", pstSavm->m_lEType, pstSavm->m_lErrno,
        sGetTError(pstSavm->m_lErrno));
    return ;
}

操作表(删除记录)

dbUser  stUser;
SATvm   *pstSavm = (SATvm *)pGetSATvm();

//  初始化TBL_USER_INFO表,每张表都需要初始化一次, 对于表重建后,需要重新初始化一次。                
if(RC_SUCC != lInitSATvm(pstSavm, TBL_USER_INFO))
{
    fprintf(stderr, "init failed, err:(%d)(%s)\n", pstSavm->m_lErrno, sGetTError(pstSavm->m_lErrno));
    return ;
}

conditinit(pstSavm, stUser, TBL_USER_INFO)                     // 对结构体赋值
numberset(pstSavm, stUser, user_type, "1");                    // 查询条件赋值
stringset(pstSavm, stUser, user_no, "20180223");               // 查询条件赋值

if(RC_SUCC != lDelete(pstSavm))      // 删除记录  
{
    fprintf(stderr, "Delete err:(%d)(%s)\n", pstSavm->m_lEType, pstSavm->m_lErrno,
        sGetTError(pstSavm->m_lErrno));
    return ;
}

操作表(修改记录)

dbUser  stUser, stUpd;
SATvm       *pstSavm = (SATvm *)pGetSATvm();

if(RC_SUCC != lInitSATvm(pstSavm, TBL_USER_INFO))
{
    fprintf(stderr, "init failed, err:(%d)(%s)\n", pstSavm->m_lErrno, sGetTError(pstSavm->m_lErrno));
    return ;
}
 
updateinit(stUpd);    
conditinit(pstSavm, stUser, TBL_USER_INFO)                        // 绑定变量
numberset(pstSavm, stUser, user_type, "1");                       // 查询条件赋值
stringset(pstSavm, stUser, user_no, "20180223");                  // 查询条件赋值

stringupd(pstSavm, stUpd, user_addr, "china");
if(RC_SUCC != lUpdate(pstSavm, (void *)&stUpd))
{
    fprintf(stderr, "Update err:(%d)(%s), (%ld)\n", pstSavm->m_lErrno,
        sGetTError(pstSavm->m_lErrno), lGetEffect());
    return ;
}

其他函数lTruncate、lCount、lExtreme、lGroup,lDropTable、lRenameTable 和网络API如lTvmDelete、lTvmInsert、lTvmSelect、lTvmUpdate、lTvmTruncate、lTvmCount、lTvmExtreme、lTvmGroup、lTvmQuery等,用法相似,不做一一枚举。[1] 

版本发布

2018年03月01日,STVM 1.2.1 。 https://github.com/DeffPuzzL/STVM


 类似资料: