相关文章推荐
纯真的领结  ·  http请求的中的 ...·  7 月前    · 
英勇无比的领带  ·  利用jQuery ...·  1 年前    · 

nodejs redis 客户端 ioredis 源码分析

1 年前 · 来自专栏 nodejs
周末有空,阅读了 nodejs的redis客户端 ioredis 源码, 分享一下。

仓库地址 github.com/luin/ioredis

依赖库

ioredis 依赖了node-redis-parser 用来解析 redis REPS 协议,依赖 redis-commands 获取 redis 支持的所有命令

redis 协议解析库 github.com/NodeRedis/no

redis 命令库 github.com/NodeRedis/re

源码

redis 命令定义

  1. lib/redis/index.ts 文件定义了客户端初始化逻辑
function Redis() {
  this.parseOptions(arguments[0], arguments[1], arguments[2]);
  // 如果 options 中提供了 Connector,则通过 Connector 中的 连接器创建
  if (this.options.Connector) {
    this.connector = new this.options.Connector(this.options);
  } else if (this.options.sentinels) {
    // 如果开启了哨兵模式,则使用哨兵连接器
    const sentinelConnector = new SentinelConnector(this.options);
    sentinelConnector.emitter = this;
    this.connector = sentinelConnector;
  } else {
    // 使用标准连接器
    this.connector = new StandaloneConnector(this.options);
    this.connect().catch(noop);
  1. lib/commander.ts 文件定义了 redis 客户端支持的命令

第三方库 redis-commands 中定义了redis支持的所有命令

const commands = require("redis-commands").list

获取redis支持的所有命令并定义执行函数

commands.forEach(function (commandName) {
  Commander.prototype[commandName] = generateFunction(
    commandName,
    commandName,
    "utf8"
  Commander.prototype[commandName + "Buffer"] = generateFunction(
    commandName + "Buffer",
    commandName,

这里可以看到每个 redis 命令执行的时候调用了 redis.sendCommand() 方法

// 每个 redis 支持的方法都是通过 generateFunction 函数动态生成的
function generateFunction( functionName: string,) {
        return function (...args) {
                return this.sendCommand(
                new Command(commandName, args, options, callback)

redis 客户端启动流程

所在文件 lib/redis/index.ts

  1. 创建 redis 客户端对象
new Redis({ host, port })
  1. 根据 Options 选择连接器
  2. SentinelConnector 如果开启了哨兵模式
  3. StandaloneConnector 标准连接模式(默认)
  4. 调用 connect() 函数,连接到 redis 服务,将 连接成功的 socket 对象保存到 Redis 的 stream 属性。
  5. connect 函数中会创建 connectHandler 连接管理器来管理当前连接的状态变化 connect ->connecting->reply
  6. connectHandler 方法中会创建 DataHandler 来接口 redis 服务器发送回来的结果
  7. 后续的读写操作都通过 stream。

单个命令执行过程分析

调用单个 redis 命令时,最终会调用 command.ts文件中生成的闭包函数

const result = await redis.set('name', 'xixi')

闭包函数中调用都会转化成调用 redis 的 sendCommand 方法

// 每个 redis 支持的方法都是通过 generateFunction 函数动态生成的
function generateFunction( functionName: string,) {
        return function (...args) {
                return this.sendCommand(
                new Command(commandName, args, options, callback)

sendCommand 内部将命令转成字符串或者 buffer,然后通过 connect() 时创建的 Socket 流,将命令发送到 redis服务器

并将当前发送的命令push 到命令执行队列,返回当前执行命令的 promise 对象

(stream || this.stream).write(command.toWritable());
// commandQueue 队列中 Push 进去
this.commandQueue.push({
    command: command,
    stream: stream,
    select: this.condition.select,
return command.promise

DataHandler.ts 订阅 stream (就是 connect() 时创建的 Socket 流),接收到数据调用 RedisParser 解析数据

所在文件 lib/DataHandler.ts

redis.stream.on("data", (data) => {
        parser.execute(data);
// redisParser成功解析数据后,会将数据发送到 returnReply 方法
returnReply(reply: ReplyData) {