Node中怎么使用Redis?下面本篇文章給大家介紹一下Node.js中使用Redis的方法,你會發(fā)現(xiàn)原來這么簡單,希望對大家有所幫助!
node.js極速入門課程:進入學習
之前的文章中我們其實留了兩個可以用redis
優(yōu)化的地方:
- 一個是我們的在做登錄時,通過
JWT
已經(jīng)實現(xiàn)了服務端生成token
以及驗證客戶端發(fā)送的token
信息?!鞠嚓P教程推薦:nodejs視頻教程 、編程視頻】 - 實現(xiàn)對文章點贊功能,采用的是將點贊數(shù)據(jù)直接寫入數(shù)據(jù)庫
JWT token
實現(xiàn)方式, 將基本信息直接放在token
中,以便于分布式系統(tǒng)使用, 但是我們沒有設置有限期(這個是可以實現(xiàn)的),并且服務端無法主動讓token
失效。 而Redis天然支持過期時間,也能實現(xiàn)讓服務端主動使token
過期。
當然并不是說JWT token 不如 redis+token實現(xiàn)方案好, 具體看使用的場景,這里我們并不討論二者孰優(yōu)孰劣,只是提供一種實現(xiàn)方案,讓大家知道如何實現(xiàn)。
1. 認識redis
對于前端的小伙伴來說,Redis可能相對比較陌生,首先認識一下
Redis是什么
Redis是一個開源(BSD許可)的,基于內(nèi)存的數(shù)據(jù)結構存儲系統(tǒng),它可以用作數(shù)據(jù)庫、緩存和消息中間件,是現(xiàn)在最受歡迎的 NoSQL 數(shù)據(jù)庫之一。
其具備如下特性:
- 速度快
- 單節(jié)點讀110000次/s,寫81000次/s
- 基于內(nèi)存運行,性能高效
- 用 C 語言實現(xiàn),離操作系統(tǒng)更近
- 持久化
- 數(shù)據(jù)的更新將異步地保存到硬盤(RDB 和 AOF
- 多種數(shù)據(jù)結構
- 不僅僅支持簡單的 key-value 類型數(shù)據(jù)
- 還支持:字符串、hash、列表、集合、有序集合
- 支持多種編程語言等等
Redis 典型使用場景
緩存
緩存可以說是Redis最常用的功能之一了, 合理的緩存不僅可以加快訪問的速度,也可以減少后端數(shù)據(jù)庫的壓力。
排行系統(tǒng)
利用Redis的列表和有序集合的特點,可以制作排行榜系統(tǒng),而排行榜系統(tǒng)目前在商城類、新聞類、博客類等等,都是比不可缺的。
計數(shù)器應用
計數(shù)器的應用基本和排行榜系統(tǒng)一樣,都是多數(shù)網(wǎng)站的普遍需求,如視頻網(wǎng)站的播放計數(shù),電商網(wǎng)站的瀏覽數(shù)等等,但這些數(shù)量一般比較龐大,如果存到關系型數(shù)據(jù)庫,對MySQL或者其他關系型數(shù)據(jù)庫的挑戰(zhàn)還是很大的,而Redis基本可以說是天然支持計數(shù)器應用。
(視頻直播)消息彈幕
直播間的在線用戶列表,禮物排行榜,彈幕消息等信息,都適合使用Redis中的SortedSet結構進行存儲。
例如彈幕消息,可使用ZREVRANGEBYSCORE
排序返回,在Redis5.0中,新增了zpopmax
,zpopmin
命令,更加方便消息處理。
Redis的應用場景遠不止這些,Redis對傳統(tǒng)磁盤數(shù)據(jù)庫是一個重要的補充,是支持高并發(fā)訪問的互聯(lián)網(wǎng)應用必不可少的基礎服務之一。
紙上談兵終覺淺,必須實戰(zhàn)一波~
Redis的安裝和簡單使用,我這里就不一一介紹了,這里貼上我之前寫的兩篇文章:
- Redis 安裝
- Redis入門篇-基礎使用
可以快速的安裝、了解Redis數(shù)據(jù)類型以及常用的命令。
可視化客戶端
在Windows下使用 RedisClient, 在mac下可以使用Redis Desktop Manager
RedisClient下載鏈接:https://github.com/caoxinyu/RedisClient
下載后直接雙擊redisclient-win32.x86.2.0.exe
文件運行即可
啟動后, 點擊server -> add
連接后就可以看到總體情況了:
與SQL型數(shù)據(jù)不同,redis沒有提供新建數(shù)據(jù)庫的操作,因為它自帶了16(0-15)個數(shù)據(jù)庫(默認使用0庫)。在同一個庫中,key是唯一存在的、不允許重復的,它就像一把“密鑰”,只能打開一把“鎖”。鍵值存儲的本質(zhì)就是使用key來標識value,當想要檢索value時,必須使用與value對應的key進行查找.
Redis認識作為文章前置條件,到這里及結束了, 接下來進入正題~
本文主要使用Redis實現(xiàn)緩存功能。
2. 在Nest.js中使用
版本情況:
庫 | 版本 |
---|---|
Nest.js | V8.1.2 |
項目是基于Nest.js 8.x
版本,與Nest.js 9.x
版本使用有所不同, 后面的文章專門整理了兩個版本使用不同點的說明, 以及如何從V8
升級到V9
, 這里就不過多討論。
首先,我們在Nest.js項目中連接Redis, 連接Redis需要的參數(shù):
REDIS_HOST:Redis 域名 REDIS_PORT:Redis 端口號 REDIS_DB: Redis 數(shù)據(jù)庫 REDIS_PASSPORT:Redis 設置的密碼
將參數(shù)寫入.env
與.env.prod
配置文件中:
使用Nest官方推薦的方法,只需要簡單的3個步驟:
1、引入依賴文件
npm install cache-manager --save npm install cache-manager-redis-store --save npm install @types/cache-manager -D
Nest
為各種緩存存儲提供統(tǒng)一的API,內(nèi)置的是內(nèi)存中的數(shù)據(jù)存儲,但是也可使用 cache-manager
來使用其他方案, 比如使用Redis
來緩存。
為了啟用緩存, 導入ConfigModule
, 并調(diào)用register()
或者registerAsync()
傳入響應的配置參數(shù)。
2、創(chuàng)建module文件src/db/redis-cache.module.ts
, 實現(xiàn)如下:
import { ConfigModule, ConfigService } from '@nestjs/config'; import { RedisCacheService } from './redis-cache.service'; import { CacheModule, Module, Global } from '@nestjs/common'; import * as redisStore from 'cache-manager-redis-store'; @Module({ imports: [ CacheModule.registerAsync({ isGlobal: true, imports: [ConfigModule], inject: [ConfigService], useFactory: async (configService: ConfigService) => { return { store: redisStore, host: configService.get('REDIS_HOST'), port: configService.get('REDIS_PORT'), db: 0, //目標庫, auth_pass: configService.get('REDIS_PASSPORT') // 密碼,沒有可以不寫 }; }, }), ], providers: [RedisCacheService], exports: [RedisCacheService], }) export class RedisCacheModule {}
CacheModule
的registerAsync
方法采用 Redis Store 配置進行通信store
屬性值redisStore
,表示'cache-manager-redis-store' 庫isGlobal
屬性設置為true
來將其聲明為全局模塊,當我們將RedisCacheModule
在AppModule
中導入時, 其他模塊就可以直接使用,不需要再次導入- 由于Redis 信息寫在配置文件中,所以采用
registerAsync()
方法來處理異步數(shù)據(jù),如果是靜態(tài)數(shù)據(jù), 可以使用register
3、新建redis-cache.service.ts
文件, 在service實現(xiàn)緩存的讀寫
import { Injectable, Inject, CACHE_MANAGER } from '@nestjs/common'; import { Cache } from 'cache-manager'; @Injectable() export class RedisCacheService { constructor( @Inject(CACHE_MANAGER) private cacheManager: Cache, ) {} cacheSet(key: string, value: string, ttl: number) { this.cacheManager.set(key, value, { ttl }, (err) => { if (err) throw err; }); } async cacheGet(key: string): Promise<any> { return this.cacheManager.get(key); } }
接下來,在app.module.ts
中導入RedisCacheModule
即可。
調(diào)整 token 簽發(fā)及驗證流程
我們借助redis來實現(xiàn)token過期處理、token自動續(xù)期、以及用戶唯一登錄。
- 過期處理:把用戶信息及token放進redis,并設置過期時間
- token自動續(xù)期:token的過期時間為30分鐘,如果在這30分鐘內(nèi)沒有操作,則重新登錄,如果30分鐘內(nèi)有操作,就給token自動續(xù)一個新的時間,防止使用時掉線。
- 戶唯一登錄:相同的賬號,不同電腦登錄,先登錄的用戶會被后登錄的擠下線
token 過期處理
在登錄時,將jwt生成的token,存入redis,并設置有效期為30分鐘。存入redis的key由用戶信息組成, value是token值。
// auth.service.ts async login(user: Partial<User>) { const token = this.createToken({ id: user.id, username: user.username, role: user.role, }); + await this.redisCacheService.cacheSet( + `${user.id}&${user.username}&${user.role}`, + token, + 1800, + ); return { token }; }
在驗證token時, 從redis中取token,如果取不到token,可能是token已過期。
// jwt.strategy.ts + import { RedisCacheService } from './../core/db/redis-cache.service'; export class JwtStrategy extends PassportStrategy(Strategy) { constructor( @InjectRepository(User) private readonly userRepository: Repository<User>, private readonly authService: AuthService, private readonly configService: ConfigService, + private readonly redisCacheService: RedisCacheService, ) { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), secretOrKey: configService.get('SECRET'), + passReqToCallback: true, } as StrategyOptions); } async validate(req, user: User) { + const token = ExtractJwt.fromAuthHeaderAsBearerToken()(req); + const cacheToken = await this.redisCacheService.cacheGet( + `${user.id}&${user.username}&${user.role}`, + ); + if (!cacheToken) { + throw new UnauthorizedException('token 已過期'); + } const existUser = await this.authService.getUser(user); if (!existUser) { throw new UnauthorizedException('token不正確'); } return existUser; } }
用戶唯一登錄
當用戶登錄時,每次簽發(fā)的新的token,會覆蓋之前的token, 判斷redis中的token與請求傳入的token是否相同, 不相同時, 可能是其他地方已登錄, 提示token錯誤。
// jwt.strategy.ts async validate(req, user: User) { const token = ExtractJwt.fromAuthHeaderAsBearerToken()(req); const cacheToken = await this.redisCacheService.cacheGet( `${user.id}&${user.username}&${user.role}`, ); if (!cacheToken) { throw new UnauthorizedException('token 已過期'); } + if (token != cacheToken) { + throw new UnauthorizedException('token不正確'); + } const existUser = await this.authService.getUser(user); if (!existUser) { throw new UnauthorizedException('token不正確'); } return existUser; }
token自動續(xù)期
實現(xiàn)方案有多種,可以后臺jwt生成access_token
(jwt有效期30分鐘)和refresh_token
, refresh_token
有效期比access_token
有效期長,客戶端緩存此兩種token, 當access_token
過期時, 客戶端再攜帶refresh_token
獲取新的access_token
。 這種方案需要接口調(diào)用的開發(fā)人員配合。
我這里主要介紹一下,純后端實現(xiàn)的token自動續(xù)期
實現(xiàn)流程:
- ①:jwt生成token時,有效期設置為用不過期
- ②:redis 緩存token時設置有效期30分鐘
- ③:用戶攜帶token請求時, 如果key存在,且value相同, 則重新設置有效期為30分鐘
設置jwt生成的token, 用不過期, 這部分代碼是在auth.module.ts
文件中, 不了解的可以看文章 Nest.js 實戰(zhàn)系列第二篇-實現(xiàn)注冊、掃碼登陸、jwt認證
// auth.module.ts const jwtModule = JwtModule.registerAsync({ inject: [ConfigService], useFactory: async (configService: ConfigService) => { return { secret: configService.get('SECRET', 'test123456'), - signOptions: { expiresIn: '4h' }, // 取消有效期設置 }; }, });
然后再token認證通過后,重新設置過期時間, 因為使用的cache-manager
沒有通過直接更新有效期方法,通過重新設置來實現(xiàn):
// jwt.strategy.ts async validate(req, user: User) { const token = ExtractJwt.fromAuthHeaderAsBearerToken()(req); const cacheToken = await this.redisCacheService.cacheGet( `${user.id}&${user.username}&${user.role}`, ); if (!cacheToken) { throw new UnauthorizedException('token 已過期'); } if (token != cacheToken) { throw new UnauthorizedException('token不正確'); } const existUser = await this.authService.getUser(user); if (!existUser) { throw new UnauthorizedException('token不正確'); } + this.redisCacheService.cacheSet( + `${user.id}&${user.username}&${user.role}`, + token, + 1800, + ); return existUser; }
到此,在Nest中實現(xiàn)token過期處理、token自動續(xù)期、以及用戶唯一登錄都完成了, 退出登錄時移除token比較簡單就不在這里一一上代碼了。
在Nest中除了使用官方推薦的這種方式外, 還可以使用nestjs-redis
來實現(xiàn),如果你存token時, 希望存hash
結構,使用cache-manager-redis-store
時,會發(fā)現(xiàn)沒有提供hash
值存取放方法(需要花點心思去發(fā)現(xiàn))。
注意:如果使用
nest-redis
來實現(xiàn)redis緩存, 在Nest.js 8 版本下會報錯, 小伙伴們可以使用@chenjm/nestjs-redis
來代替, 或者參考 issue上的解決方案:Nest 8 + redis bug。
總結
源碼地址:https://github.com/koala-coding/nest-blog