【Redis 集群】Redis集群扩容时如何避免全量数据迁移 - 教程

news/2025/10/4 13:27:13/文章来源:https://www.cnblogs.com/wzzkaifa/p/19125516

1. Redis集群数据分布原理

1.1 哈希槽(Hash Slot)机制

Redis集群采用16384个固定哈希槽进行数据分片,这是避免全量迁移的理论基础。

1.2 键值到槽位的映射算法

public class RedisSlotCalculator {
private static final int SLOT_COUNT = 16384;
/**
* Redis官方CRC16算法实现
*/
public static int crc16(byte[] bytes) {
int crc = 0x0000;
for (byte b : bytes) {
crc = ((crc << 8) ^ CRC16_LOOKUP[((crc >>> 8) ^ (b & 0xFF)) & 0xFF]);}return crc & 0xFFFF;}/*** 计算Key对应的哈希槽*/public static int calculateSlot(String key) {// 处理哈希标签:{user1000}.profile 和 {user1000}.data 会分配到同一个槽int start = key.indexOf('{');int end = key.indexOf('}');String keyToHash = key;if (start != -1 && end != -1 && end > start + 1) {keyToHash = key.substring(start + 1, end);}return crc16(keyToHash.getBytes()) % SLOT_COUNT;}// CRC16查找表private static final int[] CRC16_LOOKUP = { /* Redis官方实现 */ };}

2. 预分片(Pre-sharding)架构设计

2.1 预分片集群架构

2.2 Java实现:智能路由客户端

public class PreShardedRedisCluster {
private final Map<Integer, JedisPool> slotToNodeMap;private final Map<String, JedisPool> nodeMap;private final List<Integer> reservedSlots; // 预留的空槽位public PreShardedRedisCluster(Set<String> initialNodes, Set<Integer> reservedSlots) {this.slotToNodeMap = new ConcurrentHashMap<>();this.nodeMap = new ConcurrentHashMap<>();this.reservedSlots = new ArrayList<>(reservedSlots);initializeCluster(initialNodes);assignReservedSlots();}/*** 初始化集群映射*/private void initializeCluster(Set<String> nodes) {for (String node : nodes) {try (Jedis jedis = new Jedis(node)) {String clusterSlots = jedis.clusterSlots();// 解析cluster slots输出,构建槽位到节点的映射parseClusterSlots(clusterSlots);}}}/*** 为预留节点分配空槽位*/private void assignReservedSlots() {// 在集群初始化时,为预留节点分配空槽位for (Integer reservedSlot : reservedSlots) {// 这些槽位不包含实际数据,扩容时直接指向新节点slotToNodeMap.put(reservedSlot, getReservedNodePool());}}/*** 智能路由:新数据直接写入新节点*/public String set(String key, String value) {int slot = RedisSlotCalculator.calculateSlot(key);JedisPool targetPool = slotToNodeMap.get(slot);// 如果是预留槽位,直接使用新节点if (reservedSlots.contains(slot)) {return setToNewNode(key, value, slot);}try (Jedis jedis = targetPool.getResource()) {return jedis.set(key, value);}}/*** 支持数据迁移的读取操作*/public String get(String key) {int slot = RedisSlotCalculator.calculateSlot(key);JedisPool primaryPool = slotToNodeMap.get(slot);// 尝试从主节点读取try (Jedis jedis = primaryPool.getResource()) {String value = jedis.get(key);if (value != null) {return value;}}// 主节点未找到,尝试从可能的目标节点读取(迁移过程中)return tryGetFromMigrationTarget(key, slot);}}

3. 渐进式数据迁移原理与实现

3.1 迁移状态机

3.2 迁移控制器实现

public class IncrementalMigrationController {
private final JedisCluster jedisCluster;
private final MigrationConfig config;
private final MigrationMetrics metrics;
/**
* 渐进式迁移主流程
*/
public void migrateSlotsIncrementally(int startSlot, int endSlot,
String sourceNodeId, String targetNodeId) {
List<Integer> slotsToMigrate = getSlotsRange(startSlot, endSlot);// 分批迁移,控制迁移速度List<List<Integer>> batches = Lists.partition(slotsToMigrate, config.getBatchSize());for (List<Integer> batch : batches) {if (!migrateBatch(batch, sourceNodeId, targetNodeId)) {log.warn("Batch migration failed, will retry");// 重试逻辑handleMigrationFailure(batch, sourceNodeId, targetNodeId);}// 控制迁移速度,避免影响业务throttleMigration();// 更新路由信息updateClientRouting(batch, targetNodeId);}}/*** 单批次迁移实现*/private boolean migrateBatch(List<Integer> slots, String sourceNodeId, String targetNodeId) {// 1. 设置迁移状态setSlotState(slots, "MIGRATING", sourceNodeId, targetNodeId);// 2. 扫描并迁移键值for (Integer slot : slots) {if (!migrateKeysInSlot(slot, sourceNodeId, targetNodeId)) {return false;}}// 3. 验证数据一致性return verifyDataConsistency(slots, sourceNodeId, targetNodeId);}/*** 迁移单个槽位中的键*/private boolean migrateKeysInSlot(int slot, String sourceNodeId, String targetNodeId) {String sourceNode = getNodeById(sourceNodeId);String targetNode = getNodeById(targetNodeId);try (Jedis sourceJedis = new Jedis(sourceNode);Jedis targetJedis = new Jedis(targetNode)) {// 使用SCAN迭代,避免阻塞String cursor = "0";do {ScanResult<String> scanResult = sourceJedis.sscan("{" + slot + "}", cursor);List<String> keys = scanResult.getResult();for (String key : keys) {if (!migrateSingleKey(key, sourceJedis, targetJedis)) {return false;}metrics.incrementKeysMigrated();}cursor = scanResult.getCursor();} while (!"0".equals(cursor));return true;}}/*** 迁移单个键值*/private boolean migrateSingleKey(String key, Jedis source, Jedis target) {// 1. 序列化键值数据byte[] keyData = key.getBytes();byte[] valueData = source.dump(key);if (valueData == null) {return true; // 键可能已过期或被删除}// 2. 在目标节点恢复数据try {target.restore(key, config.getTtl(key), valueData);// 3. 验证数据一致性if (verifyKeyMigration(key, source, target)) {// 4. 删除源节点数据(可选,根据迁移策略)if (config.isDeleteAfterMigration()) {source.del(key);}return true;}} catch (Exception e) {log.error("Failed to migrate key: {}", key, e);return false;}return false;}}

4. 客户端双写与流量切换机制

4.1 双写客户端架构

public class DualWriteRedisClient {
private final JedisCluster oldCluster;
private final JedisCluster newCluster;
private final MigrationPhase phase;
private final ReadStrategy readStrategy;
public enum MigrationPhase {
DUAL_WRITE,      // 双写阶段
READ_NEW_FIRST,  // 优先读新集群
READ_NEW_ONLY,   // 只读新集群
WRITE_NEW_ONLY,  // 只写新集群
COMPLETED        // 迁移完成
}
public enum ReadStrategy {
OLD_FIRST,    // 优先读旧集群
NEW_FIRST,    // 优先读新集群  
BOTH_VERIFY   // 双读验证
}
/**
* 支持迁移的写入操作
*/
public String set(String key, String value) {
String result1 = null, result2 = null;
// 根据迁移阶段决定写入策略
switch (phase) {
case DUAL_WRITE:
result1 = oldCluster.set(key, value);
result2 = newCluster.set(key, value);
return result1 != null ? result1 : result2;
case WRITE_NEW_ONLY:
return newCluster.set(key, value);
default:
// 回退到双写
return dualWriteWithFallback(key, value);
}
}
/**
* 支持迁移的读取操作
*/
public String get(String key) {
switch (readStrategy) {
case NEW_FIRST:
try {
String value = newCluster.get(key);
if (value != null) return value;
return oldCluster.get(key); // 回退到旧集群
} catch (Exception e) {
return oldCluster.get(key);
}
case OLD_FIRST:
try {
String value = oldCluster.get(key);
if (value != null) return value;
return newCluster.get(key);
} catch (Exception e) {
return newCluster.get(key);
}
case BOTH_VERIFY:
String oldValue = oldCluster.get(key);
String newValue = newCluster.get(key);
if (!Objects.equals(oldValue, newValue)) {
log.warn("Data inconsistency detected for key: {}", key);
metrics.recordInconsistency(key);
}
return newValue != null ? newValue : oldValue;
default:
return newCluster.get(key);
}
}
}

4.2 流量切换控制器

public class TrafficMigrationController {
private final DualWriteRedisClient redisClient;
private final MigrationConfig config;
/**
* 渐进式流量切换
*/
public void gradualTrafficShift() {
// 阶段1: 1%流量切换到新集群
shiftReadTraffic(1);
monitorAndWait(30, TimeUnit.MINUTES);
// 阶段2: 10%流量
shiftReadTraffic(10);
monitorAndWait(1, TimeUnit.HOURS);
// 阶段3: 50%流量
shiftReadTraffic(50);
monitorAndWait(2, TimeUnit.HOURS);
// 阶段4: 100%读流量
shiftReadTraffic(100);
monitorAndWait(4, TimeUnit.HOURS);
// 开始写入流量切换
shiftWriteTraffic();
}
private void shiftReadTraffic(int percentage) {
// 基于一致性哈希的流量调度
String trafficKey = "read_traffic_ratio";
redisClient.set(trafficKey, String.valueOf(percentage));
// 更新客户端配置
updateClientRoutingConfig(percentage);
}
}

5. 完整的迁移架构图

数据迁移流水线
批量迁移器
槽位扫描器
数据验证器
路由更新器
应用客户端
智能路由层
迁移状态控制器
双写客户端
读取负载均衡
旧Redis集群
新Redis集群
迁移管理平台
监控指标收集
迁移进度跟踪
异常告警系统
数据一致性验证
自动回滚机制

6. 关键配置与监控

6.1 迁移配置类

@Configuration
public class MigrationConfig {
@Value("${redis.migration.batch.size:100}")
private int batchSize;
@Value("${redis.migration.max.parallelism:4}")
private int maxParallelism;
@Value("${redis.migration.throttle.delay:100}")
private long throttleDelayMs;
@Value("${redis.migration.verify.data:true}")
private boolean verifyData;
@Value("${redis.migration.reserved.slots}")
private Set<Integer> reservedSlots;// 迁移速率限制器@Beanpublic RateLimiter migrationRateLimiter() {return RateLimiter.create(1000); // 每秒1000个key}}

6.2 监控指标收集

@Component
public class MigrationMetrics {
private final MeterRegistry meterRegistry;
private final Counter keysMigratedCounter;
private final Timer migrationTimer;
private final Gauge consistencyGauge;
public MigrationMetrics(MeterRegistry registry) {
this.meterRegistry = registry;
this.keysMigratedCounter = Counter.builder("redis.migration.keys")
.description("Number of keys migrated")
.register(registry);
this.migrationTimer = Timer.builder("redis.migration.duration")
.register(registry);
}
public void recordMigrationSuccess(String key, long duration) {
keysMigratedCounter.increment();
migrationTimer.record(duration, TimeUnit.MILLISECONDS);
}
}

通过这种架构设计,Redis集群扩容时可以做到:

  1. 零停机迁移:业务无感知
  2. 按需迁移:只迁移必要数据,避免全量迁移
  3. 流量可控:渐进式切换,风险可控
  4. 数据一致:完善的验证机制
  5. 可监控:完整的监控告警体系

这种方案在生产环境中经过验证,能够有效支持TB级别Redis集群的平滑扩容。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/927169.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深入解析:Qwen-Image:开源图像生成新突破 —— 聚焦复杂文本渲染与精准图像编辑

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

做网站主要用哪种语言wordpress 文章自定义字段

前言&#xff1a;之前发了一篇树莓派刷OpenWrt系统的晒单&#xff0c;得到众多网友的关注&#xff0c;小编要希望分享更多DIY树莓派的经验。我玩树莓派都是比较简单的DIY&#xff0c;也积累了一点经验&#xff0c;在此分享给大家&#xff0c;算是抛砖引玉&#xff0c;希望看到大…

做游戏用什么电脑系统下载网站好代刷网自助建站系统

上次给大家安利了一波Pandownload手机版/电脑版。那篇文章中也说了&#xff0c;这类应用使用不当可能会遇到账号被限速的情况&#xff0c;而且手机版必须登录才能进行不限速下载。总之&#xff0c;凡是没登录账号的小伙伴&#xff0c;下载过程会非常曲折。那么是否有无需登录就…

团购网站短信平台怎样做网站模板

NTFS安全权限一、NTFS权限概述1、通过设置NTFS权限&#xff0c;实现不同的用户访问不同的权限2、分配了正确的访问权限后&#xff0c;用户才能访问其资源3、设置权限防止资源被篡改、删除二、文件系统概述 文件系统即在外部存储设备上组织文件的方法常用的文件系统&#xff1a;…

专业网站推广引流国外交易平台

4.类和对象 C面向对象的三大特性为:封装,继承,多态C认为万事万物都皆为对象&#xff0c;对象上有其属性和行为 例如&#xff1a; 人可以作为对象&#xff0c;属性有姓名、年龄、身高、体重...,行为有走、跑、跳、说话...车可以作为对象&#xff0c;属性有轮胎、方向盘、车灯…

自助建站 知乎wordpress 数组

题目 面试题57 - II. 和为s的连续正数序列 输入一个正整数 target &#xff0c;输出所有和为 target 的连续正整数序列&#xff08;至少含有两个数&#xff09;。 序列内的数字由小到大排列&#xff0c;不同序列按照首个数字从小到大排列。 示例 1&#xff1a; 输入&#x…

深入解析:逻辑回归与神经网络:本质联系与核心区别

深入解析:逻辑回归与神经网络:本质联系与核心区别pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas"…

网站备案和服务器备案吗班级网页模板

项目本地运行 1.到github或者自己创建一个flask项目&#xff0c;确保在本地是可以运行成功的 2.上传到自己的代码仓库 服务器部署 1.安装docker yum install docker -y2.配置加速器 DaoCloud加速器采用自主研发的智能路由及缓存技术&#xff0c;并引入了现金的协议层优化…

windows安全中心

windows安全中心 windowsdefender: win+R打开以上命令

检察机关门户网站建设公司域名让做网站的

略略翻了下书&#xff0c;差点儿窒息在床上… 看了几个博主的笔记&#xff0c;有点儿头疼 不知道是不是神经裂开生成新突触&#xff0c;还是脑细胞坏死前最后的呐喊 重点看了三篇&#xff0c;觉得非常惊艳&#xff0c;易于理解的 先看了主成分分析的原理详解&#xff0c;但还是…

怎么工作的?从石头分类说起就是AI大模型

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

亚太建设科技信息研究院网站公司手册制作网站

锁 事务的隔离性由锁来实现。 概述 锁是计算机协调多个进程或线程并发访问某一资源的机制。在程序开发中会存在多线程同步的问题&#xff0c;当多个线程并发访问某个数据的时候&#xff0c;尤其是针对一些敏感的数据&#xff08;比如订单、金额等&#xff09;&#xff0c;我…

金融网站建设方案ppt网站建设基础大纲文案

引言 大家好&#xff0c;我是GISer Liu&#x1f601;&#xff0c;一名热爱AI技术的GIS开发者。本系列文章是我跟随DataWhale 2024年10月实践赛的大模型生图安全疫苗注入赛道&#xff1b;本文主要整理本次赛事的基本流程和优化方法。&#x1f495;&#x1f495;&#x1f60a; 一…

详细介绍:深度学习入门:从神经网络基础到模型训练优化

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

专业做旗袍花的网站是什么网站能用的免费proxy网页

如果你在运行Minecraft时出现内存错误等问题&#xff0c;你可能需要给Minecraft分配更多内存来解决运行故障。如果你玩的是新版本的Minecraft&#xff0c;那么你可以从启动器里直接分配内存(RAM)。如果你使用的是旧版本&#xff0c;那么你需要创建一些文件来改变Minecraft内存使…

做网站可以找设计公司吗商城站人工售票时间表

WordPress博客网站搬家和换域名方法方案一 开设个人博客的朋友使用WordPress不在少数&#xff0c;那么也难免不了更换空间和域名的情况&#xff0c;由于笔者亲历了一次更换空间和域名的情况&#xff0c;将博客从AAA.com 更改为 BBB.com&#xff0c;所以本文就分享一下更换域名…

网站建设小程序开发报价雅诗兰黛网络营销策划书

[vue] 怎么缓存当前打开的路由组件&#xff0c;缓存后想更新当前组件怎么办呢&#xff1f; 可以在路由meta中加入参数, 对打开的路由进行keep-alive的判断, 通过钩子active等个人简介 我是歌谣&#xff0c;欢迎和大家一起交流前后端知识。放弃很容易&#xff0c; 但坚持一定很…

开源 C# 飞快开发(十六)数据库--sqlserver增删改查

开源 C# 飞快开发(十六)数据库--sqlserver增删改查pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas&quo…

英语语法填空

test2错题 A Tenyson suggested that we can buy the lady a flower. Tenyson suggested that we should buy the lady a flower.当suggest表示“建议”时,其后的宾语从句使用“should+动词原形”的虚拟语气结构,其中…

深入解析:基于Java的springboot/SSM+vue.js+uniapp小程序的农产品溯源系统附带文章源码部署视频讲解等

深入解析:基于Java的springboot/SSM+vue.js+uniapp小程序的农产品溯源系统附带文章源码部署视频讲解等2025-10-04 12:51 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: norm…