1.0.8 • Published 3 years ago

dbcached v1.0.8

Weekly downloads
-
License
MIT
Repository
github
Last release
3 years ago

介绍

这是一个基于mongoose和redis的数据库缓存方案dbcached, 主要目的为减少查询直接访问数据库的次数. 只针对单表系统, 不处理表关联, 不处理字段选取. 比较适合记录数据内容不是大文章类型, restful方案的项目. 链接地址https://github.com/windsome/dbcached

应用程序调用api时,首先从redis缓存中获取数据,未获取到向数据库获取,并更新到redis缓存,通过api返回. 此程序库有两套接口, 1. 直接调用mongoose的数据库方法. 2. 使用redis缓存.

实现方案

createDbOps返回的是直接对数据库操作的接口. createCachedOps返回的是带缓存的接口,主要优化的是查询缓存方面. redis缓存中,存在3类键用来加速.

  • d键, 单条记录的数据, 将数据转成字符串, (有性能损耗)
    • key_d = generateRedisKey(model, 'd', item._id), 如: "device##d##612edab50a3d97e9130af795"
    • 设置 $r.setexAsync(key_d, EX_SECONDS, JSON.stringify(item))
  • s键, 记录查询结果id列表的键
    • key_s = generateRedisKey(model, 's', where, sort), 如: "device##s##{\"name\":\"$regex-/Jack/i\"}##"
    • 设置 $r.zaddAsync(key_s, ...argsArray);
    • 可以根据返回的index作为score值,记录分页数据.
  • c键, 记录查询结果的个数. db的count查询比较费时间, 所以用了matcher算法去自行增减.
    • key_c = generateRedisKey(model, 'c', where, sort)
    • 设置 $r.setexAsync(key_c, EX_SECONDS, data_c)

主要优化逻辑在cached/index的findCreator函数中, 主要过程就是判断该查询是否存在,数据是否满足,满足直接返回cache, 否则进行数据查询, 并更新到cache中. 创建/更新/删除, 通过matcher判断对哪些查询产生影响, 并更新相应的查询.

具体用例请参考test目录. test/mockdata.js下的cfg是配置,修改成自己的. schemas是测试表结构. run-cache-find.js是测试文件, 执行node -r esm run-cache-find.js看结果. 注意: 需要先运行yarn compile编译到lib目录. 该测试引用的是lib目录文件.

安装使用

## 安装
yarn add dbcache

## db方法
import { createDbOps } from 'dbcache'

import schemas from './test/schemas';
const dbops = createDbOps('mongodb://admin:admin@localhost:27017/testcache?authSource=admin', schemas);

let items = await dbops.find('device', {where:{name:'王老板'}});
console.log('items', items);

await dbops.destroy(); //释放.


## redis方法
import { createDbOps, createCachedOps } from 'dbcache'

import schemas from './test/schemas';
const dbops = createDbOps('mongodb://admin:admin@localhost:27017/testcache?authSource=admin', schemas);
let ops = await createCachedOps({
  url: 'redis://localhost:6379/1',
  dbops,
});

let items = await ops.find('device', {where:{name:'王老板'}});
console.log('items', items);

await ops.destroy() // 释放
await dbops.destroy() // 释放

接口

export interface DbOps {
  createOne: (model: string, data: JsonData) => Promise<JsonData | null>;
  createMany: (model: string, items: JsonData[]) => Promise<JsonData[] | null>;
  find: (model: string, options: QueryOptions) => Promise<JsonData[]>;
  updateOne: (
    model: string,
    where: JsonData,
    data: JsonData,
    options?: JsonData,
  ) => Promise<JsonData | null>;
  updateMany: (
    model: string,
    where: JsonData,
    data: JsonData,
    options?: JsonData,
  ) => Promise<JsonData[]>;
  deleteOne: (
    model: string,
    where: JsonData,
    options?: JsonData,
  ) => Promise<JsonData | null>;
  deleteMany: (
    model: string,
    where: JsonData,
    options?: JsonData,
  ) => Promise<number>;
  count: (model: string, options: QueryOptions) => Promise<number>;

