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 toemit
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:
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.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。