引言:为什么Redis如此重要?
Redis从一个小众的缓存工具成长为如今不可或缺的分布式系统组件。在现代应用架构中,Redis已经远远超越了简单的缓存角色,成为了高性能数据存储、消息队列、会话存储等多功能于一体的核心技术。
记得在早期项目中,我们常常使用本地缓存或者关系型数据库来处理热点数据,经常面临性能瓶颈和扩展性问题。自从引入Redis后,系统的响应时间从几百毫秒降低到个位数,这种性能提升带来的用户体验改善是颠覆性的。
一、Redis核心特性解析
1.1 内存存储的威力
Redis之所以快,首要原因在于它是基于内存的存储系统。与传统磁盘存储相比,内存的读写速度要快几个数量级。但Redis的强大之处不仅仅在于此:
java
// 传统文件操作 vs Redis操作
long start = System.currentTimeMillis();
// 传统方式:文件读写
File file = new File("data.txt");
FileWriter writer = new FileWriter(file);
writer.write("Hello World");
writer.close();
// Redis方式(Jedis 是 Java 语言操作 Redis 数据库的官方推荐客户端,轻量且高效,能直接对接 Redis 命令。)
Jedis jedis = new Jedis("localhost");
jedis.set("greeting", "Hello World");
System.out.println("Redis操作耗时: " + (System.currentTimeMillis() - start) + "ms");
1.2 单线程架构的精妙设计
很多人对Redis的单线程模型存在误解,认为这是性能瓶颈。实际上,这正是Redis的设计精妙之处:
- 避免上下文切换:单线程避免了多线程的上下文切换开销
- 原子性操作:所有命令都是原子执行的
- 非阻塞I/O:使用多路复用技术处理并发连接
二、Redis数据结构深度剖析
Jedis 是 Java 语言操作 Redis 数据库的官方推荐客户端,轻量且高效,能直接对接 Redis 命令。
核心特点
- API 设计简洁,与 Redis 原生命令高度一致,上手成本低。
- 性能优异,无过多封装,响应速度快,适合高性能场景。
- 支持 Redis 全特性,包括字符串、哈希、列表等数据结构,以及事务、管道、发布订阅等功能。
接下来,我们使用的都是Jedis库来进行代码示例。
2.1 String:不仅仅是字符串
String是Redis最基本的数据类型,但它的能力远超普通字符串:
java
// 计数器场景 - 文章阅读量
public class ArticleViewCounter {
private Jedis jedis;
public ArticleViewCounter() {
this.jedis = new Jedis("localhost");
}
public void incrementView(Long articleId) {
String key = "article:view:" + articleId;
// INCR命令是原子操作,完美解决并发问题
jedis.incr(key);
}
public Long getViews(Long articleId) {
String key = "article:view:" + articleId;
String views = jedis.get(key);
return views != null ? Long.parseLong(views) : 0L;
}
}
2.2 Hash:对象存储的最佳选择
Hash类型特别适合存储对象,相比String的JSON序列化,它在更新和存储效率上更有优势:
java
public class UserService {
private Jedis jedis;
public void saveUser(User user) {
String key = "user:" + user.getId();
Map<String, String> userMap = new HashMap<>();
userMap.put("name", user.getName());
userMap.put("email", user.getEmail());
userMap.put("age", String.valueOf(user.getAge()));
jedis.hset(key, userMap);
// 设置过期时间 - 24小时
jedis.expire(key, 24 * 60 * 60);
}
public User getUser(Long id) {
String key = "user:" + id;
Map<String, String> userMap = jedis.hgetAll(key);
if (userMap.isEmpty()) {
return null;
}
User user = new User();
user.setId(id);
user.setName(userMap.get("name"));
user.setEmail(userMap.get("email"));
user.setAge(Integer.parseInt(userMap.get("age")));
return user;
}
}
2.3 List:消息队列的轻量级实现
Redis List可以实现简单的消息队列,特别适合处理峰值流量:
java
public class EmailQueue {
private static final String QUEUE_KEY = "queue:emails";
private Jedis jedis;
public void sendEmail(String to, String subject, String content) {
EmailMessage message = new EmailMessage(to, subject, content);
String messageJson = objectToJson(message);
// LPUSH + RPOP 实现队列
jedis.lpush(QUEUE_KEY, messageJson);
}
public void processEmails() {
while (true) {
// BRPOP 是阻塞版本,避免频繁轮询
List<String> messages = jedis.brpop(30, QUEUE_KEY);
if (messages != null) {
String messageJson = messages.get(1);
EmailMessage message = jsonToObject(messageJson, EmailMessage.class);
sendActualEmail(message);
}
}
}
}
2.4 Set:去重与集合运算
Set类型的去重特性和集合运算能力,在很多场景下非常实用:
java
public class SocialMediaService {
private Jedis jedis;
// 共同关注功能
public Set<String> getMutualFollowers(Long userId1, Long userId2) {
String key1 = "user:followers:" + userId1;
String key2 = "user:followers:" + userId2;
// SINTER 命令求交集
return jedis.sinter(key1, key2);
}
// 推荐可能认识的人
public Set<String> recommendFriends(Long userId) {
String userKey = "user:followers:" + userId;
// 获取用户的所有关注者
Set<String> followers = jedis.smembers(userKey);
Set<String> recommendations = new HashSet<>();
for (String follower : followers) {
String followerKey = "user:followers:" + follower;
// 求差集:关注者的关注者中,排除已关注的和自己
Set<String> potential = jedis.sdiff(followerKey, userKey);
potential.remove(userId.toString());
recommendations.addAll(potential);
}
return recommendations;
}
}
2.5 Sorted Set:排行榜的实现利器
Sorted Set是实现排行榜功能的完美选择:
java
public class Leaderboard {
private static final String LEADERBOARD_KEY = "leaderboard:game";
private Jedis jedis;
public void addScore(String player, double score) {
jedis.zadd(LEADERBOARD_KEY, score, player);
}
public List<PlayerScore> getTopPlayers(int limit) {
Set<Tuple> topScores = jedis.zrevrangeWithScores(LEADERBOARD_KEY, 0, limit - 1);
return topScores.stream()
.map(tuple -> new PlayerScore(tuple.getElement(), tuple.getScore()))
.collect(Collectors.toList());
}
public Long getPlayerRank(String player) {
// 排名从0开始,所以+1得到实际排名
Long rank = jedis.zrevrank(LEADERBOARD_KEY, player);
return rank != null ? rank + 1 : null;
}
}
三、Redis持久化机制对比
3.1 RDB:快照持久化
RDB是Redis默认的持久化方式,通过创建数据集的快照来工作:
优点:
- 文件紧凑,适合备份和灾难恢复
- 最大化Redis性能,父进程不需要磁盘I/O
- 恢复大数据集时比AOF更快
缺点:
- 可能丢失最后一次快照之后的数据
- 数据集很大时,fork过程可能耗时较长
3.2 AOF:追加式持久化
AOF记录每个写操作,提供更强的持久性保证:
配置示例:
text
appendonly yes
appendfsync everysec # 平衡性能和数据安全
AOF重写机制:
随着时间推移,AOF文件会变大,Redis会定期重写AOF文件以压缩体积。
四、Spring Boot集成Redis实战
4.1 基础配置
yaml
# application.yml
spring:
redis:
host: localhost
port: 6379
password:
database: 0
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
max-wait: -1ms
timeout: 2000ms
4.2 配置类详解
java
@Configuration
@EnableCaching
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
Jackson2JsonRedisSerializer<Object> serializer =
new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.activateDefaultTyping(
mapper.getPolymorphicTypeValidator(),
ObjectMapper.DefaultTyping.NON_FINAL
);
serializer.setObjectMapper(mapper);
template.setValueSerializer(serializer);
template.setKeySerializer(new StringRedisSerializer());
template.afterPropertiesSet();
return template;
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(10)) // 默认过期时间
.disableCachingNullValues(); // 不缓存空值
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
}
}
4.3 使用Spring Cache注解
java
@Service
public class ProductService {
@Cacheable(value = "products", key = "#id")
public Product getProductById(Long id) {
// 模拟数据库查询
return productRepository.findById(id)
.orElseThrow(() -> new ProductNotFoundException(id));
}
@CachePut(value = "products", key = "#product.id")
public Product updateProduct(Product product) {
return productRepository.save(product);
}
@CacheEvict(value = "products", key = "#id")
public void deleteProduct(Long id) {
productRepository.deleteById(id);
}
@Caching(evict = {
@CacheEvict(value = "products", key = "#id"),
@CacheEvict(value = "productList", allEntries = true)
})
public void refreshProductCache(Long id) {
// 同时清除单个产品和产品列表缓存
}
}
4.4 自定义Redis工具类
java
@Component
public class RedisUtil {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 设置缓存
*/
public void set(String key, Object value, long time) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
}
/**
* 获取缓存
*/
public Object get(String key) {
return redisTemplate.opsForValue().get(key);
}
/**
* 删除缓存
*/
public Boolean delete(String key) {
return redisTemplate.delete(key);
}
/**
* 批量删除
*/
public Long delete(Collection<String> keys) {
return redisTemplate.delete(keys);
}
/**
* 实现分布式锁
*/
public Boolean tryLock(String key, String value, long expire) {
return redisTemplate.opsForValue()
.setIfAbsent(key, value, Duration.ofSeconds(expire));
}
/**
* 释放分布式锁
*/
public Boolean releaseLock(String key, String value) {
String currentValue = (String) redisTemplate.opsForValue().get(key);
if (Objects.equals(currentValue, value)) {
return redisTemplate.delete(key);
}
return false;
}
}
五、Redis高级特性与最佳实践
5.1 管道技术提升性能
Redis 管道技术(Pipeline)是一种优化 Redis 客户端与服务器通信效率的机制,通过批量发送多个命令并一次性接收响应,减少因频繁网络往返(Round Trip)带来的性能开销。
为什么需要管道技术?
- 普通模式下,客户端发送一个命令后需等待服务器响应,再发送下一个命令,存在大量网络延迟(尤其客户端与服务器跨网络时)。
- 管道技术允许客户端一次性发送多个命令,服务器依次执行后批量返回结果,将多次网络往返压缩为一次,大幅提升批量操作效率。
管道技术的适用场景
- 需要执行大量连续的 Redis 命令(如批量插入、批量查询)。
- 命令之间无依赖关系(即后一个命令不需要前一个命令的结果作为参数)。
- 对执行效率要求高,希望减少网络开销的场景(如数据迁移、批量统计)。
java
@Service
public class BatchOperationService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public void batchInsertUsers(List<User> users) {
redisTemplate.executePipelined(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
for (User user : users) {
String key = "user:" + user.getId();
byte[] keyBytes = redisTemplate.getStringSerializer().serialize(key);
byte[] valueBytes = redisTemplate.getValueSerializer().serialize(user);
connection.set(keyBytes, valueBytes);
}
return null;
}
});
}
}
5.2 Lua脚本保证原子性
在 Redis 中,Lua 脚本是保证复杂操作原子性的重要手段。Redis 会将整个 Lua 脚本作为一个不可分割的执行单元,在脚本执行期间,不会被其他客户端的命令打断,从而确保脚本内所有操作的原子性。
为什么 Lua 脚本能保证原子性?
Redis 执行 Lua 脚本时,会进入单线程执行模式:
- 从脚本开始执行到结束,Redis 不会处理其他任何客户端的命令请求。
- 脚本内的所有命令会连续执行,中间不会插入其他操作,因此不会出现 “部分执行” 的情况。
这种特性使得 Lua 脚本非常适合处理多步依赖操作(如 “先判断再修改”“批量操作 + 条件逻辑” 等),避免了普通命令因分步执行可能导致的竞态问题。
java
@Service
public class InventoryService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private static final String DEDUCT_SCRIPT =
"local current = tonumber(redis.call('get', KEYS[1])) " +
"if current == nil then " +
" return -1 " +
"end " +
"if current < tonumber(ARGV[1]) then " +
" return -2 " +
"end " +
"redis.call('decrby', KEYS[1], ARGV[1]) " +
"return 0";
public boolean deductInventory(String productId, int quantity) {
DefaultRedisScript<Long> script = new DefaultRedisScript<>();
script.setScriptText(DEDUCT_SCRIPT);
script.setResultType(Long.class);
Long result = redisTemplate.execute(script,
Collections.singletonList("inventory:" + productId),
String.valueOf(quantity));
return result == 0;
}
}
5.3 缓存问题解决方案
5.3.1 缓存穿透
缓存穿透是指客户端请求的数据在缓存(如 Redis)和数据库中都不存在,导致请求每次都穿透缓存直达数据库,从而给数据库带来巨大压力(尤其在高并发场景下)。
缓存穿透的危害
- 数据库频繁处理无效请求,可能被压垮(如恶意攻击时,大量请求查询不存在的数据)。
- 缓存失去作用,系统整体性能急剧下降。
产生原因
- 业务逻辑问题:用户查询不存在的业务数据(如查询 ID 为 -1 的用户)。
- 恶意攻击:故意构造大量不存在的 key 发起请求,消耗数据库资源。
- 数据过期 / 删除:缓存和数据库中都已删除的数据,仍有请求访问。
解决方案
针对缓存穿透,常见的解决策略有以下几种:
1. 缓存空值(空对象)
- 原理:当数据库查询结果为空时,仍将这个空结果(如
null或特定标记)缓存起来,并设置较短的过期时间(避免长期占用缓存空间)。 - 效果:后续相同的请求会直接命中缓存中的空值,不再访问数据库。
- 示例:查询
user:10086时,若数据库无此用户,缓存user:10086 → null,过期时间设为 5 分钟。 - 注意:需合理设置过期时间,避免缓存大量空值导致内存浪费。
2. 布隆过滤器(Bloom Filter)
-
原理:在缓存层之前增加一个布隆过滤器,预先存储所有存在的 key(如数据库中已有的用户 ID、商品 ID)。请求到来时,先通过布隆过滤器判断 key 是否存在:
- 若不存在,直接返回空(无需访问缓存和数据库);
- 若可能存在(布隆过滤器有极小误判率),再走正常的缓存 + 数据库流程。
-
优势:占用内存小、查询速度快,适合海量数据场景(如亿级 key)。
-
注意:
- 布隆过滤器存在误判(可能把不存在的 key 判定为存在),但不会漏判(存在的 key 一定能判定为存在)。
- 需要定期同步数据库中的新增 / 删除 key 到布隆过滤器。
3. 接口层限流与校验
- 原理:在接口层对请求进行合法性校验(如 ID 格式、范围),过滤明显无效的请求(如负数 ID、超范围 ID)。
- 示例:用户 ID 为正整数,直接拦截 ID ≤ 0 的请求。
- 补充:结合限流措施(如 Redis 限流),防止恶意请求洪水攻击。
4. 数据预热与主动缓存
- 原理:对于核心业务数据,提前加载到缓存中;对于新增数据,在写入数据库时同步更新缓存,避免缓存中出现 “应该存在却不存在” 的 key。
java
@Service
public class CachePenetrationSolution {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public Object getDataWithBloomFilter(String key) {
// 1. 先查询布隆过滤器
if (!bloomFilter.mightContain(key)) {
return null;
}
// 2. 查询缓存
Object value = redisTemplate.opsForValue().get(key);
if (value != null) {
return value;
}
// 3. 查询数据库
value = database.get(key);
if (value != null) {
redisTemplate.opsForValue().set(key, value, 30, TimeUnit.MINUTES);
} else {
// 缓存空值,防止穿透
redisTemplate.opsForValue().set(key, "", 5, TimeUnit.MINUTES);
}
return value;
}
}
5.3.2 缓存雪崩
缓存雪崩是指大量缓存数据在同一时间过期失效,或缓存服务突然宕机,导致所有请求瞬间穿透到数据库,造成数据库压力骤增甚至崩溃的现象。
核心成因
- 缓存集中过期:批量设置缓存时使用了相同的过期时间(如凌晨 1 点统一过期),到期后大量请求直达数据库。
- 缓存服务故障:Redis 等缓存集群宕机、网络中断,导致缓存完全不可用。
- 热点数据集中失效:某类热点数据(如促销商品)缓存过期,且并发请求量极大。
解决方案
1. 避免缓存集中过期
- 给缓存过期时间添加随机偏移量(如基础过期 1 小时,再加 0-30 分钟随机值),分散过期时间点。
- 核心数据设置永不过期,通过后台异步任务定期更新(如每天凌晨低峰期刷新)。
- 分层缓存设计:不同层级缓存设置不同过期时间(如本地缓存 + 分布式缓存),减少同一时间失效概率。
2. 提升缓存服务可用性
- 搭建缓存集群(如 Redis 主从 + 哨兵、Redis Cluster),避免单点故障。
- 开启缓存服务的持久化(RDB+AOF),宕机后可快速恢复数据。
- 配置熔断降级机制:当缓存服务响应超时,暂时返回默认值或降级服务,避免雪崩扩散。
3. 流量控制与兜底
- 数据库层添加限流措施(如使用 Sentinel、Nginx 限流),限制并发请求数。
- 接口层设置降级开关:缓存雪崩时,关闭非核心功能接口,优先保障核心业务。
- 本地缓存兜底:关键接口在应用本地维护一份临时缓存(如 Caffeine),缓存失效时先返回本地数据。
4. 热点数据特殊防护
- 热点数据缓存过期时间设为永久,或延长至远大于业务周期。
- 针对热点 key,提前预热缓存(如促销活动前手动加载数据到缓存)。
- 使用 “互斥锁”:缓存失效时,只允许一个线程去数据库查询并更新缓存,其他线程等待重试,避免并发穿透。
java
@Service
public class CacheAvalancheSolution {
public void setDataWithRandomExpire(String key, Object value) {
// 设置随机过期时间,避免大量key同时过期
Random random = new Random();
int expireTime = 1800 + random.nextInt(600); // 30-40分钟随机
redisTemplate.opsForValue().set(key, value, expireTime, TimeUnit.SECONDS);
}
}
六、Redis主从复制
Redis 主从复制(Master-Slave Replication)是实现高可用和读写分离的基础,通过将主节点(Master)的数据同步到从节点(Slave),可分担主节点的读压力,同时在主节点故障时提供数据备份。
核心原理
- 数据同步:从节点主动连接主节点,主节点将数据全量同步给从节点,之后通过增量同步(命令传播)保持数据一致。
- 读写分离:主节点负责写操作,从节点负责读操作(默认从节点只读),提升整体吞吐量。
- 故障备份:主节点故障时,可手动或通过哨兵(Sentinel)将从节点晋升为主节点。
主从复制配置步骤
假设我们有 3 台服务器(或同一服务器的不同端口),规划如下:
- 主节点(Master):
192.168.1.100:6379 - 从节点 1(Slave1):
192.168.1.101:6379 - 从节点 2(Slave2):
192.168.1.102:6379
6.1 准备工作
- 所有节点安装相同版本的 Redis(版本兼容很重要,建议主从版本一致)。
- 确保主从节点之间网络互通(关闭防火墙或开放 Redis 端口)。
- 主节点无需特殊配置(默认允许被从节点连接),但建议设置密码(
requirepass)增强安全性。
6.2 主节点(Master)配置
修改主节点的 redis.conf 配置文件:
conf
# 绑定地址(允许所有IP访问,生产环境建议指定具体IP)
bind 0.0.0.0
# 端口
port 6379
# 设置密码(可选,建议设置)
requirepass "master_password"
# 开启持久化(可选,防止主节点重启后数据丢失)
appendonly yes
appendfilename "appendonly.aof"
重启主节点使配置生效:
bash
redis-server /path/to/redis.conf
6.3 从节点(Slave)配置
从节点需指定主节点的地址、端口和密码,有两种配置方式:
方式 1:修改 redis.conf(永久生效)
编辑从节点的 redis.conf:
conf
# 绑定地址
bind 0.0.0.0
# 端口(与主节点不同,避免冲突)
port 6379
# 配置主节点信息
replicaof 192.168.1.100 6379 # 主节点IP和端口
masterauth "master_password" # 主节点的密码(若主节点设置了密码)
# 从节点只读(默认开启,建议保持)
replica-read-only yes
重启从节点:
bash
redis-server /path/to/redis.conf
6.4 验证主从关系
-
在主节点执行,查看从节点列表:
bash
redis-cli -h 192.168.1.100 -p 6379 -a "master_password" 192.168.1.100:6379> info replication输出中
slave0、slave1会显示从节点的 IP、端口和状态(online表示正常)。 -
在从节点执行,查看主节点信息:
bash
redis-cli -h 192.168.1.101 -p 6379 192.168.1.101:6379> info replication输出中
master_host、master_port会显示主节点信息,master_link_status:up表示连接正常。
6.5 测试数据同步
-
在主节点设置一个键值对:
bash
192.168.1.100:6379> set test "hello replication" OK -
在从节点查询该键,验证是否同步成功:
bash
192.168.1.101:6379> get test "hello replication" # 成功同步
6.6 进阶配置与注意事项
-
级联复制:从节点可以再作为其他从节点的主节点(如 Master → Slave1 → Slave2),减轻主节点的同步压力。只需在 Slave2 的配置中设置
replicaof 192.168.1.101 6379即可。 -
主节点写负载:主节点需要处理所有写操作和同步请求,高并发场景下建议减少主节点数量(通常 1 主多从)。
-
数据延迟:从节点数据同步存在轻微延迟(毫秒级),不适合强一致性场景(如金融交易的实时对账)。
-
主节点故障处理:
- 手动切换:在从节点执行
replicaof no one,使其成为新主节点,其他从节点再指向新主节点。 - 自动切换:结合 Redis 哨兵(Sentinel)实现主从自动故障转移(推荐生产环境使用)。
- 手动切换:在从节点执行
-
安全配置:
- 主从节点都建议设置密码(
requirepass和masterauth)。 - 限制
replicaof命令只能在配置文件中设置,避免客户端恶意修改(rename-command REPLICAOF "")。
- 主从节点都建议设置密码(
通过主从复制,既能提高 Redis 的读性能,又能实现数据备份,是构建 Redis 高可用架构的基础。
七、Redis 集群
Redis 集群(Redis Cluster)是官方提供的分布式解决方案,用于解决单节点性能瓶颈和单点故障问题,支持数据分片存储、自动故障转移和水平扩展,最多可容纳 16384 个节点。
核心特性
- 数据分片:采用哈希槽(Hash Slot)机制,将 16384 个槽(0-16383)分配给集群中的节点,每个 key 通过
CRC16(key) % 16384计算所属槽位,实现数据分布式存储。 - 高可用:每个主节点(Master)可配置多个从节点(Slave),主节点故障时,从节点自动晋升为主节点。
- 去中心化:集群无中心节点,每个节点对等通信,客户端可连接任意节点获取全集群信息。
- 水平扩展:支持动态添加 / 删除节点,自动重新分配槽位,无需停机。
集群配置步骤(以 3 主 3 从为例)
假设规划 6 个节点(可在同一服务器用不同端口模拟):
- 主节点:
6380、6381、6382 - 从节点:
6383(对应 6380)、6384(对应 6381)、6385(对应 6382)
7.1. 准备节点配置文件
为每个节点创建独立的配置文件(如 redis-6380.conf 至 redis-6385.conf),核心配置如下(以 6380 为例):
conf
# 端口
port 6380
# 开启集群模式
cluster-enabled yes
# 集群配置文件(自动生成,记录槽位和节点信息)
cluster-config-file nodes-6380.conf
# 集群节点超时时间(毫秒,超过此时间认为节点故障)
cluster-node-timeout 15000
# 绑定地址(生产环境建议指定具体IP)
bind 0.0.0.0
# 关闭保护模式(允许跨网络访问)
protected-mode no
# 数据持久化(可选)
appendonly yes
# 密码(所有节点密码需一致,否则无法通信)
requirepass "cluster_password"
masterauth "cluster_password"
其他节点配置仅需修改 port 和 cluster-config-file 文件名(如 6381 对应 nodes-6381.conf)。
7.2. 启动所有节点
分别启动 6 个节点:
bash
redis-server /path/to/redis-6380.conf
redis-server /path/to/redis-6381.conf
redis-server /path/to/redis-6382.conf
redis-server /path/to/redis-6383.conf
redis-server /path/to/redis-6384.conf
redis-server /path/to/redis-6385.conf
启动后,每个节点会生成对应的 nodes-xxxx.conf 文件,初始状态为 “未加入集群”。
7.3. 创建集群
使用 redis-cli --cluster 工具初始化集群(需 Redis 5.0+,低版本用 redis-trib.rb):
bash
# 语法:redis-cli --cluster create 节点列表 --cluster-replicas 从节点数量
redis-cli -a cluster_password --cluster create \
127.0.0.1:6380 127.0.0.1:6381 127.0.0.1:6382 \
127.0.0.1:6383 127.0.0.1:6384 127.0.0.1:6385 \
--cluster-replicas 1
--cluster-replicas 1表示每个主节点分配 1 个从节点。- 执行后会显示主从分配方案和槽位分配计划,输入
yes确认。
7.4. 验证集群状态
连接任意节点,查看集群信息:
bash
# 连接节点(需指定 -c 启用集群模式客户端)
redis-cli -c -h 127.0.0.1 -p 6380 -a cluster_password
# 查看集群状态
127.0.0.1:6380> cluster info
# 输出示例:cluster_state:ok(集群正常)、cluster_slots_assigned:16384(所有槽位已分配)
# 查看节点列表(主从关系和槽位范围)
127.0.0.1:6380> cluster nodes
# 输出中包含每个节点的 ID、角色(master/slave)、所属主节点、负责的槽位等信息
7.5. 测试数据分片与自动跳转
在集群模式客户端中设置 key,会自动路由到对应槽位的主节点:
bash
127.0.0.1:6380> set name "redis-cluster"
# 若当前节点不是目标槽位,会显示 "-> Redirected to slot [xxx] located at 127.0.0.1:xxxx"
OK
# 在任意节点查询,会自动跳转至存储节点
127.0.0.1:6381> get name
"redis-cluster"
7.6 进阶操作
添加新节点
-
启动新节点(如 6386),配置与其他节点一致(
cluster-enabled yes)。 -
将新节点加入集群(先作为空节点):
bash
redis-cli -a cluster_password --cluster add-node 127.0.0.1:6386 127.0.0.1:6380 -
分配槽位给新节点(若作为主节点):
bash
redis-cli -a cluster_password --cluster reshard 127.0.0.1:6380 # 按提示输入需迁移的槽位数、目标节点ID、源节点ID,完成后槽位自动分配 -
若作为从节点,关联主节点:
bash
# 连接新节点,执行 cluster replicate 主节点ID redis-cli -c -p 6386 -a cluster_password 127.0.0.1:6386> cluster replicate <master-node-id>
删除节点
-
若节点是从节点,直接删除:
bash
redis-cli -a cluster_password --cluster del-node 127.0.0.1:6380 <node-id> -
若节点是主节点,需先迁移其所有槽位到其他主节点,再删除(迁移步骤类似添加节点的 reshard 操作)。
故障转移测试
-
停止一个主节点(如 6380):
bash
redis-cli -p 6380 -a cluster_password shutdown -
观察其从节点(如 6383)是否自动晋升为主节点:
bash
redis-cli -c -p 6383 -a cluster_password cluster nodes | grep master
注意事项
- 密码一致性:所有节点必须使用相同的密码(
requirepass和masterauth),否则节点间无法通信。 - 槽位完整性:集群状态为
ok的前提是 16384 个槽位全部被分配,否则无法正常读写。 - 数据迁移:动态迁移槽位时,数据会在线同步,不影响集群可用性,但需控制迁移速度(避免阻塞)。
- 客户端适配:客户端需支持集群模式(如 JedisCluster、Lettuce),否则需手动处理槽位路由。
- 备份策略:集群不直接支持整体备份,需对每个主节点单独进行持久化(RDB/AOF)。
Redis 集群通过分片和自动故障转移,完美解决了单节点的性能和可用性瓶颈,适合数据量较大、并发量高的生产环境。