  deleteOneById: (model: string, id: string) => Promise<JsonData | null>; //deleteOne
  updateOneById: (
    model: string,
    id: string,
    data: JsonData,
    options?: JsonData,
  ) => Promise<JsonData | null>; // updateOne
  findOne: (
    model: string,
    where: JsonData,
    options?: JsonData,
  ) => Promise<JsonData>; // retrieve
  findOneById: (
    model: string,
    id: string,
    options?: JsonData,
  ) => Promise<JsonData>; // findOne

  destroy: () => Promise<boolean>;
}

存在的问题

  1. db返回数据做了过多费性能处理.
  • 从db接口返回的数据都做了 JSON.parse(JSON.stringify(item/items))处理,使得返回的ObjectId都转成了字符串, 这会损失性能, 未来需要优化.
  • 当前这么做的原因是,无法确切知道ObjectId的类型,用typeof()获得的是object object,无法与其他的区分, 导致在做matcher时, 无法匹配此类型.
  • 未来找到界定ObjectId类型的方法后,可以参考regexp正则表达式的处理方法,进行正反转换.
  1. 批量更新,删除数据时,检测哪些查询过期了,目前是将每个查询与数据运算,判断数据是否满足查询. 未来,可以直接将查询条件求交集,如果有交集则表示该查询应该更新,count也需要重新查询更新.
  2. 目前未防止缓存数据直接穿透到数据库. 当某个查询过期时, 目前直接将该查询的s键删除无效, 未来可以将该键插入待更新队列, 由更新任务异步去更新.
  3. 分页查询, 部分页面过期了, 导致数据穿透, 是否有优化空间?
  4. 数据库操作应该转成队列执行, 相连接的相同查询且中间没有更新/创建/删除命令的可以合并成一个,减少查询次数. 接口则等待数据返回.

注意的问题

如果使用此库的微服务有多片同时运行,或者多个微服务操作相同的数据库,则涉及到redis-key重入问题. 建议使用相同数据库的服务进行整理,将数据库操作部分抽象出来,统一整理到数据存取服务. 该服务,支持事务操作(数据库事务转为程序逻辑事务),原子操作等.

目的是优化查询的速度,相同的查询,不必再走数据库. 如果业务逻辑比较复杂, 是否还有必要去做? 是不是再往下细究, 发现就他妈是数据库的功能了? 变成自己实现数据库中缓存功能了? 还有必要去优化吗?

还是有必要的, 这里要把查询和增删改区分开. 考虑数据调用场景: 1. 涉及订单,交易等的调用,一般希望用事务解决,保证原子性,正确性. 比如, 客户扣钱给商家. 这个过程中查询和更新交叉调用. 2. 看新闻, 一般只有查询. 3. 点赞,评论,对文章点赞,评论数有影响,一般慢一点也不要紧.

注意事项

  1. 用node运行测试程序时,可能遇到不能识别import的问题. 需要yarn add -D esm, 然后运行node -r esm <js文件>.

  2. 当需要npm publish时, 且机器上默认使用阿里源, 需要指定源推送到npmjs上. npm publish --registry https://registry.npmjs.org

  3. 发布或更新也可以遵循如下操作

  • nrm use npm 切换到npm源
  • 如果更新:npm version major升大版本,或者npm version minor/patch为升中小版本.
  • 发布 npm publish
  • npm好像1.0.0以上的版本才可以用
  1. 可以用nrm源管理工具,方便切换源. 见https://github.com/Pana/nrm
1.0.8

3 years ago

1.0.7

3 years ago

1.0.6

3 years ago

1.0.5

3 years ago

1.0.4

3 years ago

1.0.3

3 years ago

1.0.2

3 years ago

1.0.1

3 years ago

1.0.0

3 years ago