• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

AvenirSQL: 用Node.js设计一个数据库

原作者: [db:作者] 来自: 网络 收藏 邀请

开源软件名称:

AvenirSQL

开源软件地址:

https://gitee.com/onlyyyy/AvenirSQL

开源软件介绍:

AvenirSQL

介绍

用Node.js设计一个数据库,支持常见的SQL语句

安装使用

git clone https://gitee.com/onlyyyy/AvenirSQL

cd AvenirSQL

npm i pm2 -g 安装pm2管理工具

pm2 start AvenirSQL 注意查看run.ini 设置数据库的配置文件

更新配置需要重启数据库

pm2 restart AvenirSQL

建议使用高版本Node.js(v14+),通过版本管理工具n进行更新:

目前n只支持Mac和Linux

npm i n -gn lts//下载最新版Nodejs

技术特点

  • 1.支持增删改查
  • 2.精确查找支持哈希索引,范围查找支持B+树索引
  • 3.智能缓存,提升QPS性能
  • 4.提供用户管理,cli程序(curl.js)
  • 5.实现串行锁功能
  • 6.灵活的策略配置

代码结构

./database/ : AvenirSQL核心实现

./AvenirSQL.js : AvenirSQL启动程序

./curl.js : cli程序

./dtest.js : 测试程序

./jmeter/ : Jmeter测试用例文件

./db/ : 数据库和表数据

./run.ini : 数据库配置文件

./AvenirSQL/ : Nodejs版的AvenirSQL操作库

以下是项目中使用的Avenir开源组织开发的Node模块:

  1. Nodejs通用方法库 libcu
  2. Avenir日志库 avenir-log
  3. Nodejs对文件某一行的操作库 nth-file
  4. Nodejs多叉树和字典树实现 multiple-tree

支持的数据类型

  1. number

数字,默认长度10

  1. int

其实也是数字 并不是整数,默认长度10

  1. bignumber

不限制长度的数字

  1. varchar

字符型 默认长度20

  1. string

字符型 默认长度20

  1. bigstring

不限制长度的字符

具体技术实现

  • 1.统一的底层错误处理函数,避免函数重复传递参数。
