1
The last reading of the source code for redis involved ordinary clients, and this time I analyzed the client source code in cluster mode.
The specific source code path is in the lib/cluster directory.

Cluster instantiation

Cluster , we use the 060b9a5521ddc5 model to be instantiated at the very beginning. The code called here is located at lib/cluster/index.ts :

const { Cluster } = require('ioredis')

const cluster = new Redis.Cluster([
  {
    port: 6380,
    host: "127.0.0.1",
  },
  {
    port: 6381,
    host: "127.0.0.1",
  },
])

cluster.get('someKey').then()

From the source code point of view, Cluster expected to receive two parameters, the first is the node set startNodes be started, and the second is an optional options ,
The first parameter is relatively fixed and does not have much meaning, while the second parameter can pass many configurations. This can be found in the README (currently 12 parameters): https://www.npmjs.com/package /ioredis#cluster
is not passed in, it will be filled with default values, but not all parameters will have default values.

And Redis Client same processing logic in the constructor will call about Commander , then will instantiate a ConnectionPool object and options.redisOptions passed into it.

The code is located in lib/cluster/ConnectionPool.ts

In ConnectionPool , no substantial operation was done, but options.redisOptions put into a private attribute.
Subsequently, four corresponding events were registered in the Cluster -node , +node , drain and nodeError , which respectively represent the removal of the node, the addition of the node, the node is empty, and the node error.
These are ConnectionPool , and you will see where they are triggered later.

Next, a ClusterSubscriber object was instantiated, and the connectionPool instance instantiated above was put in as a parameter, and Cluster instance was also passed in.
What is done during the instantiation process is also relatively simple, that is, listening to the -node and +node . When removing the node, it will determine whether the subscriber attribute exists. If it does not exist, it will jump out. Determine whether the removed key is equal to the current subscriber .
Here you can mention subscriber what is, in the bottom of selectSubscriber can see the function, it is an example of a Redis Client , and instantiate Redis Client parameters used are by calling connectionPool of getNodes methods to get and Randomly select one of the node configurations for instantiation.
After that, two commands will be called through the Redis Client subscriber and psubscriber , which are used to implement Pub/Sub. The specific difference is that the latter can monitor a service with wildcards.

The difference between subscriber and psubscriber: https://redis.io/topics/pubsub
And after receiving the data, it will be forwarded to emit Cluster . Subsequent Pub/Sub in the Cluster mode will use this to transfer the data.

Connect

Finally, we call the connect function to complete the entire instantiation process of Cluster

If lazyConnect is turned on, then directly modify the instance status to wait, and then end the entire process.

In connect when we first resolve startNodes, get the information corresponding to the IP and port, and then calls the reset reset connectionPool instance, connectionPool will store multiple IP + Redis instance port references, call the reset will put some should not examples exist to turn off, then put some new additions to create, reuse an existing instance, the new node at the same time when the trigger ClusterSubscriber of +node event, if this time is the first time the trigger, then the time ClusterSubscriber Will really create a Redis instance for Pub/Sub.

After registers a refresh event within the event calls readyCheck , before that, you need to go get Redis number of information nodes, this is by getInfoFromNode method to achieve, you will get inside a Redis instance and call duplicate create An additional instance, and then call the cluster slots command to get the current Redis cluster service status. The data returned here will include all the node IP + ports, as well as the start and end returns of a node. The specific return values are as follows:

redis 127.0.0.1:6379> cluster slots
1) 1) (integer) 0
   1) (integer) 4095
   2) 1) "127.0.0.1"
      1) (integer) 7000
   3) 1) "127.0.0.1"
      1) (integer) 7004
2) 1) (integer) 12288
   1) (integer) 16383
   2) 1) "127.0.0.1"
      1) (integer) 7003
   3) 1) "127.0.0.1"
      1) (integer) 7007
3) 1) (integer) 4096
   1) (integer) 8191
   2) 1) "127.0.0.1"
      1) (integer) 7001
   3) 1) "127.0.0.1"
      1) (integer) 7005
4) 1) (integer) 8192
   1) (integer) 12287
   2) 1) "127.0.0.1"
      1) (integer) 7002
   3) 1) "127.0.0.1"
      1) (integer) 7006

The data converted into JS roughly has a structure like this:

