为帮助阿里/字节跳动资深Java工程师深入理解RedisCluster与Sentinel模式,我将从架构、核心能力等多方面对比,结合项目案例、源码解析,并设置面试追问板块,全面剖析二者区别。

Redis Cluster与Sentinel模式深度剖析:从架构到工程实践

在分布式缓存领域,Redis的高可用方案一直是大厂面试的核心考点,也是生产环境架构设计的关键环节。本文将从架构设计、核心能力、数据流转、源码实现四个维度,结合阿里电商缓存集群与字节跳动短视频计数服务的实际案例,深度解析Redis Cluster与Sentinel模式的本质区别,并针对大厂高频追问提供工程化解决方案。

一、架构本质:分布式存储 vs 高可用监控

Redis Cluster与Sentinel模式的核心区别在于设计目标不同:Sentinel聚焦于「单点Redis的高可用」,通过监控实现主从故障转移;而Cluster则是「分布式存储+高可用」的完整解决方案,通过分片实现数据水平扩展。

1.1 Sentinel模式架构

Sentinel模式由「主从节点+Sentinel节点集群」组成,主节点负责写操作,从节点负责读操作和数据备份,Sentinel节点负责监控主从状态并执行故障转移。

监控
监控
监控
监控
监控
监控
监控
监控
监控
集群通信
集群通信
集群通信
故障转移
新主节点
读操作
读操作
通过Sentinel获取主节点
客户端
主节点 Master
从节点 Slave1
从节点 Slave2
Sentinel节点1
Sentinel节点2
Sentinel节点3
Sentinel集群

注:Sentinel节点至少需要3个才能避免脑裂,通过Raft协议选举Leader执行故障转移。

1.2 Cluster模式架构

Cluster模式将数据分片存储在多个「主从节点组」中,每个主节点负责一部分槽位(共16384个槽),从节点作为主节点的备份。Cluster自身集成了高可用能力,无需额外Sentinel节点。

槽位路由
Gossip协议
Gossip协议
Gossip协议
故障检测
故障检测
故障检测
客户端
Cluster节点代理
主节点A 槽0-5460
主节点B 槽5461-10922
主节点C 槽10923-16383
从节点A1
从节点A2
从节点B1
从节点B2
从节点C1
从节点C2

注:每个主节点通过Gossip协议与其他节点通信,维护集群元数据;当主节点故障时,其从节点会自动选举为新主节点。

二、核心能力对比:从数据分片到故障转移

对比维度Sentinel模式Cluster模式
数据分片无分片能力,所有数据存储在单个主节点,受限于单机内存支持槽位分片,16384个槽分布在多个主节点,实现水平扩展
高可用实现依赖独立Sentinel集群,通过Raft协议选举Leader执行故障转移内置高可用,主节点故障时从节点自动选举(基于Raft协议)
客户端访问客户端需通过Sentinel获取主节点地址,或直接连接主从(读扩散)客户端需支持槽位路由,或通过Proxy(如Codis)访问
脑裂防护需配置min-replicas-to-write参数,确保主节点有足够从节点同步通过quorum机制(超过半数主节点确认故障)避免脑裂
适用场景数据量较小(GB级),需高可用但无需水平扩展的场景数据量大(TB级),需水平扩展和高可用的分布式场景

三、数据流转时序图:从写操作到故障转移

3.1 Sentinel模式写操作与故障转移

客户端主节点从节点Sentinel集群其他SentinelLeader Sentinel其他Slave新主节点执行SET key value写入内存并记录AOF/RDB发送复制命令(PSYNC)同步数据并持久化复制确认正常监控流程定期发送PING命令返回PONG主节点故障无响应发送主节点故障通知发起Leader选举选举出Leader Sentinel执行SLAVEOF NO ONE(晋升主节点)晋升成功执行SLAVEOF 新主节点复制配置完成通知新主节点地址后续写操作指向新主节点客户端主节点从节点Sentinel集群其他SentinelLeader Sentinel其他Slave新主节点

3.2 Cluster模式写操作与故障转移

