如果在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次之類的,否則並沒有什麼差異。

广告
将在 10 秒后关闭
bannerAds