Files
interview/questions/01-分布式系统/一致性哈希.md
yasinshaw 0e46a367c4 refactor: rename files to Chinese and organize by category
Organized 50 interview questions into 12 categories:
- 01-分布式系统 (9 files): 分布式事务, 分布式锁, 一致性哈希, CAP理论, etc.
- 02-数据库 (2 files): MySQL索引优化, MyBatis核心原理
- 03-缓存 (5 files): Redis数据结构, 缓存问题, LRU算法, etc.
- 04-消息队列 (1 file): RocketMQ/Kafka
- 05-并发编程 (4 files): 线程池, 设计模式, 限流策略, etc.
- 06-JVM (1 file): JVM和垃圾回收
- 07-系统设计 (8 files): 秒杀系统, 短链接, IM, Feed流, etc.
- 08-算法与数据结构 (4 files): B+树, 红黑树, 跳表, 时间轮
- 09-网络与安全 (3 files): TCP/IP, 加密安全, 性能优化
- 10-中间件 (4 files): Spring Boot, Nacos, Dubbo, Nginx
- 11-运维 (4 files): Kubernetes, CI/CD, Docker, 可观测性
- 12-面试技巧 (1 file): 面试技巧和职业规划

All files renamed to Chinese for better accessibility and
organized into categorized folders for easier navigation.

