1. 现象描述
在使用redis集群的时候,当我们连接一个节点的时候,如果这个节点不是主节点,而是从节点,那么我们的连接命令会报错,提示错误信息为(error) MOVED。表示所连接的节点已经不是该数据的归属节点了,需要我们重新去找该数据所在的节点。
2. 错误原因
redis集群中,有6个节点,在节点1加入一个数据后(假设是字符串类型),redis会对该数据执行CRC16运算,得到一个整型数,然后对整型数取模,得到一个数字n,在redis集群中n对应的节点就是数据的归属节点,存储了该数据。但是这里的问题在于,每个节点都有可能是主节点也有可能是从节点,也就是说通过连接某一个节点,我们不能确定该节点是否是我们想要的归属节点,所以在连接从节点并执行命令的时候,如果该从节点的数据发生了迁移,就会报错。
3. 解决方法
3.1 迁移前确认节点状态
在使用redis集群的时候,我们可以通过命令 redis-cli cluster nodes
查看所有节点的状态,找到主节点,然后连接主节点。如果我们要执行一些读操作,连接的从节点就可以了。如果我们要执行一些写操作,在连接之前先使用 redis-cli cluster keyslot key
命令获取数据所在的槽信息,然后在连接节点之前先检查该节点是否为相应槽的主节点,如果是,连接该节点执行操作,否则使用新的节点重新连接。
# 查看所有节点的状态
$ redis-cli cluster nodes
# 获取槽信息,并检查所连接的节点是否为主节点
$ redis-cli -c cluster keyslot mykey
$ redis-cli -c cluster nodes | grep 1234
3.2 使用redis的客户端
除了手动连接节点进行操作之外,我们也可以使用redis的客户端来连接redis集群。例如php语言,我们可以使用 Predis
客户端来连接redis,具体操作可以参考 这篇文章。
3.3 使用哨兵模式
除了使用redis集群之外,我们还可以使用redis的哨兵模式。哨兵模式是由一个或多个sentinel实例组成的集合。当某个redis主节点不可用时,哨兵会选举出新的主节点,同时还会通知客户端新的主节点的已变更的地址和端口号。如下图所示:
在哨兵模式下,客户端只需要连接哨兵,然后哨兵会返回最新的主节点的地址信息,客户端再连接最新的主节点进行操作就行了。
3.4 封装连接方式
在实际的开发中,如果我们需要频繁地通过手动方式连接节点进行操作,这无疑会增加我们的开发难度,并且代码重复程度高。建议大家将连接redis集群的代码进行封装,精简操作,增强代码的可重用性。
示例代码:
class RedisCluster {
private $nodes;
public function __construct($nodes) {
$this->nodes = $nodes;
}
public function getConnection($key) {
$slot = $this->keyToSlot($key);
$node = $this->getNode($slot);
$conn = new Redis;
$conn->pconnect($node['host'], $node['port']);
return $conn;
}
private function keyToSlot($key) {
$CRC16_TABLE = array(
// CRC16数组
);
$hash = crc16($key);
return $hash % 16384;
}
private function getNode($slot) {
foreach ($this->nodes as $node) {
if ($node['slots'][0] <= $slot && $node['slots'][1] >= $slot) {
return $node;
}
}
}
}
$nodes = array(
array('host' => '127.0.0.1', 'port' => 7000, 'slots' => array(0, 1000)),
array('host' => '127.0.0.1', 'port' => 7001, 'slots' => array(1001, 2000)),
// ...
);
$rc = new RedisCluster($nodes);
$conn = $rc->getConnection('mykey');
4. 总结
以上是关于redis连接集群时遇到的错误,以及解决方法的总结。遇到这种问题,一定不要慌张,总结查看相信一定会给你带来帮助,增长你的技能。