10.1 数据分布
10.1.1 数据分布理论
-
数据分区示意图
image.png
- 分区规则
分区方式 | 特点 | 代表产品 |
---|---|---|
顺序分区 | 离散度易倾斜;数据分布业务相关;可顺序访问 | Bigtable; HBase; Hypertable |
哈希分区 | 离散度好;数据分布业务相关;无法顺序访问 | Redis Cluster; Cassandra; Dynamo |
-
节点取余哈希
image.png
-
一致性哈希
image.png
优点:
- 加入和删除节点只影响哈希环中相邻的节点
缺点:
- 加减节点后,会导致部分原节点中的数据无法命中,需要迁移到新节点
- 当使用少量节点时,单个节点的数据量较大,因此加减节点时影响的数据量较大,数据迁移成本较高
- 要保证负载均衡保持稳定,加减节点需要增加一倍或者减少一半的节点
- 虚拟槽分区hash
- 顺着一致性哈希的思路,为了解决第二个缺点,需要尽量增加节点数,但要解决第三个缺点,又要减少节点数;按照计算机加层解决问题的思路,引入了中间概念:虚拟槽
- 虚拟槽分区巧妙的使用了哈希空间,使用分散度良好的哈希函数把所有数据映射到一个固定范围的整数集合中,整数定义为槽(slot),Redis Cluster槽范围是0~16283, 每个节点负责一部分槽。这样一来,加减节点时只需要迁移一部分槽的数据,而依赖于槽的粒度较小的特点,做到数据哈希分散良好较为容易。
10.1.2 Redis数据分区
slot = CRC16(key) & 16383
10.1.3 集群功能限制
- key批量操作支持有限: 如mset, mget, 只支持具有相同slot值的key执行批量操作
- key事务操作支持有限: 同理只支持多key在同一节点上的事务操作
- key作为数据分区的最小粒度, 因此不能将一个大的键值对象如hash, list映射到不同的节点
- 不支持多数据库空间
- 复制结构只支持一层, 从节点复制主节点, 不支持嵌套树状复制结构
10.2 搭建集群
10.2.1 节点准备
- 目录
# 建议集群内所有节点统一目录
conf # 配置
data # 数据
log # 日志
- 配置文件redis-6379.conf
# 配置示例
# 节点端口
port 6379
# 开启集群模式
cluster-enabled yes
# 节点超时时间, 单位毫秒
cluster-node-timeout 15000
# 集群内部配置文件: 由redis自动维护, 不需要手动修改
cluster-config-file "nodes-6379.conf"
- 启动节点
# 启动所有节点
redis-server conf/redis-6379.conf
redis-server conf/redis-6380.conf
redis-server conf/redis-6381.conf
redis-server conf/redis-6382.conf
redis-server conf/redis-6383.conf
redis-server conf/redis-6384.conf
> cat data/nodes-6379.conf
cfb28ef1deee4e0fa78da86abe5d24566744411e 127.0.0.1:6379 myself,master - 0 0 0 connected vars currentEpoch 0 lastVoteEpoch 0
127.0.0.1:6380> cluster nodes
8e41673d59c9568aa9d29fb174ce733345b3e8f1 127.0.0.1:6380 myself,master - 0 0 0 connected
这一步完成后, 每个节点都启动好了,但彼此并不知道对方的存在。
10.2.2 节点握手
- 握手过程采用Gossip协议, 需要一个过程
-
示意图
image.png
- 节点握手过程
6379 --meet--> 6380
6380 --pong--> 6379
6379 --定期ping--> 6380
6380 --定期pong--> 6379
- 命令
127.0.0.1:6379>cluster meet 127.0.0.1 6380
127.0.0.1:6379>cluster meet 127.0.0.1 6381
127.0.0.1:6379>cluster meet 127.0.0.1 6382
127.0.0.1:6379>cluster meet 127.0.0.1 6383
127.0.0.1:6379>cluster meet 127.0.0.1 6384
- 效果
上述命令执行完毕一段时间后, 节点之间通过Gossip协议互相告知自己所知的节点, 这样最终形成一个两两互联的节点网络.
10.2.3 槽位分配
- 先将16384个槽位(slot)平均分配给6379,6380,6381三个节点
redis-cli -h 127.0.0.1 -p 6379 cluster addslots {0...5461}
redis-cli -h 127.0.0.1 -p 6380 cluster addslots {5462...10922}
redis-cli -h 127.0.0.1 -p 6381 cluster addslots {10923...16383}
- 查看集群状态
127.0.0.1:6379>cluster info
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:5
cluster_my_epoch:0
cluster_stats_messages_sent:4874
cluster_stats_messages_received:4726
- 查看集群内节点信息
127.0.0.1:6379>cluster nodes
- 让剩余的节点6382,6383,6384成为从节点
127.0.0.1:6382>cluster replicate cfb28ef1deee4e0fa78da86abe5d24566744411e
127.0.0.1:6383>cluster replicate 8e41673d59c9568aa9d29fb174ce733345b3e8f1
127.0.0.1:6384>cluster replicate 4041673d59c9568aa9d29fb174ce733345b36746
这样,我们就手动搭建了一个3主3从, 槽位均分的集群, 还是比较麻烦的,节点多了就需要更先进的工具了
10.2.4 用redis-trib.rb搭建集群
redis-trib.rb是一个Ruby实现的Redis集群管理工具, 内部通过Cluster相关命令帮助我们简化集群创建、检查、槽迁移和均衡等常见运维操作,使用之前需要安装Ruby依赖环境。
- Ruby环境准备
wget https://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.1.tar.gz
tar xvf ruby-2.3.1.tar.gz
./configure -prefix=/usr/local/ruby
make
make install
cd /usr/local/ruby
sudo cp bin/ruby /usr/local/bin
sudo cp bin/gem /usr/local/bin
wget http://rubygems.org/downloads/redis-3.3.0.gem
gem install -l redis-3.3.0.gem
gem list --check redis gem
sudo cp /{redis_home}/src/redis-trib.rb /usr/local/bin
# redis-trib.rb
- 准备节点
redis-server conf/redis-6481.conf
redis-server conf/redis-6482.conf
redis-server conf/redis-6483.conf
redis-server conf/redis-6484.conf
redis-server conf/redis-6485.conf
redis-server conf/redis-6486.conf
- 创建集群
# 每个主节点的从节点数为1, 前面3个为主, 后面3个为从, 按顺序配对
redis-trib.rb create --replicas 1 127.0.0.1:6481 127.0.0.1:6482 127.0.0.1:6483 127.0.0.1:6484 127.0.0.1:6485 127.0.0.1:6486
- 集群完整性检查
# 选择集群中任意一个节点进行检查, 即可检查整个集群的情况
redis-trib.rb check 127.0.0.1:6381
10.3 节点通信
10.3.1 通信流程
- 通信内容: 节点元数据
节点负责哪些数据
是否出现故障
通信方式: Gossip协议
节点彼此不断通信交换信息,一段时间后所有的节点都会知道集群完整的信息,类似流言传播-
流程说明
- 每个节点单独开辟一个TCP通道, 用来节点彼此通信, 端口号在基础端口号上加10000
- 每个节点在固定周期内通过特定规则选择几个节点发送ping消息
- 节点接收到ping后, 返回pong作为相应.
10.3.2 Gossip消息
- 分类
ping, pong, meet, fail
-
meet节点间建立通信
image.png
-
ping/pong节点间维持通信
image.png
-
判断其他节点处于下线状态, 则发送fail消息给其他节点寻求共识
image.png
- 消息头
typedef struct {
char sig[4]; // 信号标识
uint32_t totlen; // 消息总长度
uint16_t ver; // 协议版本
uint16_t type; // 消息类型(meet,ping,pong,fail)
uint16_t count; // 消息体包含的节点数量, 进用于meet,ping,pong消息类型
uint64_t currentEpoch; // 当前发送节点的配置纪元
uint64_t configEpoch; // 主节点/从节点的主节点的配置纪元
uint64_offset; // 复制偏移量
char sender[CLUSTER_NAMELEN]; // 发送节点的nodeId
unsigned char myslots[CLUSTER_SLOTS/8]; // 发送节点负责的槽信息
char slaveof[CLUSTER_NAMELEN]; // 如果发送节点是从节点, 记录对应主节点的nodeId
uint16_t port; // 端口号
uint16_t flags; // 发送节点标识, 区分主从角色, 是否下线等
unsigned char state; // 发送节点所处的集群状态
unsigned char mflags[3]; // 消息标识
union clusterMsgData data; // 消息正文
} clusterMsg;
- 消息体
union clusterMsgData {
/* ping,meet,pong消息体 */
struct {
/* gossip消息结构数组 */
clusterMsgDataGossip gossip[1];
} ping;
/* FAIL消息体 */
struct {
clusterMsgDataFail about;
} fail;
};
typedef struct {
char nodename[CLUSTER_NAMELEN]; /* 节点的nodeId */
uint32_t ping_sent; /* 最后一次向该节点发送ping消息时间 */
uint32_t pong_received; /* 最后一次接收该节点pong消息时间 */
char ip[NET_IP_STR_LEN]; /* IP */
uint16_t port; /* port */
uint16_t flags; /* 该节点标识 */
} clusterMsgDataGossip;
-
消息解析流程
image.png
10.3.3 节点选择
- Gossip天然耗费带宽和计算,需要选择一部分节点来降低消耗
-
选择规则和通信量
image.png
- 选择发送消息的节点数量
每个节点每秒发送ping消息的数量: 1 + 10 * num(node.pong_received > cluster_node_timeout / 2)
当带宽资源紧张时, 可以适当调大cluster_node_timeout参数, 比如由默认15秒改为30秒
- 消息数据量
消息头主要部分: myslots[CLUSTER_SLOTS/8]: 2KB
消息体: 默认包含节点总量的1/10个节点的信息, 所以集群越大, 消息体越大
10.4 集群伸缩
10.4.1 伸缩原理
-
槽和对应数据在不同节点之间的移动
image.png
10.4.2 扩容集群
- 准备新节点
redis-server conf/redis-6385.conf
redis-server conf/redis-6386.conf
- 加入集群
redis-trib.rb add-node 127.0.0.1:6385 127.0.0.1:6379
redis-trib.rb add-node 127.0.0.1:6386 127.0.0.1:6379
- 迁移槽和数据
# [目标节点]执行: 目标节点准备导入槽
cluster setslot {slot} importing {sourceNodeId}
# [源节点]执行: 源节点准备迁出槽的数据
cluster setslot {slot} migrating {targetNodeId}
# [源节点]循环执行: 获取slot下{count}个键
cluster getkeysinslot {slot} {count}
# [源节点]执行: 批量迁移相关键的数据
migrate {targetIp} {targetPort} "" 0 {timeout} keys {keys...}
# 向集群内所有节点通知指定槽节点映射变更结果
cluster setslot {slot} node {targetNodeId}
例如
127.0.0.1:6385>cluster setslot 4096 importing cfb28eafadsfdsafesdf411e
127.0.0.1:6379>cluster setslot 4096 migrating 1a205dfafsafdfsdfadfb756
127.0.0.1:6379>cluster getkeysinslot 4096 100
127.0.0.1:6379>migrate 127.0.0.1 6385 "" 0 5000 keys key:test:5028 key:test:68253 key:test:79212
127.0.0.1:6379>cluster setslot 4096 node 1a205dfafsafdfsdfadfb756
127.0.0.1:6380>cluster setslot 4096 node 1a205dfafsafdfsdfadfb756
127.0.0.1:6381>cluster setslot 4096 node 1a205dfafsafdfsdfadfb756
127.0.0.1:6385>cluster setslot 4096 node 1a205dfafsafdfsdfadfb756
redis-trib.rb支持
redis-trib.rb reshard host:port --from <arg> --to <arg> --slots <arg> --yes --timeout <arg> --pipeline <arg>
# host:port: 集群内任意节点地址
# --from: 源节点的id
# --to: 目标节点的id
# --slots: 需要迁移键的总数量
# --yes: 是否需要用户确认才执行
# --timeout: migrate操作的超时时间, 默认60 000毫秒
# --pipeline: 每次批量迁移键的数量, 默认为10
redis-trib.rb reshard 127.0.0.1:6379 --from cfb28eafadsfdsafesdf411e --to 1a205dfafsafdfsdfadfb756 --slots 4096
redis-trib.rb rebalance 127.0.0.1:6380
- 添加从节点
127.0.0.1:6386>cluster replicate 1a205dfafsafdfsdfadfb756
10.4.3 收缩集群
- 迁移槽
redis-trib.rb reshard 127.0.0.1:6379
- 忘记节点
redis-trib.rb del-node {host:port} {downNodeId}
10.5 请求路由
10.5.1 请求重定向
-
MOVED重定向流程
image.png
- 槽位计算方式
1. 如果键内容包含{...}大括号字符, 则计算槽的有效部分是括号内的内容; 否则采用键的全内容。
2. crc16(key, keylen) & 16383
- 槽节点查找
typedef struct clusterState {
clusterNode *myself;
clusterNode *slots[CLUSTER_SLOTS]; // 16384个槽和节点映射数组, 数组下标代表槽位序号
} clusterState;
如果每次都重定向,显然比较耗费带宽和IO,所以需要客户端"聪明"一点,能记住槽位所对应的节点。
10.5.2 Smart客户端
- 原理: 客户端维护
slot->node
映射
image.png
10.5.3 ASK重定向
- 当一个slot数据分布在多个节点时, 客户端去对应的节点取数据如果取不到, 该节点会返回ASK重定向错误:
(error) ASK {slot} {targetIP}:{targetPort}
-
Smark客户端工作流程
image.png
需要评估mset,mget,pipeline等批量操作在slot迁移中间过程中的容错性,在客户端写出容错代码。
10.6 故障转移
10.6.1 故障发现
-
主观下线
image.png
typedef struct clusterState {
clusterNode *myself; // 自身节点
dict *nodes; // 当前集群内所有节点的字典集合, <节点ID --> 对应节点的clusterNode结构>
...
} clusterState;
typedef struct clusterNode {
int flags; // 当前节点状态(主从角色, 是否下线等)
mstime_t ping_sent; // 最后一次与该节点发送ping消息的时间
mstime_t pong_received; // 最后一次接收到该节点pong消息的时间
...
} clusterNode;
flags的取值:
CLUSTER_NODE_MASTER 1 // 当前为主节点
CLUSTER_NODE_SLAVE 2 // 当前为从节点
CLUSTER_NODE_PFAIL 4 // 主观下线状态
CLUSTER_NODE_FAIL 8 // 客观下线状态
CLUSTER_NODE_MYSELF 16 // 表示自身节点
CLUSTER_NODE_HANDSHAKE 32 // 握手状态, 未与其他节点进行消息通信
CLUSTER_NODE_NOADDR 64 // 无地址节点, 用于第一次meet通信未完成或者通信失败
CLUSTER_NODE_MEET 128 // 需要接受meet消息的节点状态
CLUSTER_NODE_MIGRATE_TO 256 // 该节点被选中为新的主节点状态
- 客观下线
struct clusterNode { // 认为是主观下线的clusterNode结构
list *fail_reports; // 记录了所有其他节点对该节点的下线报告
...
}
# 每个节点clusterNode都存在一个下线链表结构,维护着其他主节点针对当前节点的下线报告
typedef struct clusterNodeFailReport {
struct clusterNode *node; // 报告该节点为主观下线的节点
mstime_t time; // 最近收到下线报告的时间
} clusterNodeFailReport;
# 更新报告故障的节点结构和最近收到下线报告的时间
def clusterNodeAddFailureReport(failNode -> clusterNode, senderNode -> clusterNode):
report_list = failNode.fail_reports
for report in report_list:
if senderNode == report.node:
report.time = now()
return 0
report_list.append(clusterNodeFailReport(senderNode, now()))
return 1
# 尝试触发客观下线时, 删除过期的下线报告
def clusterNodeCleanupFailureReports(node -> clusterNode):
report_list = node.fail_reports
maxtime = server.cluster_node_timeout * 2
now = now()
for report in report_list:
if now - report.time > maxtime:
report_list.remove(report)
break
cluster_node_timeout设置的过小时, 主观下线报告的上传速度赶不上下线报告过期的速度, 这样故障节点就永远无法被客观下线,从而导致故障转移失败。需要有针对这种情况的监控代码,或者调大cluster_node_timeout值
def markNodeAsFailingIfNeeded(failNode -> clusterNode):
slotNodeSize = getSlotNodeSize()
needed_quorum = (slotNodeSize // 2) + 1
failures = clusterNodeFailureReportsCount(failNode)
if nodeIsMaster(myself):
failures += 1
if failures < needed_quorum:
return
failNode.flags = CLUSTER_NODE_FAIL
failNode.fail_time = mstime()
if nodeIsMaster(myself):
clusterSendFail(failNode)
假如网络分区情况存在,会导致分割后的小集群无法收到大集群的fail消息,从而无法顺利完成故障转移,因此部署时需要避免这种情况。
10.6.2 故障恢复
主节点发生故障,则提升该主节点的从节点来替换它,保证集群的高可用
- 资格检查
从节点与故障主节点的断线时间 <= cluster_node_timeout * cluster_slave_validity_factor
其中cluster_slave_validity_factor默认为10
- 准备选举时间
- 延迟触发机制: 复制偏移量越大的从节点, 具有更高的优先级来替换故障主节点。
struct clusterState {
...
mstime_t failover_auth_time; // 记录之前或者下次将要执行故障选举时间
int failover_auth_rank; // 记录当前从节点排名
}
# 计算从节点优先级排名
def clusterGetSlaveRank():
rank = 0
master = myself.slaveof
myoffset = replicationGetSlaveOffset()
for j in range(len(master.slaves)):
if master.slaves[j] != myself and
master.slaves[j].repl_offset > myoffset:
rank += 1
return rank
# 使用优先级排名, 更新从节点选举触发时间
def updateFailoverTime():
server.cluster.failover_auth_time = now() + 500 + random() % 500
rank = clusterGetSlaveRank()
added_delay = rank * 1000
server.cluster.failover_auth_time += added_delay
server.cluster.failover_auth_rank = rank
- 发起选举
配置纪元
- 每个主节点有个配置纪元cluster_my_epoch
- 集群的全局配置纪元是所有主节点纪元的最大值cluster_current_epoch
- 从节点复制主节点的配置纪元
- 纪元同步靠ping/pong消息传递
- 发送主节点和接收主节点的配置纪元相等时,若发送方nodeId大于接收方, 则接收方纪元自增1
def clusterHandleConfigEpochCollision(sender -> clusterNode):
if sender.configEpoch != myself.configEpoch \
or not nodeIsMaster(sender)
or not nodeIsMaster(myself):
return
if sender.nodeId <= myself.nodeId:
return
server.cluster.currentEpoch += 1
myself.configEpoch = server.cluster.currentEpoch
配置纪元的应用场景
- 新节点加入
- 槽节点映射冲突检测
- 从节点投票选举冲突检测
发起选举的操作步骤
- 自增集群的全局配置纪元, 单独保存在clusterState.failover_auth_epoch
- 在集群内广播选举消息(FAILOVER_AUTH_REQUEST)
- 选举投票
投票资格
- 是主节点
- 持有数据槽
- 没有投票过
当选条件
- 收集到N/2+1张选票
投票作废
- 开始投票后, 超过cluster_node_timeout * 2时间内没有从节点当选, 则本次选举作废。
- 替换主节点
- 当前从节点取消复制,变为主节点
- 执行clusterDelSlot操作撤销故障主节点负责的槽,并执行clusterAddSlot操作把这些槽委派给自己
- 向集群广播自己的pong消息, 通知集群内所有的节点当前从节点变为主节点并接管了故障主节点的槽信息。
10.6.3 故障转移时间
- 主观下线(pfail)识别时间 = cluster_node_timeout
- 主观下线状态消息传播时间 <= cluster_node_timeout / 2
- 从节点转移时间 <= 1000毫秒
failover_time(毫秒) <= cluster_node_timeout + cluster_node_timeout / 2 + 1000
其中, cluster_node_timeout默认15秒。
10.6.4 故障转移演练
# 查看集群内节点信息
cluster nodes
# 关闭6385节点进程
ps -ef | grep redis-server | grep 6385
kill -9 6385的pid
# 从节点6386和主节点6385之间复制中断
redis-6386.log
Connecting to MASTER 127.0.0.1:6385
MASTER <-> SLAVE sync started
Error Condition on socket for SYNC: Connection refused
# 主观下线和客观下线
redis-6380.log
Marking node 6385的nodeId as failing (quorum reached)
redis-6379.log
Marking node 6385的nodeId as failing (quorum reached)
# 从节点选举
redis-6386.log
Start of election delayed for 964 milliseconds (rank #0, offset 1822).
Starting a failover election for epoch 17.
redis-6380.log
Failover auth granted to 6386的nodeId for epoch 17
redis-6379.log
Failover auth granted to 6386的nodeId for epoch 17
redis-6386.log
Failover election won: I'm the new master.
configEpoch set to 17 after successful failover.
10.7 集群运维
10.7.1 集群完整性
- 所有的16384个槽都指派到了节点
- 通过cluster_require_full_coverage=no来控制主节点故障后,只影响该主节点内槽位数据的相关命令执行,不干扰其他节点
10.7.2 带宽消耗
- 集群节点数限制在1000以内
- 消息发送频率, 受cluster_node_timeout控制
- 消息数据量,主要部分:slots槽数组(2KB)+集群1/10的状态数据(10个节点1KB)
- 同样节点数,机器越多可用带宽越高
- 200节点, 10节点/机器, cluster_node_timeout=15秒, ping/pong消息占用带宽25Mb,如果clusterNode_timeout=30秒, 带宽消耗降低到15Mb以下。
对策
- 避免大集群
- 适度提高cluster_node_timeout降低消息发送频率
- 尽量均匀部署在更多机器上
10.7.3 Pub/Sub广播问题
节点一多,广播风暴会造成带宽负担加重。
对策
- 使用sentinel结构而非集群来规避Pub/Sub广播风暴对带宽的压力。
10.7.4 集群倾斜
常见情况
- 数据倾斜
- 节点和槽分配不均
redis-trib.rb info 127.0.0.1:6379
redis-trib.rb rebalance 127.0.0.1:6379
- 不同槽对应键数量差异过大
cluster countkeysinslot {slot}
cluster getkeysinslot {slot} {count}
- 集合对象包含大量元素
redis-cli --bigkeys
- 内存相关配置不一致
查看hash_max_ziplist_value, set_max_intset_entries
- 请求倾斜
- 合理设计键, 热点大集合进行拆分
- 不实用热键作为hash_tag, 避免映射到同一槽
- 实用本地缓存减少热键调用
10.7.5 集群读写分离
- 只读连接
client list
get key:test:3130 # 收到MOVED返回
readonly # 打开当前连接为只读状态
client list
get key:test:3130 # 从节点响应read命令
- 读写分离
cluster slaves {主节点的nodeId}
客户端所做修改
- 维护每个主节点可用从节点列表
- 针对读命令维护请求节点路由
- 从节点新建连接开启readonly状态
成本较高,不建议集群使用读写分离。
10.7.6 手动故障转移
-
流程
image.png 场景
- 主节点迁移
- 强制故障转移
10.7.7 数据迁移
redis-trib.rb import host:port --from <arg> --copy --replace
本章重点回顾
- Redis集群数据分区规则采用虚拟槽方式,所有的键映射到16384个槽中,每个节点负责一部分槽和相关数据,实现数据和请求的负载均衡。
- 搭建集群划分三个步骤:准备节点、节点握手、分配槽。可以使用redis-trib.rb create命令快速搭建集群。
- 集群内部节点通信采用Gossip协议彼此发送消息,消息类型分为:ping消息、pong消息、meet消息、fail消息等。节点定期不断发送和接受ping/pong消息来维护更新集群的状态。消息内容包括节点自身数据和部分其他节点的状态数据。
- 集群伸缩通过在节点之间移动槽和相关数据实现。扩容时根据槽迁移计划把槽从源节点迁移到目标节点,源节点负责的槽相比之前变少从而达到集群扩容的目的,收缩时如果下线的节点有负责的槽需要迁移到其他节点,再通过cluster forget命令让集群内其他节点忘记被下线节点。
- 使用Smart客户端操作集群达到通信效率最大化,客户端内部负责计算维护键->槽->节点的映射,用于快速定位键命令到目标节点。集群协议通过Smart客户端全面高效的支持需要一个过程,用户在选择Smart客户端时建议review下集群交互代码如:异常判定和重试逻辑,更新槽的并发控制等。节点接收到键命令时会判断相关的槽是否由自身节点负责,如果不是则返回重定向信息。重定向分为MOVED和ASK,ASK说明集群正在进行槽数据迁移,客户端只在本次请求中做临时重定向,不会更新本地槽缓存。MOVED重定向说明槽已经明确分配到另一个节点,客户端需要更新槽节点缓存。
- 集群自动故障转移过程分为故障发现和故障恢复。节点下线分为主观下线和客观下线,当超过半数主节点认为故障节点为主观下线时标记它为客观下线状态。从节点负责对客观下线的主节点触发故障恢复流程,保证集群的可用性。
- 开发和运维集群过程中常见问题包括:超大规模集群带宽消耗,pub/sub广播问题,集群节点倾斜问题,手动故障转移,在线迁移数据等。