This article brings you relevant knowledge about Redis, which mainly introduces cluster-related issues. Redis cluster is a distributed database solution. The cluster shares data through sharding. , and provides replication and failover functions. I hope it will be helpful to everyone.
Recommended learning: Redis learning tutorial
Several Redis high availability solutions. Including: "Master-Slave Mode", "Sentinel Mechanism" and "Sentinel Cluster".
Redis has gone from the simplest stand-alone version to data persistence, master-slave multiple copies, and sentinel clusters. Through such optimization, both performance and stability have become higher and higher.
But with the development of time, the company's business volume has experienced explosive growth. Can the architectural model at this time still be able to bear such a large amount of traffic?
For example, there is such a requirement: use Redis to save 50 million
key-value pairs, each key-value pair is approximately 512B
, in order to quickly deploy and externally To provide services, we use cloud hosts to run Redis instances. So, how to choose the memory capacity of the cloud host?
By calculation, the memory space occupied by these key-value pairs is approximately 25GB (50 million *512B).
The first solution that comes to mind is: choose a cloud host with 32GB memory to deploy Redis. Because 32GB of memory can save all data, and there is still 7GB left to ensure the normal operation of the system.
At the same time, RDB is also used to persist data to ensure that data can be recovered from RDB after a Redis instance failure.
However, during use, you will find that the response of Redis is sometimes very slow. Check the latest_fork_usec
indicator value of Redis through the INFO command
(indicating the time taken for the latest fork), and it is found that this indicator value is particularly high.
This is related to the persistence mechanism of Redis.
When using RDB for persistence, Redis will fork
sub-process to complete, fork
The operation time is proportional to the amount of data in Redis Relevant , while fork
will block the main thread when executed. The larger the amount of data, the longer the main thread will be blocked due to the fork operation.
So, when using RDB
to persist 25GB of data, the amount of data is large, and the child process running in the background is blocked when fork
is created. The main thread is blocked, which causes Redis response to slow down.
Obviously this solution is not feasible, we must find other solutions.
In order to save a large amount of data, we generally have two methods: "Vertical expansion" and "Horizontal expansion":
First of all, the advantage of "vertical expansion" is that it is simple and direct to implement. However, this solution also faces two potential problems.
fork
when the child process May be blocked. Compared with "vertical expansion", "horizontal expansion" is a better scalability solution. This is because if you want to save more data, if you adopt this solution, you only need to increase the number of Redis instances, and you don't have to worry about the hardware and cost limitations of a single instance.
Redis cluster is implemented based on "horizontal expansion". It forms a cluster by starting multiple Redis instances, and then divides the received data into multiple parts according to certain rules, and uses one instance for each part. save.
Redis cluster is a distributed database solution. The cluster is divided into sharding
(sharding
, also called slicing)
) to share data and provide replication and failover functions.
Going back to the scenario we just had, if the 25GB data is divided equally into 5 parts (of course, it does not need to be divided equally), and 5 instances are used to save it, each instance only needs to save 5GB data. As shown below:
Then, in a slicing cluster, when an instance generates an RDB for 5GB data, the amount of data is much smaller. fork
The child process generally does not bring any significant overhead to the main thread. Prolonged blockage.
After using multiple instances to save data slices, we can not only save 25GB of data, but also avoid the sudden slowdown in response caused by fork
when the child process blocks the main thread.
When using Redis in practice, as the business scale expands, it is usually unavoidable to save a large amount of data. And Redis cluster is a very good solution.
Let’s start studying how to build a Redis cluster?
A Redis cluster usually consists of multiple nodes. At the beginning, each node is independent of each other, and there is no relationship between the nodes. To form a working cluster, we must connect the independent nodes to form a cluster containing multiple nodes.
We can connect each node through the CLUSTER MEET
command:
CLUSTER MEET <ip> <port></port></ip>
Command description: By sending the CLUSTER MEET
command to a node A, the node A that receives the command can add another node B to the node. In the cluster where A is located.
This is a bit abstract, let’s look at an example.
Suppose there are now three independent nodes 127.0.0.1:7001
, 127.0.0.1:7002
, 127.0.0.1:7003
.
We first use the client to connect to node 7001
:
$ redis-cli -c -p 7001
and then send commands to node 7001
, add node 7002
to the cluster where 7001
is located:
127.0.0.1:7001> CLUSTER MEET 127.0.0.1 7002
Similarly, we send the command to 7003
and also add it to # The cluster where ##7001 and
7002 are located.
127.0.0.1:7001> CLUSTER MEET 127.0.0.1 7003
You can view node information in the cluster through theCLUSTER NODES
command.
The cluster now contains three nodes:
7001,
7002 and
7003. However, when using a single instance, it is very clear where the data exists and where the client accesses it. However, slicing clusters inevitably involve
distributed management issues for multiple instances.
Redis Cluster solution that I will talk about next. However, we must first understand the connection and difference between slicing cluster and
Redis Cluster.
Before Redis 3.0, the official did not provide specific solutions for slicing clusters. Starting from 3.0, the official provides a solution calledIn fact, slicing cluster is a general mechanism for saving large amounts of data. This mechanism can have different implementation solutions.Redis Cluster
for implementing slicing clusters.
Redis Cluster The scheme stipulates the corresponding rules for data and instances.
Redis Cluster solution uses
Hash Slot (Hash Slot) to handle the mapping relationship between data and instances.
Redis Cluster solution, a slice cluster has a total of
16384 hash slots (2^14 ), these hash slots are similar to data partitions, each key-value pair will be mapped to a hash slot according to its key.
7001,
7002, and
7003 are connected through the
CLUSTER MEET command. Go to the same cluster, but this cluster is currently in
offline state, because the three nodes in the cluster have not been assigned any slots.
CLUSTER MEET command to manually establish connections between instances to form a cluster, and then use the
CLUSTER ADDSLOTS command to specify the number of hash slots on each instance. number.
CLUSTER ADDSLOTS <slot> [slot ...]</slot>
Redis5.0 provides theCLUSTER CREATE
command to create a cluster. Using this command, Redis will automatically distribute these slots evenly among the cluster instances.
举个例子,我们通过以下命令,给 7001
、7002
、7003
三个节点分别指派槽。
将槽 0 ~ 槽5000 指派给 给 7001
:
127.0.0.1:7001> CLUSTER ADDSLOTS 0 1 2 3 4 ... 5000
将槽 5001 ~ 槽10000 指派给 给 7002
:
127.0.0.1:7002> CLUSTER ADDSLOTS 5001 5002 5003 5004 ... 10000
将槽 10001~ 槽 16383 指派给 给 7003
:
127.0.0.1:7003> CLUSTER ADDSLOTS 10001 10002 10003 10004 ... 16383
当三个 CLUSTER ADDSLOTS
命令都执行完毕之后,数据库中的 16384 个槽都已经被指派给了对应的节点,此时集群进入上线状态。
通过哈希槽,切片集群就实现了数据到哈希槽、哈希槽再到实例的分配。
但是,即使实例有了哈希槽的映射信息,客户端又是怎么知道要访问的数据在哪个实例上呢?
一般来说,客户端和集群实例建立连接后,实例就会把哈希槽的分配信息发给客户端。但是,在集群刚刚创建的时候,每个实例只知道自己被分配了哪些哈希槽,是不知道其他实例拥有的哈希槽信息的。
那么,客户端是如何可以在访问任何一个实例时,就能获得所有的哈希槽信息呢?
Redis 实例会把自己的哈希槽信息发给和它相连接的其它实例,来完成哈希槽分配信息的扩散。当实例之间相互连接后,每个实例就有所有哈希槽的映射关系了。
客户端收到哈希槽信息后,会把哈希槽信息缓存在本地。当客户端请求键值对时,会先计算键所对应的哈希槽,然后就可以给相应的实例发送请求了。
当客户端向节点请求键值对时,接收命令的节点会计算出命令要处理的数据库键属于哪个槽,并检查这个槽是否指派给了自己:
MOVED
错误,然后重定向(redirect)到正确的节点,并再次发送之前待执行的命令。节点通过以下算法来定义 key
属于哪个槽:
crc16(key,keylen) & 0x3FFF;
通过
CLUSTER KEYSLOT <key></key>
命令可以查看 key 属于哪个槽。
当节点计算出 key 所属的 槽 i
之后,节点会判断 槽 i
是否被指派了自己。那么如何判断呢?
每个节点会维护一个 「slots数组」,节点通过检查 slots[i]
,判断 槽 i
是否由自己负责:
slots[i]
对应的节点是当前节点的话,那么说明 槽 i
由当前节点负责,节点可以执行客户端发送的命令;slots[i]
对应的不是当前节点,节点会根据 slots[i]
所指向的节点向客户端返回 MOVED
错误,指引客户端转到正确的节点。格式:
MOVED <slot> <ip>:<port></port></ip></slot>
比如:MOVED 10086 127.0.0.1:7002
,表示,客户端请求的键值对所在的哈希槽 10086
,实际是在 127.0.0.1:7002
这个实例上。
通过返回的 MOVED
命令,就相当于把哈希槽所在的新实例的信息告诉给客户端了。
这样一来,客户端就可以直接和 7002
连接,并发送操作请求了。
同时,客户端还会更新本地缓存,将该槽与 Redis 实例对应关系更新正确。
集群模式的
redis-cli
客户端在接收到MOVED
错误时,并不会打印出MOVED
错误,而是根据MOVED
错误自动进行节点转向,并打印出转向信息,所以我们是看不见节点返回的MOVED
错误的。而使用单机模式的redis-cli
客户端可以打印MOVED
错误。
其实,Redis 告知客户端重定向访问新实例分两种情况:MOVED
和 ASK
。下面我们分析下 ASK
重定向命令的使用方法。
在集群中,实例和哈希槽的对应关系并不是一成不变的,最常见的变化有两个:
重新分片可以在线进行,也就是说,重新分片的过程中,集群不需要下线。
举个例子,上面提到,我们组成了 7001
、7002
、7003
三个节点的集群,我们可以向这个集群添加一个新节点127.0.0.1:7004
。
$ redis-cli -c -p 7001 127.0.0.1:7001> CLUSTER MEET 127.0.0.1 7004 OK
然后通过重新分片,将原本指派给节点 7003
的槽 15001 ~ 槽 16383 改为指派给 7004
。
在重新分片的期间,源节点向目标节点迁移槽的过程中,可能会出现这样一种情况:如果某个槽的数据比较多,部分迁移到新实例,还有一部分没有迁移咋办?
在这种迁移部分完成的情况下,客户端就会收到一条 ASK
报错信息。
如果客户端向目标节点发送一个与数据库键有关的命令,并且这个命令要处理的键正好属于被迁移的槽时:
ASK
错误,指引客户端转向目标节点,并再次发送之前要执行的命令。看起来好像有点复杂,我们举个例子来解释一下。
如上图所示,节点 7003
正在向 7004
迁移 槽 16383
,这个槽包含 hello
和 world
,其中键 hello
还留在节点 7003
,而 world
已经迁移到 7004
。
我们向节点 7003
发送关于 hello
的命令 这个命令会直接执行:
127.0.0.1:7003> GET "hello" "you get the key 'hello'"
如果我们向节点 7003
发送 world
那么客户端就会被重定向到 7004
:
127.0.0.1:7003> GET "world" -> (error) ASK 16383 127.0.0.1:7004
客户端在接收到 ASK
错误之后,先发送一个 ASKING
命令,然后在发送 GET "world"
命令。
ASKING
命令用于打开节点的ASKING
标识,打开之后才可以执行命令。
ASK
错误和 MOVED
错误都会导致客户端重定向,它们的区别在于:
槽 i
的 MOVED
错误之后,客户端每次遇到关于 槽 i
的命令请求时,都可以直接将命令请求发送至 MOVED
错误指向的节点,因为该节点就是目前负责 槽 i
的节点。槽 i
的 ASK
错误之后,客户端只会在接下来的一次命令请求中将关于 槽 i
的命令请求发送到 ASK
错误指向的节点,但是 ,如果客户端再次请求 槽 i
中的数据,它还是会给原来负责 槽 i
的节点发送请求。这也就是说,ASK 命令的作用只是让客户端能给新实例发送一次请求,而且也不会更新客户端缓存的哈希槽分配信息。而不像 MOVED
命令那样,会更改本地缓存,让后续所有命令都发往新实例。
我们现在知道了 Redis 集群的实现原理。下面我们再来分析下,Redis 集群如何实现高可用的呢?
Redis 集群中的节点也是分为主节点和从节点。
举个例子,对于包含 7001
~ 7004
的四个主节点的集群,可以添加两个节点:7005
、7006
。并将这两个节点设置为 7001
的从节点。
设置从节点命令:
CLUSTER REPLICATE <node_id></node_id>
如图:
如果此时,主节点 7001
下线,那么集群中剩余正常工作的主节点将在 7001
的两个从节点中选出一个作为新的主节点。
例如,节点 7005
被选中,那么原来由节点 7001
负责处理的槽会交给节点 7005
处理。而节点 7006
会改为复制新主节点 7005
。如果后续 7001
重新上线,那么它将成为 7005
的从节点。如下图所示:
集群中每个节点会定期向其他节点发送 PING
消息,来检测对方是否在线。如果接收消息的一方没有在规定时间内返回 PONG
消息,那么接收消息的一方就会被发送方标记为「疑似下线」。
集群中的各个节点会通过互相发消息的方式来交换各节点的状态信息。
节点的三种状态:
一个节点认为某个节点失联了并不代表所有的节点都认为它失联了。在一个集群中,半数以上负责处理槽的主节点都认定了某个主节点下线了,集群才认为该节点需要进行主从切换。
Redis 集群节点采用 Gossip 协议来广播自己的状态以及自己对整个集群认知的改变。比如一个节点发现某个节点失联了 (PFail),它会将这条信息向整个集群广播,其它节点也就可以收到这点失联信息。
我们都知道,哨兵机制可以通过监控、自动切换主库、通知客户端实现故障自动切换。那么 Redis Cluster
又是如何实现故障自动转移呢?
当一个从节点发现自己正在复制的主节点进入了「已下线」状态时,从节点将开始对下线主节点进行故障切换。
故障转移的执行步骤:
SLAVEOF no one
命令,成为主节点PONG
消息,让集群中其他节点知道,该节点已经由从节点变为主节点,且已经接管了原主节点负责的槽这个选主方法和哨兵的很相似,两者都是基于 Raft算法
的领头算法实现的。流程如下:
大于等于 N/2 + 1
时,该从节点就会当选为新的主节点;集群中的各个节点通过发送和接收消息来进行通信,我们把发送消息的节点称为发送者,接收消息的称为接收者。
节点发送的消息主要有五种:
Cluster Each node in the exchanges the status information of different nodes through the Gossip
protocol. Gossip
is composed of MEET
, PING
, PONG
Composed of three kinds of messages.
Every time the sender sends a MEET
, PING
, PONG
message, he will randomly select two nodes from his known node list. node (can be a master node or a slave node) and sent to the recipient together. When the
receiver receives the MEET
, PING
, PONG
message, it performs different processing based on whether it knows these two nodes:
Recommended learning: Redis tutorial
The above is the detailed content of Take you step by step to understand Redis high availability cluster. For more information, please follow other related articles on the PHP Chinese website!