Redis 驱动
背景闲聊
本插件最早起于nestjs-redis库在 NestJS 8.x
版本下不好使,同时又迟迟等不到更新,其他的解决方案下载量又不够大的情况下迫不得已自己写了一套,当时主要参考@liaoliaots/nestjs-redis这个包。其实在当时我是没有勇气写一个这么基础、这么通用的包的,因为这种属于生态的基石类的库肯定是有成熟稳定的解决方案的,自研又无法投入大量的时间精力来慢慢打磨。最终还是咬牙写了下来的主要原因是在当时@liaoliaots/nestjs-redis这个包的下载了还没有上来,不敢直接使用。现在看来这个包的下载量已经上来了,但是我们自研的@iot9x.com/nestjs-redis也走出了自己的特色,就继续在自研的道路上走下去吧。
设计说明
自研的道路肯定是以满足自身业务发展为前提的,目前我们的 redis
还没有到上集群的规模,因此此包专注于单机 Redis
。目前的 nestjs-redis
解决方案都是基于底层ioredis或redis的,通过依赖注入得到一个 ioredis
或 redis
的客户端,接下来的各种操作都是 ioredis/redis
的客户端提供的接口了。
关于 ioredis
和 redis
的具体区别不在本文档的讨论范围内,有兴趣可以自行研究。
本插件选择将 ioredis
作为底层驱动。
特色功能
RedisService
由于市场上的解决方案都是通过依赖注入得到一个 ioredis/redis
的客户端,但是这个客户端的操作返回都是比较原始的,基本上和使用 redis-cli
的效果一致。那就会遇到一个不够友好的情况,就是我们得到的数据很多情况下要做进一步的处理。
例如,通过 set
方法往数据库中插入数据,如果成功,返回的是一个 Promise<"OK">
,那要判断插入是否成功了,就要通过判断 result === "OK"
来实现了。众所周知,在代码中写死字符串不是一个好习惯,我们更希望得到一个 boolean
类型的值来代表执行成功与否。
还有,有的时候会将 Date
、 Number
、 Object
、 Array
类型的数据插入数据库,在某些环境下会直接将其序列化为 string
后作为 value
插入数据库。
对于 Object
、 Array
使用更加具体的散列表、集合、链表等方式无疑是更合适的,这里考虑的是“偷懒”的情况。
这种情况下,我们通过 get
得到的数据还要继续通过 JSON.parse(data)
来转换回来,无疑增加了代码量。我们更希望借助 TS
的泛型来实现这种指定类型的方式,如下所示:
const data = await this.redisService.get<Date>("redis_key");
基于以上几点,我封装了 RedisService
,并通过 @InjectRedisService()
装饰器来注入,可以实现更便捷的数据库操作方式。
锁
由于 NodeJS
是单主线程的,我们在线上为了利用多核性能与集群,一般会部署多个实例。但是部署多个实例的时候就会遇到资源竞争问题,最常见的就是定时计划任务。例如:每天凌晨2点执行不重要的日志清理,这个时候如若不加限制,那就会造成每个实例都执行一次计划 任务。为了这种简单的资源竞争问题,基于 Redis
的 SetNX
实现了一个简易的锁,具体详见文档——锁