客户端主节点A(槽0-5460)从节点A主节点B(槽5461-10922)其他主节点其他从节点所有节点执行SET key value(key哈希到槽1000)检查槽1000是否归自己所有写入内存并记录AOF/RDB发送复制命令同步数据复制确认主节点A故障发送主节点A故障检测请求广播主节点A故障确认故障(超过半数)发起Leader选举(自身为候选人)发送投票请求投票确认晋升为新主节点广播新主节点信息后续槽1000的操作指向新主节点客户端主节点A(槽0-5460)从节点A主节点B(槽5461-10922)其他主节点其他从节点所有节点

四、源码解析:核心机制的实现原理

4.1 Sentinel故障转移源码(Redis 6.2)

Sentinel的故障转移核心逻辑在sentinel.c中,主要包括三个阶段:故障检测、Leader选举、故障转移执行。



// 1. 故障检测:Sentinel定期向主节点发送PING命令
void sentinelCheckMasterFlags(sentinelRedisInstance *ri) {
    if (ri->flags & SRI_MASTER) {
        if (sentinelIsSubjectivelyDown(ri)) { // 主观下线(单个Sentinel检测到故障)
            sentinelMarkAsSubjectivelyDown(ri);
            sentinelAskMasterStateToOtherSentinels(ri); // 询问其他Sentinel
        } else if (ri->flags & SRI_SDOWN) {
            sentinelClearSubjectivelyDown(ri);
        }
    }
}

// 2. Leader选举:基于Raft协议的投票机制
int sentinelElectLeader(sentinelRedisInstance *ri) {
    dictIterator *di;
    dictEntry *de;
    int votes = 0;
    // 向其他Sentinel发送投票请求
    di = dictGetIterator(sentinel.masters);
    while((de = dictNext(di)) != NULL) {
        sentinelRedisInstance *master = dictGetVal(de);
        if (master == ri) continue;
        if (sentinelSendVoteRequest(master, ri)) votes++;
    }
    dictReleaseIterator(di);
    // 获得超过半数投票则成为Leader
    return (votes > sentinelQuorum(ri)) ? 1 : 0;
}

// 3. 故障转移执行:选择从节点晋升为主节点
void sentinelFailoverStateMachine(sentinelRedisInstance *ri) {
    switch(ri->failover_state) {
        case SENTINEL_FAILOVER_STATE_SELECT_SLAVE:
            ri->failover_slave = sentinelSelectSlave(ri); // 选择最优从节点
            ri->failover_state = SENTINEL_FAILOVER_STATE_SEND_SLAVEOF_NOONE;
            break;
        case SENTINEL_FAILOVER_STATE_SEND_SLAVEOF_NOONE:
            redisAsyncCommand(ri->cc, sentinelFailoverSlaveOfNoOneReply, ri, "SLAVEOF NO ONE");
            ri->failover_state = SENTINEL_FAILOVER_STATE_WAIT_PROMOTION;
            break;
        // 后续步骤:设置新主节点地址、通知其他从节点同步等
    }
}
    

4.2 Cluster故障转移源码(Redis 6.2)

Cluster的故障转移逻辑在cluster.c中,核心是故障检测(Gossip协议)和从节点选举(Raft协议)。



// 1. Gossip协议:节点间交换集群状态
void clusterSendGossipPackets(void) {
    listNode *ln;
    listIter li;
    listRewind(server.cluster->nodes, &li);
    while((ln = listNext(&li)) != NULL) {
        clusterNode *node = ln->value;
        if (node->flags & (CLUSTER_NODE_MYSELF|CLUSTER_NODE_PFAIL)) continue;
        clusterSendGossipPacket(node); // 发送包含节点状态的Gossip包
    }
}

// 2. 故障检测:标记节点为客观下线
void clusterProcessGossipMessage(clusterNode *sender, clusterMsg *hdr) {
    clusterNode *node = clusterLookupNode(hdr->data.gossip.nodeid);
    if (node && hdr->data.gossip.pfail) {
        clusterNodeAddFailureReport(node, sender); // 记录故障报告
        if (clusterNodeIsMajorityPfail(node)) { // 超过半数主节点报告故障
            clusterMarkNodeAsFail(node); // 标记为客观下线
        }
    }
}

