
在分布式系统中,缓存是提升系统性能、降低数据库压力的核心组件,而Redis凭借超高的读写性能、丰富的数据结构,成为了缓存场景的首选。但单节点Redis存在无法回避的短板:单点故障会导致整个缓存服务不可用;单节点内存上限限制了数据存储规模;单节点无法应对高并发的读写压力。
Redis高可用架构的演进,正是为了解决这些核心问题,从最简单的主从复制,到具备自动故障转移的哨兵模式,再到支持分布式分片的集群模式,形成了一套完整的高可用解决方案。
高可用架构的设计,始终围绕四个核心目标展开,也是判断架构是否满足业务需求的核心标准:
主从复制是Redis高可用的基石,哨兵与集群架构的底层数据同步能力,都基于主从复制实现。它采用一主多从的架构,主节点负责处理写请求,从节点负责同步主节点的数据,并处理读请求。
主从复制的核心分为两个阶段:全量同步与增量同步,基于PSYNC命令实现,目前主流的PSYNC2.0版本,优化了断线重连后的同步逻辑,大幅降低了全量同步的触发概率。
全量同步是从节点首次连接主节点,或无法进行增量同步时触发的完整数据同步流程,核心是基于RDB快照实现:
PSYNC ? -1命令,请求进行全量同步。bgsave命令,在后台生成RDB快照文件。增量同步是从节点断线重连后,在满足条件的情况下,仅同步断线期间主节点执行的写命令,无需全量同步,大幅提升同步效率:
PSYNC <主节点runid> <自身复制偏移量>命令。
采用一主两从的架构,使用Redis 7.2.5版本,三个节点分别部署在6379(主)、6380(从)、6381(从)端口。
bind 0.0.0.0
port 6379
daemonize yes
pidfile /var/run/redis_6379.pid
logfile "redis-6379.log"
dbfilename dump-6379.rdb
dir /usr/local/redis/data
appendonly yes
appendfilename "appendonly-6379.aof"
aof-use-rdb-preamble yes
repl-backlog-size 104857600
repl-diskless-sync yes
bind 0.0.0.0
port 6380
daemonize yes
pidfile /var/run/redis_6380.pid
logfile "redis-6380.log"
dbfilename dump-6380.rdb
dir /usr/local/redis/data
appendonly yes
appendfilename "appendonly-6380.aof"
aof-use-rdb-preamble yes
replicaof 127.0.0.1 6379
replica-read-only yes
6381端口的从节点配置,仅需修改端口、pidfile、日志文件、rdb文件名等与端口相关的配置,replicaof配置保持一致。
redis-server redis-6379.conf
redis-server redis-6380.conf
redis-server redis-6381.conf
redis-cli -p 6379
127.0.0.1:6379> info replication
输出结果中,role:master,connected_slaves:2,表示两个从节点已成功连接,主从复制部署完成。
主从复制实现了读写分离与数据备份,但存在明显的短板:
哨兵模式(Redis Sentinel)在主从复制的基础上,增加了哨兵节点集群,实现了故障的自动发现与自动转移,解决了主从复制的核心痛点。
哨兵节点是专门用于监控Redis节点的独立进程,不存储业务数据,仅负责监控、选主、通知与配置管理,核心功能如下:
哨兵模式的核心逻辑分为四个阶段:主观下线判定、客观下线判定、哨兵领导者选举、故障转移执行。
单个哨兵节点检测到主节点在down-after-milliseconds配置的时间内,没有正确回复PING命令,就会将该主节点标记为主观下线。 主观下线是单个哨兵的判断,存在误判的可能,比如网络抖动导致单个哨兵与主节点通信异常,但主节点本身正常运行,因此需要多个哨兵的共同确认,才能进入客观下线阶段。
当哨兵将主节点标记为主观下线后,会向其他哨兵节点发送SENTINEL is-master-down-by-addr命令,询问其他哨兵是否也认为该主节点已经下线。 当收到同意下线的哨兵数量达到配置的quorum值时,该哨兵会将主节点标记为客观下线,正式进入故障转移流程。
故障转移操作只能由一个哨兵节点执行,因此需要在所有哨兵节点中选举出一个领导者,负责后续的故障转移操作。选举采用Raft一致性算法,核心规则是:
哨兵领导者选举完成后,会执行完整的故障转移流程,核心步骤如下:
slaveof no one命令,将其升级为主节点。slaveof <新主节点地址> <端口>命令,让它们复制新的主节点。
基于之前的一主两从架构,部署3个哨兵节点,分别运行在26379、26380、26381端口,形成三哨兵集群,避免哨兵单点故障。
bind 0.0.0.0
port 26379
daemonize yes
pidfile /var/run/redis-sentinel-26379.pid
logfile "sentinel-26379.log"
dir /usr/local/redis/data
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 30000
sentinel parallel-syncs mymaster 1
其他两个哨兵节点的配置,仅需修改端口、pidfile、日志文件名等与端口相关的配置,监控主节点的配置保持一致。
redis-sentinel sentinel-26379.conf
redis-sentinel sentinel-26380.conf
redis-sentinel sentinel-26381.conf
redis-cli -p 26379
127.0.0.1:26379> sentinel master mymaster
输出结果中,num-slaves:2,num-sentinels:3,表示哨兵已成功监控到主从节点与其他哨兵节点,部署完成。
基于JDK 17与Spring Boot 3.2.4,实现Redis哨兵模式的客户端接入,项目配置与代码如下。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.4</version>
<relativePath/>
</parent>
<groupId>com.jam.demo</groupId>
<artifactId>redis-sentinel-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>redis-sentinel-demo</name>
<properties>
<java.version>17</java.version>
<fastjson2.version>2.0.52</fastjson2.version>
<guava.version>33.1.0-jre</guava.version>
<mybatis-plus.version>3.5.6</mybatis-plus.version>
<springdoc.version>2.5.0</springdoc.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${springdoc.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>${fastjson2.version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.32</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
spring:
data:
redis:
sentinel:
master:mymaster
nodes:
-127.0.0.1:26379
-127.0.0.1:26380
-127.0.0.1:26381
lettuce:
pool:
max-active:8
max-idle:8
min-idle:2
max-wait:-1ms
datasource:
url:jdbc:mysql://127.0.0.1:3306/demo_db?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false
username:root
password:root
driver-class-name:com.mysql.cj.jdbc.Driver
springdoc:
swagger-ui:
path:/swagger-ui.html
api-docs:
path:/v3/api-docs
package com.jam.demo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* Redis配置类
* @author ken
*/
@Configuration
publicclass RedisConfig {
/**
* 配置RedisTemplate,设置序列化规则
* @param connectionFactory Redis连接工厂
* @return RedisTemplate实例
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setHashKeySerializer(stringRedisSerializer);
GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
redisTemplate.setValueSerializer(jsonRedisSerializer);
redisTemplate.setHashValueSerializer(jsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
package com.jam.demo.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
/**
* 用户实体类
* @author ken
*/
@Data
@TableName("t_user")
@Schema(description = "用户实体")
publicclass User implements Serializable {
privatestaticfinallong serialVersionUID = 1L;
@TableId(type = IdType.AUTO)
@Schema(description = "用户ID", example = "1")
private Long id;
@Schema(description = "用户名", example = "zhangsan")
private String username;
@Schema(description = "年龄", example = "20")
private Integer age;
@Schema(description = "邮箱", example = "zhangsan@example.com")
private String email;
}
package com.jam.demo.controller;
import com.jam.demo.entity.User;
import com.jam.demo.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* 用户管理Controller
* @author ken
*/
@Slf4j
@RestController
@RequestMapping("/user")
@Tag(name = "用户管理", description = "用户相关操作接口")
publicclass UserController {
@Autowired
private UserService userService;
/**
* 根据ID查询用户信息
* @param id 用户ID
* @return 用户信息
*/
@GetMapping("/{id}")
@Operation(summary = "查询用户", description = "根据用户ID查询用户详情")
public User getUserById(
@Parameter(description = "用户ID", required = true, example = "1")
@PathVariable Long id) {
return userService.getUserById(id);
}
/**
* 新增用户
* @param user 用户信息
* @return 新增结果
*/
@PostMapping
@Operation(summary = "新增用户", description = "创建新的用户信息")
public Boolean addUser(@RequestBody User user) {
return userService.addUser(user);
}
/**
* 更新用户信息
* @param user 用户信息
* @return 更新结果
*/
@PutMapping
@Operation(summary = "更新用户", description = "更新已有的用户信息")
public Boolean updateUser(@RequestBody User user) {
return userService.updateUser(user);
}
/**
* 删除用户信息
* @param id 用户ID
* @return 删除结果
*/
@DeleteMapping("/{id}")
@Operation(summary = "删除用户", description = "根据用户ID删除用户信息")
public Boolean deleteUser(
@Parameter(description = "用户ID", required = true, example = "1")
@PathVariable Long id) {
return userService.deleteUser(id);
}
}
package com.jam.demo.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.jam.demo.entity.User;
import com.jam.demo.mapper.UserMapper;
import com.jam.demo.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import java.util.concurrent.TimeUnit;
/**
* 用户服务实现类
* @author ken
*/
@Slf4j
@Service
publicclass UserServiceImpl implements UserService {
privatestaticfinal String USER_CACHE_PREFIX = "user:info:";
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private UserMapper userMapper;
@Autowired
private PlatformTransactionManager transactionManager;
@Override
public User getUserById(Long id) {
if (ObjectUtils.isEmpty(id)) {
log.warn("查询用户信息,用户ID为空");
returnnull;
}
String cacheKey = USER_CACHE_PREFIX + id;
User user = (User) redisTemplate.opsForValue().get(cacheKey);
if (!ObjectUtils.isEmpty(user)) {
log.info("从缓存中查询到用户信息,用户ID:{}", id);
return user;
}
user = userMapper.selectById(id);
if (!ObjectUtils.isEmpty(user)) {
redisTemplate.opsForValue().set(cacheKey, user, 30, TimeUnit.MINUTES);
log.info("从数据库中查询到用户信息,写入缓存,用户ID:{}", id);
}
return user;
}
@Override
public Boolean addUser(User user) {
if (ObjectUtils.isEmpty(user) || !StringUtils.hasText(user.getUsername())) {
log.warn("新增用户信息,参数不合法");
return Boolean.FALSE;
}
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
TransactionStatus status = transactionManager.getTransaction(def);
try {
int result = userMapper.insert(user);
if (result > 0) {
String cacheKey = USER_CACHE_PREFIX + user.getId();
redisTemplate.opsForValue().set(cacheKey, user, 30, TimeUnit.MINUTES);
transactionManager.commit(status);
log.info("新增用户信息成功,用户ID:{}", user.getId());
return Boolean.TRUE;
}
transactionManager.rollback(status);
return Boolean.FALSE;
} catch (Exception e) {
transactionManager.rollback(status);
log.error("新增用户信息异常,用户信息:{}", user, e);
thrownew RuntimeException("新增用户信息异常", e);
}
}
@Override
public Boolean updateUser(User user) {
if (ObjectUtils.isEmpty(user) || ObjectUtils.isEmpty(user.getId())) {
log.warn("更新用户信息,参数不合法");
return Boolean.FALSE;
}
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
TransactionStatus status = transactionManager.getTransaction(def);
try {
int result = userMapper.updateById(user);
if (result > 0) {
String cacheKey = USER_CACHE_PREFIX + user.getId();
redisTemplate.opsForValue().set(cacheKey, user, 30, TimeUnit.MINUTES);
transactionManager.commit(status);
log.info("更新用户信息成功,用户ID:{}", user.getId());
return Boolean.TRUE;
}
transactionManager.rollback(status);
return Boolean.FALSE;
} catch (Exception e) {
transactionManager.rollback(status);
log.error("更新用户信息异常,用户信息:{}", user, e);
thrownew RuntimeException("更新用户信息异常", e);
}
}
@Override
public Boolean deleteUser(Long id) {
if (ObjectUtils.isEmpty(id)) {
log.warn("删除用户信息,用户ID为空");
return Boolean.FALSE;
}
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
TransactionStatus status = transactionManager.getTransaction(def);
try {
int result = userMapper.deleteById(id);
if (result > 0) {
String cacheKey = USER_CACHE_PREFIX + id;
redisTemplate.delete(cacheKey);
transactionManager.commit(status);
log.info("删除用户信息成功,用户ID:{}", id);
return Boolean.TRUE;
}
transactionManager.rollback(status);
return Boolean.FALSE;
} catch (Exception e) {
transactionManager.rollback(status);
log.error("删除用户信息异常,用户ID:{}", id, e);
thrownew RuntimeException("删除用户信息异常", e);
}
}
}
package com.jam.demo.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jam.demo.entity.User;
import org.apache.ibatis.annotations.Mapper;
/**
* 用户Mapper接口
* @author ken
*/
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
package com.jam.demo.service;
import com.jam.demo.entity.User;
/**
* 用户服务接口
* @author ken
*/
publicinterface UserService {
User getUserById(Long id);
Boolean addUser(User user);
Boolean updateUser(User user);
Boolean deleteUser(Long id);
}
package com.jam.demo;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 项目启动类
* @author ken
*/
@SpringBootApplication
@MapperScan("com.jam.demo.mapper")
publicclass RedisSentinelDemoApplication {
public static void main(String[] args) {
SpringApplication.run(RedisSentinelDemoApplication.class, args);
}
}
哨兵模式实现了自动故障转移,解决了主从复制的单点故障问题,但依然存在无法回避的短板:
Redis Cluster是Redis官方提供的分布式集群解决方案,采用去中心化的架构,将数据分散存储在多个主节点上,每个主节点负责一部分数据,同时每个主节点都配有从节点,实现故障自动转移,彻底解决了单节点的存储与性能瓶颈。
Redis Cluster的核心是哈希槽分片机制与去中心化的Gossip通信协议,实现了数据的分布式存储与节点的状态同步。
Redis Cluster将整个数据空间划分为16384个哈希槽(Hash Slot),每个key都会映射到其中一个槽位,每个主节点负责管理一部分槽位,核心规则如下:
CRC16(key) % 16384计算出该key对应的槽位编号,然后将数据写入负责该槽位的主节点。MOVED重定向指令,告诉客户端该槽位对应的节点地址,客户端会自动重定向到目标节点执行请求。
这是集群模式中最容易混淆的两个概念,核心区别如下:
Redis Cluster采用去中心化的架构,没有专门的配置中心节点,所有节点之间通过Gossip协议进行通信,同步节点状态、槽位分配、故障信息等,核心通信消息类型如下:
Redis Cluster的故障转移流程与哨兵模式类似,但由集群节点自身完成,无需单独的哨兵节点,核心流程如下:
cluster-node-timeout配置的时间内没有回复PONG消息,节点A会将节点B标记为主观下线。采用3主3从的架构,6个节点分别运行在7001-7006端口,其中7001、7002、7003为主节点,7004、7005、7006分别为对应的从节点。
bind 0.0.0.0
port 7001
daemonize yes
pidfile /var/run/redis-7001.pid
logfile "redis-7001.log"
dbfilename dump-7001.rdb
dir /usr/local/redis/cluster/data
appendonly yes
appendfilename "appendonly-7001.aof"
aof-use-rdb-preamble yes
cluster-enabled yes
cluster-config-file nodes-7001.conf
cluster-node-timeout 15000
cluster-require-full-coverage no
其他5个节点的配置,仅需修改端口、pidfile、日志文件、rdb文件名、cluster-config-file等与端口相关的配置即可。
redis-server redis-7001.conf
redis-server redis-7002.conf
redis-server redis-7003.conf
redis-server redis-7004.conf
redis-server redis-7005.conf
redis-server redis-7006.conf
redis-cli --cluster create 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 127.0.0.1:7006 --cluster-replicas 1
执行命令后,输入yes确认槽位分配,集群创建完成。 3. 验证集群状态:
redis-cli -c -p 7001
127.0.0.1:7001> cluster info
输出结果中,cluster_state:ok,表示集群状态正常;cluster_slots_assigned:16384,表示所有槽位都已分配。
基于JDK 17与Spring Boot 3.2.4,实现Redis Cluster集群的客户端接入,核心配置如下,业务代码与哨兵模式完全一致,无需修改。
spring:
data:
redis:
cluster:
nodes:
-127.0.0.1:7001
-127.0.0.1:7002
-127.0.0.1:7003
-127.0.0.1:7004
-127.0.0.1:7005
-127.0.0.1:7006
max-redirects:3
lettuce:
pool:
max-active:16
max-idle:16
min-idle:4
max-wait:-1ms
架构类型 | 核心定位 | 核心优势 | 核心劣势 | 节点规模 | 数据分片 | 故障转移 | 适用场景 |
|---|---|---|---|---|---|---|---|
主从复制 | 基础数据同步与读写分离 | 部署简单、运维成本低、读写分离 | 无自动故障转移、单节点存储与性能瓶颈 | 3-5节点 | 不支持 | 人工手动切换 | 小型项目、测试环境、数据量小、并发量低的场景 |
哨兵模式 | 中小规模自动故障转移高可用 | 自动故障转移、部署简单、运维成本低、读写分离 | 单节点存储与性能瓶颈、写能力无法扩展 | 3-10节点 | 不支持 | 哨兵集群自动切换 | 中小型项目、数据量在10GB以内、并发量中等、需要自动故障转移的场景 |
集群模式 | 大规模分布式高可用 | 分布式分片、线性扩容、读写性能水平扩展、自动故障转移 | 部署复杂、运维成本高、跨槽操作有限制 | 6节点起步,最大支持1000+节点 | 16384哈希槽分片 | 集群节点自动切换 | 中大型项目、数据量超过20GB、高并发读写、需要线性扩容的场景 |
脑裂是指由于网络分区,导致集群中出现两个主节点,同时处理写请求,最终导致数据不一致的问题。核心优化方案如下:
min-replicas-to-write 1,要求主节点至少有1个正常连接的从节点,否则拒绝处理写请求,避免网络分区时主节点继续写入数据,导致数据丢失。min-replicas-max-lag 10,要求从节点的复制延迟不能超过10秒,否则主节点拒绝处理写请求,保证主从数据的一致性。down-after-milliseconds与集群模式的cluster-node-timeout,建议设置为5000-15000毫秒,避免网络抖动导致的误判,同时保证故障转移的及时性。repl-diskless-sync yes,主节点生成RDB时直接通过网络发送给从节点,无需落盘,大幅降低磁盘IO压力,提升全量同步的性能。repl-backlog-size调大至100MB,减少断线重连后全量同步的触发概率,降低主节点的性能开销。MGET、MSET等命令,如果key分布在不同的槽位,会报错。可以使用hashtag技术,将相关的key强制分配到同一个槽位,格式为{hashtag}key,计算槽位时只会使用大括号内的内容。cluster-require-full-coverage no,避免当某个主节点故障,槽位没有接管时,整个集群无法提供服务,保证部分槽位故障时,其他槽位依然可以正常使用。aof-use-rdb-preamble yes,结合RDB与AOF的优势,RDB用于快速恢复数据,AOF用于保证数据不丢失,平衡性能与数据安全性。save 3600 1,每小时生成一次RDB快照,避免频繁生成RDB导致的磁盘IO压力。appendfsync everysec,每秒刷盘一次,平衡性能与数据安全性,最多丢失1秒的数据,满足绝大多数业务的要求。Redis高可用架构的选型,本质上是在业务需求、性能、成本、运维复杂度之间做平衡。主从复制是基础,哨兵模式解决了自动故障转移的核心痛点,集群模式则实现了海量数据的分布式存储与线性扩容。理解三种架构的底层原理,掌握核心的优化与避坑方案,才能根据业务的实际场景,选择最合适的架构,搭建出稳定、高性能、高可用的Redis服务。