前言
有时候我们的数据库压力很大,为了减少数据库压力,除了分表、分库等操作,还有一种比较常见的操作就是引用redis
redis 和我们的关系型数据库有点不一样,我们的重要数据一般仍然要存放到数据库,一般访问量大,并且对于实时性、安全性要求不是那么严格(即数据延迟,丢失都不会存在实际隐患,最多体验稍微差点,但对于整体提升效果来说是值得的)
特点
:redis 数据主要以内存为主,内存的高效率,这也奠定了其性能优势,并且庞大并发下,使用多组 redis 等方式提升吞吐量;且这不代表其不会储存到本地,当一定次数、时间后,会自动更新本地缓存(更新更新过于频繁会丢失一些性能优势),因此,如果需要 redis 保存到本地的效果,需要最后部分数据更新丢失的接受能力(也许可以调整后自动保存本地)
定位
: redis 大多数被是用来分担数据库压力的,毕竟服务器可以不断增加,相当于没有瓶颈,而数据库并不是,即使有分库等优化操作,终归是远不能和服务器比的,正因为如此 redis 也被叫数据库
使用情况
:除了特殊场景,我们也基本上不会将 redis 直接给前端用户访问,而是也经过一或多台服务器连接访问 redis,多台服务器访问多台redis都是有可能的,因此,一台 redis 可能对接多台服务器,一台服务器也可能连接多个 redis(可能根据不同功能模块连接不同 redis)
ps
:如果看到什么功能就一味依靠 redis,有时不仅仅会增加自己和运维的工作,甚至还会降低性能,为什么这么说,例如:在一个小项目中,只有一台服务器情况,自己语言库里面的哈希表性能就一定比 redis 效率低么,那可不一定哈,很不仅不会,由于少了一步交互,不仅性能高,还节省硬件成本,用一个东西一定是看情况需要才去用
安装 redis 到主机
安装 redis
brew install redis
前台启动redis,control + c 取消运行,也就意味着关闭终端停止运行
redis-server
使 redis 在后端也能够运行,启动命令,如果没安装服务,会先安装服务
brew services start redis
查看 redis 服务的运行信息
brew services info redis
停止 redis 的运行,如果不主动停止将会一直运行
brew services stop redis
可以通过 vscode
的 database client
连接 redis,输入 ip 端口号就可以连接看情况了(设置了密码后就输入密码就行了),就可以看到 redis 数据库的情况了
应用到项目
我们在后端连接 redis 需要用到相关库,因此需要导入 redis 库,javascript的redis文档
yarn add redis
(也可以导入 ioredis,ioredis看着也更好用一些,不过最后一个案例有,就不多介绍了哈)
yarn add ioredis
配置我们的 redis 服务文件,这里只是简单介绍
一般使用 set、get 等就可以了,如果真的需要一个组合大对象的话,可以考虑hSet、gGet
ps
:下面几种 redis
的 provider 和三方的使用,如果有不明白的,我们的 module模块 里面有介绍的(第一版内容很少,后面改动增加了不少内容哈)
创建 service 使用
里面的一些函数主要作为使用案例介绍,实际可以直接使用 service.client 即可,多个模块导入,可以将 client 搞成单例,下面主要是用来讲解方法了,实际不一定这么使用哈
import { envConfig } from 'src/app.config';
import { createClient, RedisClientType } from '@redis/client';
import { Injectable } from '@nestjs/common';
//虽然这么写,但是实际不这么用
@Injectable()
export class RedisService {
client: RedisClientType; //开放的,外面也可以通过这个参数直接使用
constructor() {
this.client = createClient({
socket: {
host: envConfig.REDIS_HOST,
port: Number(envConfig.REDIS_PORT),
},
// url: `redis://alice:foobared@awesome.redis.server:6380`,
// username: '',
// password: '',
//默认就是这个,一般一个项目就用一个,可能多个项目用一台redis服务器,看情况来就行
// database: 0,
})
this.client.connect()
}
//下面的以介绍案例为主,实际上推荐直接使用 client
//下面的操作可以封装使用,但肯定比直接使用上面的效率低一些,自己看情况
//根据 hash 设置 value值
async set(key: string, val: string) {
await this.client.set(key, val);
}
//根据 hash 获取内容
async get(key: string) {
return await this.client.get(key);
}
//根据某个hash 删除
async delete(key: string) {
await this.client.del(key);
}
//根据 hash 设置 一个大对象的某个 key 和 值,key 和 value
async hSet(hash: string, key: string, val: string) {
await this.client.hSet(hash, key, val);
}
//根据 hash 获取对应的大对象 或 某个key对应的值
async hGet(hash: string, key?: string) {
if (key) {
return await this.client.hGet(hash, key);
}
return await this.client.hGetAll(hash)
}
//获取某个大对象的所有values
async hGetVals(hash: string) {
return await this.client.hVals(hash);
}
//删除hash对应的对象某个key、对象
async hDelete(hash: string, key?: string) {
if (key) {
return await this.client.hDel(hash, key);
}
return await this.client.del(hash);
}
}
上面的 service 使用起来很方便,符合习惯,哪个模块用到了,直接导入即可,但是似乎也没那么好用,service感觉很多余哈
useFactory版本(provide + inject)(改进后推荐)
也是要导入 redis,然后在指定模块的 module 的 providers 中添加下面操作即可直接使用
//添加标识,都写到一个文件,封装一个 InjectMyRedis 减少代码
export const redis_provide_identifier = 'REDIS_CLIENT'
export const InjectMyRedis = () => Inject(redis_provide_identifier);
providers: [
{
provide: 'redis_provide_identifier',
async useFactory() {
const client = createClient({
socket: {
host: envConfig.REDIS_HOST,
port: Number(envConfig.REDIS_PORT),
},
});
await client.connect();
return client;
},
},
],
在本模块其他文件中,直接使用 @Inject 引入 redis,即可直接使用
export class XXXController {
constructor(
@Inject('redis_provide_identifier') private readonly redisClient: RedisClientType,
) {}
嫌弃使用标识麻烦,或者报错,可以声明换一个类型,或者创建一个新的装饰器,创建一个装饰器文件(例如:redis.decorator.ts),写到里面即可
import { Inject } from "@nestjs/common";
export const redis_provide_identifier = 'REDIS_CLIENT'
export const InjectMyRedis = () => Inject(redis_provide_identifier);
ps
:这个写的代码也没比第一种少多少,只有一个模块使用还行,多个模块,看看怎么引入吧,改进一下挺推荐的
改进方案
(前面模块讲了全局,自己再按照上面改进装饰器,基本上和后面的三方差不多了)
//redis.decorator.ts
import { Inject } from "@nestjs/common";
export const redis_provide_identifier = 'REDIS_CLIENT'
export const InjectMyRedis = () => Inject(redis_provide_identifier);
//redis.module.ts
type ConfigType = {
...
}
@Global()
@Module({})
export class RedisModule {
//这种配置我们一般在不同模块配置,然后就可以直接在导入的模块使用了
static register(config: ConfigType): DynamicModule {
return {
module: RedisModule,
providers: [
{
provide: redis_provide_identifier,
async useFactory() {
const client = createClient({
socket: {
host: envConfig.REDIS_HOST,
port: Number(envConfig.REDIS_PORT),
},
});
await client.connect();
return client;
},
}
],
exports: [ConfigService],
};
}
}
使用案例
//app.module.ts中动态注入即可
imports: [
RedisModule.register({})
]
//使用,其他模块不用导入模块直接使用
export class XXXController {
constructor(
@InjectMyRedis private readonly redisClient: RedisClientType,
) {}
使用三方组件库(@liaoliaots/nestjs-redis、ioredis)-推荐
这个除了使用方便,还多了一些文档等功能,可以直接入手使用,嫌库多没必要,可以用用第二种改进方案
另外这里发现还引入了 ioredis,个人也比较推荐这个哈,可以使用这个库 ioredis
,看着感觉它比第一个案例的 redis 库看着优秀点,实际可能也差不多哈
yarn add @liaoliaots/nestjs-redis ioredis
然后和数据库一样,直接在 app.module 动态导入 redis module,想知道别人怎么封装的模块导入,可以看源码,实际上官网的模块的案例代码应该足够理解了
imports: [
RedisModule.forRoot({
//closeClient: true, //redis挂了,nestjs也挂掉
//readyLog: true, 在客户端展示日志
config: {
host: envConfig.REDIS_HOST,
port: Number(envConfig.REDIS_PORT),
db: Number(envConfig.REDIS_DB),
},
}),
]
上面是全局导入,之后直接注入方式获取我们的 redis 对象即可,另外可以看到 ioredis 确实比 redis 的文档多一些,用着舒服一些哈,也更加人性化
import { Redis } from 'ioredis';
@InjectRedis() private readonly redis: Redis,
this.redis.set
this.redis.get
this.redis.hset
this.redis.hget
this.redis.hgetBuffer
//假如需要过期,过期后会自动删除,例如:某些公告、token
this.redis.set("key", "data", "EX", 60); //假如需要设置过期
......
最后
redis 使用就是如此简单,实际上其还有不少功能,自己也不乏尝试一下,看了别人的模块化搞得有点舒服的,也可以自己参考文档尝试,并不复杂,不管是多路还是单路,自己看着用即可