// 3. 从节点选举:基于Raft协议
int clusterHandleSlaveElection(clusterNode *master) {
    clusterNode *slave = server.cluster->myself;
    if (!(slave->flags & CLUSTER_NODE_SLAVE) || slave->slaveof != master) return 0;
    if (master->flags != CLUSTER_NODE_FAIL) return 0;
    
    // 发起选举请求
    clusterSendSlaveVoteRequests(master);
    // 等待投票结果
    if (slave->votes >= clusterQuorumForNode(master)) {
        clusterPromoteSlave(); // 晋升为新主节点
        return 1;
    }
    return 0;
}
    

五、工程实践案例

5.1 阿里电商:Sentinel模式在商品详情页缓存的应用

阿里电商商品详情页缓存初期采用Sentinel模式,原因是:商品详情数据量适中(单商品约1KB),总缓存量约50GB,单机128GB内存即可承载;且读请求量极大(每秒数十万QPS),通过「一主多从」实现读扩散。

优化点:为避免Sentinel故障转移期间的读抖动,客户端实现了「读多活」逻辑——同时连接多个从节点,当某个从节点不可用时自动切换到其他从节点;并通过「限流降级」机制,在主从切换期间将请求导流到DB。

5.2 字节跳动:Cluster模式在短视频计数服务的应用

字节跳动短视频的点赞、评论、播放量计数服务采用Cluster模式,原因是:单条视频计数数据量小(约100B),但总视频数超过10亿,总缓存量达TB级,需水平扩展;且计数操作需高并发写入(每秒百万级QPS)。

优化点:采用「预分片+动态扩缩容」策略,初始化时创建100个主节点槽位分布,后续根据数据量动态增加主节点;并通过「本地缓存+Redis Cluster」二级缓存,减少Redis访问压力。

六、大厂面试深度追问

追问1:Sentinel模式中,如何避免主从切换期间的数据丢失?

解决方案:需从「同步机制」和「故障转移策略」两方面优化:

  • 配置同步参数:设置min-replicas-to-write=2和min-replicas-max-lag=10,确保主节点至少有2个从节点的同步延迟不超过10秒,否则拒绝写操作,避免主节点故障时从节点数据过旧。

  • 优先选择最新从节点:在Sentinel的sentinelSelectSlave函数中,修改从节点选择策略,优先选择「复制偏移量最大」的从节点晋升为主节点,确保数据最新。

  • 启用AOF持久化:主从节点均启用AOF持久化,并配置appendfsync=everysec,确保数据写入磁盘的安全性。

追问2:Redis Cluster的槽位迁移过程中,如何保证数据一致性?

解决方案:Redis Cluster的槽位迁移采用「渐进式迁移」策略,核心步骤如下:

  1. 迁移准备:源主节点将槽位标记为「migrating」状态,目标主节点将槽位标记为「importing」状态,确保其他节点不会向该槽位写入数据。

  2. 数据迁移:源主节点通过MIGRATE命令逐个迁移键值对到目标主节点,迁移期间源主节点仍可处理该槽位的读请求,写请求则返回「ASK」重定向到目标主节点。

  3. 槽位更新:迁移完成后,源主节点将槽位状态设为「none」,目标主节点将槽位状态设为「assigned」,并通过Gossip协议广播槽位变更信息。

  4. 客户端适配:客户端接收到「ASK」重定向时,临时向目标主节点发送请求;接收到「MOVED」重定向时,更新本地槽位路由表,永久指向目标主节点。

追问3:如何设计一个同时支持高可用和水平扩展的Redis架构?

解决方案:推荐采用「Redis Cluster+Proxy+监控平台」的架构,具体如下:

  • 核心层:Redis Cluster:采用3主3从的Cluster集群,每个主节点负责5461个槽位,实现水平扩展和内置高可用。

  • 接入层:Proxy(如Twemproxy/Codis):客户端通过Proxy访问Redis,Proxy负责槽位路由、负载均衡和故障屏蔽,降低客户端复杂度。

  • 监控层:Prometheus+Grafana:监控集群槽位分布、节点状态、数据同步延迟等指标,设置告警阈值(如主从延迟超过5秒告警)。

  • 运维层:自动化工具:开发自动化扩缩容工具,支持一键添加主从节点、迁移槽位;开发故障自愈工具,自动处理节点故障和槽位异常。

七、总结

这篇博客涵盖了多方面技术要点与实践案例。你若对某个部分想进一步细化,或有新的面试问题想补充,欢迎随时告知。

更多推荐