[
  [ // slot info
    0,    // slot range start 
    4095, // slot range end
    [
      '127.0.0.1', // IP
      7000         // port
    ]
  ],
  [  // other slot info
    12288,
    16383,
    [
      '127.0.0.1',
      7003
    ]
  ],
]
Description of cluster slot: https://redis.io/commands/cluster-slots

In acquiring these real node information in the future, it will be based on a set of nodes to get the call again connectionPool of reset method, since the last call is actually used startNode passed the initial value, where the data is currently running services will be used Make a replacement.
After this action is completed, the refresh event will be triggered, and the following readyCheck link will be entered to ensure that the service is available.

readyCheck

View readyCheck , mainly by calling the cluster info command to obtain the current service status.
When dealing with the problem in twemproxy mode Redis Client above readyCheck was info to the ping command to achieve, but here, there is no modification, because it should be noted that this is not the info command, but the cluster command. Only the parameter is info .

Cluster module will use cluster_state field in the cluster info command as the basis for detection. The data will be combined in the format of k:v\nk:v\n, so we will see in the code that the corresponding field is obtained by matching line breaks. And get the specific value by intercepting.

But for the logic here, I personally feel that it is easier to directly use regular matching, because the value of the parameter does not do any additional operations, it is only used for verification.
private readyCheck(callback: CallbackFunction<void | "fail">): void {
  (this as any).cluster("info", function (err, res) {
    if (err) {
      return callback(err);
    }
    if (typeof res !== "string") {
      return callback();
    }

    let state;
    const lines = res.split("\r\n");
    for (let i = 0; i < lines.length; ++i) {
      const parts = lines[i].split(":");
      if (parts[0] === "cluster_state") {
        state = parts[1];
        break;
      }
    }

    if (state === "fail") {
      debug("cluster state not ok (%s)", state);
      callback(null, state);
    } else {
      callback();
    }
  });
}

When we find cluster info returned data fail time, then that cluster node is a state that is not available, then it will call disconnect disconnect and reconnect.
When disconnect ClusterSubscriber will be closed at the same time. Because our connection is about to be closed, there is no need to keep a registered Pub/Sub instance here.
After these operations are completed, it will enter retry connect method is actually called again according to a certain logic, and the logic described above is executed again.

Drawing a diagram for the entire process is roughly like this:

Redis-Cluster-Create-Flow.jpg

sendCommand

After the instance is created, the next step will involve calling the command.
In front instantiation inevitably also mentioned some sendCommand things, Redis instantiation of the process, there will be a change in status, but each time the trigger sendCommand actually will go to check the status, if not established yet. Good connection, then the command at this time will be put into the temporary storage in offlineQueue
These commands will be called in order after readyCheck

Of course, there is also a judgment on the current instance status sendCommand wait , then it can be considered that the instance has enabled the lazyConnect mode, and then it will try to establish a connection with the server.

At the same time sendCommand . Some Pub/Sub corresponding commands, such as publish , will be forwarded to the ClusterSubscriber for execution, while other common commands will be placed in connectionPool for execution.
In this way, publish and subscribe are separated from ordinary commands.

Similarly, because it is Cluster mode, there will be a split logic between master and slave. This can be determined by scaleReads parameter passed in when Redis Cluster . The default is master , and optional all , slave and a A custom function that receives commands and a list of examples.

knowledge points are coming

In ioredis, the default configuration is master , which means that all requests will be sent to the master node, which means that if you create some slave libraries to improve the read performance, __ will not Was visited to __.

See the document for details: https://www.npmjs.com/package/ioredis#user-content-read-write-splitting

If you want to use the slave library, you can modify scaleReads slave , but you don't need to worry about sending some commands that will modify the database to the slave library. sendCommand will detect the sent commands if they are not read-only Command, and scaleReads is not set to master will be forcibly overwritten to master .

To determine whether the command is read-only: https://github.com/luin/ioredis/blob/master/lib/cluster/index.ts#L599

Regarding the custom function, you actually need to evaluate which instance(s) to use command
In the end, we got a Redis instance, and then use the Redis instance to call sendCommand .
Then the logic behind is no different from the Redis

to sum up

In general, in the implementation of Redis Cluster , 060b9a5521e3e0 is made as an Redis . In many places, you will see Redis , and will also inherit from the Command instance, which allows users to use the process without There are too many differences. Only the parameters passed in during instantiation are different. There is no difference when Redis Cluster Redis of sendCommand is internally called to complete the logical multiplexing.


Jarvis
5.1k 声望4.7k 粉丝

GitHub: [链接]