Generated with [Claude Code](https://claude.com/claude-code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-03-01 00:10:53 +08:00

8.8 KiB
Raw Blame History

一致性哈希算法

问题

  1. 什么是一致性哈希?解决了什么问题?
  2. 一致性哈希的原理是什么?
  3. 什么是虚拟节点?为什么需要虚拟节点?
  4. 一致性哈希在负载均衡、分布式缓存中的应用
  5. 一致性哈希有哪些优缺点?
  6. 在实际项目中如何实现一致性哈希?

标准答案

1. 传统哈希的问题

场景:分布式缓存

假设有 3 台缓存服务器:

Server A, Server B, Server C

使用传统哈希:hash(key) % N

int serverIndex = hash(key) % 3;  // 3 台服务器

问题:服务器扩容/缩容

新增一台服务器Server D

原来hash(key) % 3
现在hash(key) % 4

结果:大部分 key 的路由都变了!
- 缓存全部失效
- 数据库压力激增

示例

3 台服务器时:
hash("user:1") % 3 = 1 → Server B
hash("user:2") % 3 = 2 → Server C

4 台服务器时(新增 Server D
hash("user:1") % 4 = 2 → Server C变了
hash("user:2") % 4 = 3 → Server D变了

影响75% 的缓存失效

2. 一致性哈希原理

核心思想

将服务器和数据都映射到哈希环上:

  • 顺时针查找最近的服务器
  • 服务器变化时,只影响相邻数据

哈希环0 - 2^32-1

      0
      ↓
Server A ──────────── Server B
    ↗                    ↘
  ↗                      ↘
 ↑                        ↓
 |                        ↓
Server D ←────────────→ Server C
      ↑
    2^32-1

算法步骤

步骤 1映射服务器到环

// 服务器 IP → 哈希值
hash("192.168.1.10")   1000     Server A
hash("192.168.1.11")   5000     Server B
hash("192.168.1.12")   10000    Server C

步骤 2映射数据到环

// 数据 Key → 哈希值
hash("user:1")  2000
hash("user:2")  6000
hash("user:3")  15000

步骤 3顺时针查找服务器

user:1 (2000) → Server B (5000)  // 顺时针第一个服务器
user:2 (6000) → Server C (10000)
user:3 (15000) → Server A (1000 环绕)

Java 实现

public class ConsistentHash<T> {

    private final TreeMap<Long, T> ring = new TreeMap<>();
    private final int virtualNodes;  // 虚拟节点数

    public ConsistentHash(int virtualNodes) {
        this.virtualNodes = virtualNodes;
    }

    // 添加节点
    public void addNode(T node) {
        for (int i = 0; i < virtualNodes; i++) {
            String virtualNodeName = node.toString() + "#" + i;
            long hash = hash(virtualNodeName);
            ring.put(hash, node);
        }
    }

    // 移除节点
    public void removeNode(T node) {
        for (int i = 0; i < virtualNodes; i++) {
            String virtualNodeName = node.toString() + "#" + i;
            long hash = hash(virtualNodeName);
            ring.remove(hash);
        }
    }

    // 获取节点
    public T getNode(String key) {
        if (ring.isEmpty()) {
            return null;
        }

        long hash = hash(key);

        // 顺时针查找
        Map.Entry<Long, T> entry = ring.ceilingEntry(hash);
        if (entry == null) {
            // 环绕到第一个节点
            entry = ring.firstEntry();
        }

        return entry.getValue();
    }

    // 哈希函数FNV1_32_HASH
    private long hash(String key) {
        final long p = 16777619;
        long hash = 2166136261L;

        for (byte b : key.getBytes()) {
            hash = (hash ^ b) * p;
        }

        hash += hash << 13;
        hash ^= hash >> 7;
        hash += hash << 3;
        hash ^= hash >> 17;
        hash += hash << 5;

        return hash & 0xffffffffL;
    }
}

使用示例

// 创建一致性哈希环
ConsistentHash<String> consistentHash = new ConsistentHash<>(150);

// 添加服务器
consistentHash.addNode("192.168.1.10");  // Server A
consistentHash.addNode("192.168.1.11");  // Server B
consistentHash.addNode("192.168.1.12");  // Server C

// 获取路由
String server = consistentHash.getNode("user:1001");
System.out.println("路由到服务器: " + server);

// 新增服务器
consistentHash.addNode("192.168.1.13");  // Server D
// 只有部分数据受影响

3. 虚拟节点

问题:数据倾斜

场景

哈希环:
Server A (1000)
Server B (5000)
Server C (10000)

数据分布:
A: 2000 条
B: 500 条
C: 500 条

数据倾斜A 的负载远大于 B、C

原因

  • 节点少,哈希分布不均
  • 真实服务器数量有限

解决方案:虚拟节点

原理 每个真实节点映射多个虚拟节点:

真实节点 A
├─ 虚拟节点 A#1 → 1000
├─ 虚拟节点 A#2 → 3000
├─ 虚拟节点 A#3 → 7000
└─ ...

真实节点 B
├─ 虚拟节点 B#1 → 2000
├─ 虚拟节点 B#2 → 4000
└─ ...

真实节点 C
├─ 虚拟节点 C#1 → 5000
└─ ...

效果

数据分布:
A: 1000 条25%
B: 1000 条25%
C: 1000 条25%
D: 1000 条25%

均衡度:高

虚拟节点数量

  • 一般100-150 个
  • 更多:更均衡,但内存占用大

4. 一致性哈希的应用

应用 1分布式缓存Redis Cluster

// Redis 集群路由
public class RedisClusterRouter {

    private final ConsistentHash<JedisPool> consistentHash;

    public RedisClusterRouter(List<JedisPool> pools) {
        this.consistentHash = new ConsistentHash<>(150);
        for (JedisPool pool : pools) {
            consistentHash.addNode(pool);
        }
    }

    public Jedis getJedis(String key) {
        JedisPool pool = consistentHash.getNode(key);
        return pool.getResource();
    }
}

优点

  • 扩容/缩容影响小
  • 数据迁移量小

应用 2负载均衡Nginx

# Nginx 一致性哈希配置
upstream backend {
    consistent_hash $request_uri;

    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080;
}

效果

  • 相同请求路由到同一服务器
  • 会话保持(无需 Sticky Session

应用 3分库分表

// 分库路由
public class ShardingRouter {

    private final ConsistentHash<String> dbRouter;

    public ShardingRouter(List<String> databases) {
        this.dbRouter = new ConsistentHash<>(100);
        for (String db : databases) {
            dbRouter.addNode(db);
        }
    }

    public String getDatabase(String userId) {
        return dbRouter.getNode(userId);
    }
}

5. 一致性哈希的优缺点

优点

  1. 最小化数据迁移

    • 新增节点:只影响相邻节点数据
    • 移除节点:只影响该节点数据
  2. 良好的容错性

    • 节点故障:数据自动迁移到下一个节点
    • 平滑恢复:节点恢复后数据自动迁移回来
  3. 可扩展性

    • 支持动态增删节点
    • 适合大规模分布式系统

缺点

  1. 数据倾斜

    • 节点少时分布不均
    • 需要虚拟节点解决
  2. 实现复杂

    • 相比简单哈希复杂度高
    • 需要维护哈希环
  3. 内存占用

    • 虚拟节点占用内存
    • 节点多时内存开销大

6. 实际项目经验

案例Redis 集群扩容

场景

  • 现有 3 台 Redis每台 10 万 key
  • 新增 2 台 Redis

不使用一致性哈希

迁移量10 万 × 5/6 ≈ 8.3 万 key83%

使用一致性哈希

迁移量10 万 × 2/5 ≈ 4 万 key40%

实现

// 1. 新节点上线
JedisPool newPool1 = new JedisPool("192.168.1.13");
JedisPool newPool2 = new JedisPool("192.168.1.14");

consistentHash.addNode(newPool1);
consistentHash.addNode(newPool2);

// 2. 数据迁移
for (String key : allKeys) {
    JedisPool newPool = consistentHash.getNode(key);
    JedisPool oldPool = oldMapping.get(key);

    if (newPool != oldPool) {
        // 迁移数据
        migrateData(key, oldPool, newPool);
    }
}

7. 阿里 P7 加分项

深度理解

  • 理解一致性哈希的数学原理(哈希函数、分布均匀性)
  • 理解虚拟节点数量对均衡度的影响
  • 了解其他哈希算法(如 Rendezvous Hash

实战经验

  • 有使用一致性哈希实现分库分表的经验
  • 有处理数据倾斜和迁移的经验
  • 有一致性哈希在生产环境的调优经验

架构能力

  • 能设计支持平滑扩容的分片集群
  • 能设计数据迁移的灰度方案
  • 有一致性哈希的监控和告警经验

技术选型

  • 了解 Redis Cluster、Cassandra 等系统的一致性哈希实现
  • 了解 Nginx、HAProxy 等负载均衡器的一致性哈希配置
  • 能根据业务特点选择合适的哈希算法