async response(type, client) {        let res = null;        if (typeof type == 'string') {            res = getError(type);            if (!res) {                res = unknown;            }        } else {            let code = type.code;            let data = type.data;            res = getError(code);            if (!res) {                res = unknown;            }            res.data = data;        }        client.write(JSON.stringify(res));        //如果没有配置默认短连接        if( ini.db.keepAlive != true) {            toLog("主动踢掉客户端的连接");            client.end();        }    }

2.数据库结构

数据库:文件夹名

表:数据文件、哈希索引文件、B+树索引文件(聚合索引)

  • 数据文件:

    第一行存放表结构定义,第二行开始第一位为压缩的16进制数,表示该行元素是否为空,后续存储按分隔符排列。

  • 哈希索引:

    对象,key为主键,value为所在文件的行号

  • B+树索引:

    存放B+树的结构

3.连接管理

为了区分不同的用户对数据库进行的不同操作,如同一秒内多个进程进行多次请求,AvenirSQL会生成一个签名,用户登录后需使用此签名进行操作。

4.串行锁

进行操作前加锁,操作完成后解锁,并刷新缓存(select语句不会刷新缓存)

//自动释放锁防止数据库死锁    async releaseLock() {        let now = moment().valueOf();        let releaseLockTime = ini.db.releaseLockTime;        releaseLockTime = releaseLockTime > ini.db.checkLockTime ? releaseLockTime : ini.db.checkLockTime;        for(let key in this.table) {            let tables = this.table[key];            for(let subKey in tables) {                let times = tables[subKey];                if(moment(now).diff(moment(times),'seconds') > releaseLockTime) {                    delete tables[subKey];                    toLog("自动释放了锁 ",tables[subKey]);                }            }        }    }

5.缓存

目前共五类缓存,数据库配置文件缓存和表结构缓存不会刷新,哈希索引、表数据、B+树索引缓存会定时刷新。

6.解析SQL

在此感谢阿里巴巴的sql解析器 node-sqlparser

AvenirSQL独有的sql会先解析,除此之外的SQL会转交给node-sqlparser。

//包含原生SQL和能够被AvenirSQL识别的语句    async parse(sql, sign) {        //先解析AvenirSQL特有的语句 再解析原生SQL        toLog("要解析的 sql为 ", sql);        let raw = this.getArray(sql);        if (raw.length === 0 || !sql) {            throw ('SQL_PARSE_ERROR');        } else {            //AvenirSQL解析出错不报错,转给解析器解析,解析器报错直接throw            try {                await this.parseAvenirSql(raw, sql, sign);            } catch (error) {                //不是内部定义的错误就代表程序处理出错了                toLog('error = ', error);                if (error == SUCCESS || error != 'error') {                    throw (error);                }                //不需要try catch了,底层会抓住错误                let par = this.parseSql(sql);                await this.doSql(par, sign);            }        }    }

7.事务

事务操作在缓存中临时处理,使用串行锁避免并发,不会出现重复读,读未提交等。

rollback操作直接清除缓存,commit操作提交 写文件,释放锁,同样会释放缓存。

超时自动释放锁 避免出现数据库死锁的情况。

8.日志

暂时只支持记录类日志,未实现undolog,binlog等日志,通过Trace类,avenir-log模块实现。

9.cli程序

提供一个与AvenirSQL交互的工具,实现自动重连功能,当发现报错为code 2 签名失效则直接重连。

//检查返回值,超时就自动重发async function checkError(response) {    if (response && response.code == 2) {        //返回值是2代表签名失效 重新登录即可        let data = await safeConnect();        if (data.code === 0) {            data = data.data;        } else {            //说明重新登录也报错了 数据库故障            return {                code: -1,                data: null,            }        }        return {            code: 1,            data        };    } else if (response.code == 0) {        return {            code: 0,            data: null        };    } else {        return {            code: -1,            data: null,        };  //其他错误 其实返回这个没啥用 只需要判断1 就行     }}
  1. select distinct

在返回了结果集之后,根据distinct参数来对数据去重,通过依次插入到多叉树中,一旦发现插入失败则表示数据重复了。

//处理distinct的函数 20210222 考虑用多叉树来处理    async doDistinct(data, columns, tableDetail) {        let mulTree = new MultipleTree();        if (columns.length == 1 && columns[0].expr.column == tableDetail.key) {            //如果是主键的话本身就是distinct了            toLog("优化器跳过distinct");            return data;        }        //遍历结果集 剔除重复的列        for (let i = 0; i < data.length; i++) {            let line = data[i];            if(mulTree.insert(line) === false) {                data.splice(i,1);                i--;            } else {                continue;            }        }        return data;    }
  1. 导入导出

导出即执行select操作,并将SQL语句转换为insert语句。

导入则按分隔符执行SQL语句

导出文件名规范:dump[table || database]_dbname_tableName_YYYYMMDDHHmmss.sql

接口规范

数据库连接使用tcp通信,传输文本为JSON数据格式。

  1. 登录
{    user:"root",    password:"123456"    type:"login"}

返回值:

{    code:0,    message:"success",    data:"ef3d843f26c4e900e9ab4979f324d5571a4cb5f5c011278b36985b2802c828185ad0bdd7e19390cfffe479afe1b09d1c"}
  1. 数据库SQL操作
{    sign:"ef3d843f26c4e900e9ab4979f324d5571a4cb5f5c011278b36985b2802c828185ad0bdd7e19390cfffe479afe1b09d1c",    type:"sql",    data:"select * from test"}

返回值:

{    code : 0,    message:"success",    data:[{        name:"test",        id:"1",    }]}
  1. 数据库事务
{    sign:"ef3d843f26c4e900e9ab4979f324d5571a4cb5f5c011278b36985b2802c828185ad0bdd7e19390cfffe479afe1b09d1c",    id:"transID"            //事务执行流程: begin->sql->commit,begin的时候会给id 后续用id来执行sql    type:"trans",    data:"delete from test",}

返回值:

{    code : 0,    message:"success",}

行数据分隔符

分隔符为∫,故所有的列数据不可以含有∫符号,否则会报错

错误代码表

{    //不该发生的错误    UNKNOWN_CMD: {        code: -1,        message: 'unknown command',    },    SYSTEM_BUSY: {        code: -2,        message: 'system busy',    },    BAD_REQUEST: {        code: -3,        message: 'bad request',     //请求格式无法JSON序列化    },    UNKNOWN_ERROR: {        code: -100,        message: 'unknown error'    },    //成功    AVENIR_SUCCESS: {        code: 0,        message: 'success'    },    //程序级别错误    SQL_PARSE_ERROR: {        code: 1,        message: 'sql parse error'    },    NOT_CONNECTED: {        code: 2,        message: "no connect info, please login first"    },    //大意了没有3 4不吉利 年轻人不讲5的    FILE_NOT_EXIST: {        code: 6,        message: 'file not exist'    },    GEN_SIGN_ERROR: {        code: 7,        message: 'generate sign error',    },    SET_SIGN_EXIT: {        code: 8,        message: 'sign exit',    },    LACK_OF_SIGN: {        code: 9,        message: 'lack of sign, please login first',    },    INVALID_NAME: {        code: 10,        message: "invalid name",    },    BAD_USER: {        code: 11,        message: 'user or password error',    },    PERMISSION_DENIED: {        code: 12,        message: 'permission denied',    },    //以下是数据库类错误    DATABASE_NOT_FOUND: {        code: 1001,        message: 'database not found'    },    DATABASE_EXIST: {        code: 1002,        message: 'database already exist',    },    TABLE_NOT_FOUND: {        code: 1003,        message: 'table not found'    },    TABLE_EXIST: {        code: 1004,        message: 'table already exist'    },    INVALID_SQL_ERROR: {        code: 1005,        message: "invalid sql or sql parse Error"    },    TOO_MANY_COLUMNS: {        code: 1006,        message: 'too many columns',    },    COLUMN_NOT_FOUND: {        code: 1007,        message: 'column not found',    },    COLUMN_NOT_MATCH: {        code: 1008,        message: 'columns and values not match',    },    COLUMN_REPEAT: {        code: 1009,        message: 'columns repeat',    },    COLUMN_NOT_NULL: {        code: 1010,        message: 'some columns cant be null',    },    COLUMN_NOT_CHECK: {        code: 1011,        message: 'column not check error',    },    ONLY_ONE_KEY: {        code: 1012,        message: 'AvenirSQL only support one key',    },    LACK_OF_PRIMARY_KEY: {        code: 1013,        message: 'lack of primary key',    },    KEY_EXIST: {        code: 1014,        message: 'duplicate primary key value',    },    OPER_NO_ROW: {        code: 1015,        message: 'the operated row not found, may be not a error'    },    VALUE_NOT_NUMBER: {        code: 1016,        message: 'compared value is not a number',    },    SQL_TOO_LONG: {        code: 1017,        message: 'sql is too long',    },    SQL_NOT_SUPPORT: {        code: 1018,        message: 'AvenirSQL dont support this sql yet',    },    GET_LOCK_FAILED: {        code: 1019,        message: 'AvenirSQL get lock timeout',    },    RELEASE_LOCK_FAILED: {        code: 1020,        message: 'AvenirSQL release lock failed',    },    USER_EXISTS: {        code: 1021,        message: 'user already exists',    },    USER_NOT_FOUND: {        code: 1022,        message: 'bad user or password',    },    //事务类错误    TRANS_NOT_FOUND: {        code: 1023,        message: 'invalid trans id',    },    TRANS_TIME_OUT: {        code: 1024,        message: 'trans timeout',    },    NO_GROUP_DIS: {        code: 1025,        message: 'AvenirSQL dont support group by yet'    },    COUNT_NO_TABLE: {        code: 1026,        message: `count(colums) cant be [table.column]`    },    WHITE_SPACE_ERROR: {        code: 1027,        message: `sql cant contain AvenirSQL's separator:${this.WHITE_SPACE}`,    },    NOT_SUPPORT_DATA: {        code: 1028,        message: 'AvenirSQL dont support such data type',    },    COLUMN_TYPE_ERROR: {        code: 1029,        message: 'table columns type error(number or string)',    },    COLUMN_OUT_OF_LENGTH: {        code: 1030,        message: 'columns over the length',    },    DUMP_TABLENAME_ERROR: {        code: 1031,        message: 'dumped table name error'    },    DUMP_SQL_ERROR: {        code: 1032,        message: "dump sql error"    },}

SQL规范

  1. 暂不支持join、 like、 group by

  2. show命令

show database 展示目前所有的数据库列表

show table [dbname] 展示该数据库下的所有表名,不输入dbname会去获取默认值

show dump 展示默认的导出目录下所有的文件名

{"code":0,"message":"success","data":["home_def","hot","t"]}
  1. update

不支持不带where条件的全表更新 如update t set a = '1'

  1. distinct

distinc a,b,c 即代表数据库里面a,b,c三个列都重复才会判断重复。

通过多叉树模块multiple-tree来实现

  1. dump

dump table dbname.tableName as select * from tableName where a > 10000

如果不带SQL语句,则导出全表,在配置文件配置是否是覆盖导出和导入

配置文件示例

文件名必须是run.ini,在安装目录下

若找不到此文件,AvenirSQL会自动创建默认的配置文件,内容如下:

  1. execexec [fileName] 执行SQL文件 若只有文件名,则会拼上ini文件的dump.path,否则就直接读取全路径。
[main]ip=127.0.0.1port=44944#数据库的工作目录path=./db#是否输出日志至显示屏 频繁I/O操作将影响性能ifConsoleLog=true[db]#maxConnect=100 目前存在问题#签名的有效期(秒)signValidTime=100#事务自动回滚时间(秒)rollbackTime=10#是否维持长连接keepAlive=false#是否记录debug类的日志debug=false#每个连接的超时时间 毫秒timeOut=10000#数据库表列的上限maxColoums=100#数据库SQL的最大长度maxSqlLength=200#默认的用户数据库user=User#缓存失效的时间(秒)cacheInvalid=200#检查缓存失效的频率(秒)clearCache=500#upda 

鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap