如果在Node.js中使用Redis,建议使用ioredis
简而言之
-
- まだ node_redis を使ってる人が多いけど標準で Promise 対応してなくてレガシー
ioredis
使い方はほぼいっしょ
標準で Promise 対応してるので async/await でそのまま書ける
Cluster, Sentinel, LuaScripting 含めたフル機能が使える
?? 卢因先生是一位开发者
-
- 元 Alibaba のエンジニア
- Redis の GUI ツール Medis も開発
? 如何使用
$ yarn install ioredis
异步/等待
当然可以使用传统的回调方式来编写,但一旦掌握了async/await,就不想再回到回调函数了。
const Redis = require('ioredis');
(async () => {
const redis = new Redis();
const pong = await redis.ping();
console.log(pong); // => PONG
redis.disconnect();
})();
顺便提一下,如果在 node_redis 中使用 async/await 的话。
来源于公式。不使用promisify真的很麻烦。真的很麻烦(因为这是一件重要的事情)。
const {promisify} = require('util');
const redis = require("redis");
const client = redis.createClient();
const getAsync = promisify(client.get).bind(client);
(async () => {
const res = await getAsync('foo');
console.log(res);
})();
? 如果在TypeScript中使用
$ yarn install ioredis
$ yarn install -D @types/ioredis
我认为为了获得类型信息,应该将其导入为 IORedis,这样更容易理解。
import * as IORedis from 'ioredis';
export class Sample {
private readonly redis: IORedis.Redis;
constructor(options?: IORedis.RedisOptions) {
this.redis = new IORedis(options);
}
async echo(message: string): string {
return await this.redis.echo(message);
}
}
? 尝试实施排名系统
使用 ioredis 进行每日排行榜的实现示例。
import * as IORedis from 'ioredis';
import {DateTime} from 'luxon';
import {RankingUser} from './ranking-user';
import {UserDto} from './user-dto';
import {RankingUtil} from './ranking-util';
export class DailyRanking {
private readonly redis: IORedis.Redis;
constructor(options?: IORedis.RedisOptions) {
this.redis = new IORedis(options);
}
// e.g. RANKING_DAILY_20181016
static createKey(): string {
return DateTime.utc().toFormat("'RANKING_DAILY_'yyyyMMdd");
}
update(user: RankingUser, score: number): void {
const key = DailyRanking.createKey();
const dto: UserDto = {name: user.name, grade: user.grade}; // ignore userId, score
const json = JSON.stringify(dto);
this.redis.zadd(key, `${score}`, `${user.userId}:${json}`);
}
async listByHighScore(limit: number): Promise<RankingUser[]> {
const key = DailyRanking.createKey();
const max = '+inf';
const min = '-inf';
const args = ['LIMIT', '0', `${limit}`, 'WITHSCORES'];
const result = await this.redis.zrevrangebyscore(key, max, min, ...args);
const users: RankingUser[] = [];
for (let i = 0, len = result.length; i < len; i++) {
if (i % 2 === 1) {
const member = result[i - 1];
const score = result[i];
const user = RankingUtil.createRankingUser(member, score);
users.push(user)
}
}
return users;
}
async getByUserId(userId: number): Promise<RankingUser> {
const key = DailyRanking.createKey();
const args = ['MATCH', `${userId}:*`];
const [cursor, result] = await this.redis.zscan(key, 0, ...args);
const [member, score] = result;
return RankingUtil.createRankingUser(member, score);
}
close(): void {
this.redis.disconnect();
}
}
在 Redis 的 SortedSet 中,可以使用键来附带存储成员(member)和分数(score),并根据分数进行排名。
通常情况下,只保存用户ID在成员中,然后从RDB获取最新的用户信息,但上述代码将用户信息作为JSON字符串完全存储在Redis中。如果数据量较小且不太关注最新性,这种方式就足够了。
通过将成员的前缀设置为:,可以使用zscan根据用户ID进行获取。
这里可以找到完整的源代码 => GitHub
✏️ ES6及以上版本中,将数字转换为字符串
顺便提一句,在 TypeScript 中,由于没有隐式类型转换,所以无论是 member 还是 score 都必须明确地传入字符串,但对于数字字符串转换来说,使用模板字符串是最快的方法。
const s1 = `${score}`; // Fast!!
const s2 = score + ''; // ↑
const s3 = String(score); // ↓
const s4 = score.toString(); // Slow
嗯,在文章中,除非轉100,000次之類的,否則並沒有什麼差異。