← 返回首页
🌐

分布式缓存:一致性哈希与虚拟节点

📂 architecture ⏱ 2 min 357 words

分布式缓存:一致性哈希与虚拟节点

一致性哈希算法

一致性哈希将哈希值空间组织成一个虚拟的环(0到2^32-1),节点和数据都映射到环上。数据沿顺时针方向找到第一个节点存储,当节点增减时只影响相邻节点的数据分布。

import hashlib
from bisect import bisect_right

class ConsistentHash:
    def __init__(self, nodes: list, virtual_nodes: int = 150):
        self.ring = {}
        self.sorted_keys = []
        self.virtual_nodes = virtual_nodes
        
        for node in nodes:
            self.add_node(node)
    
    def _hash(self, key: str) -> int:
        return int(hashlib.md5(key.encode()).hexdigest(), 16)
    
    def add_node(self, node: str):
        for i in range(self.virtual_nodes):
            virtual_key = f"{node}:v{i}"
            hash_val = self._hash(virtual_key)
            self.ring[hash_val] = node
            self.sorted_keys.append(hash_val)
        self.sorted_keys.sort()
    
    def remove_node(self, node: str):
        for i in range(self.virtual_nodes):
            virtual_key = f"{node}:v{i}"
            hash_val = self._hash(virtual_key)
            del self.ring[hash_val]
            self.sorted_keys.remove(hash_val)
    
    def get_node(self, key: str) -> str:
        if not self.ring:
            return None
        hash_val = self._hash(key)
        idx = bisect_right(self.sorted_keys, hash_val) % len(self.sorted_keys)
        return self.ring[self.sorted_keys[idx]]

虚拟节点解决数据倾斜

物理节点数量较少时,一致性哈希可能导致数据分布不均匀。虚拟节点将每个物理节点映射为多个虚拟节点,使数据分布更均匀。

type ConsistentHash struct {
    ring         map[uint32]string
    sortedKeys   []uint32
    virtualNodes int
    mu           sync.RWMutex
}

func NewConsistentHash(nodes []string, virtualNodes int) *ConsistentHash {
    ch := &ConsistentHash{
        ring:         make(map[uint32]string),
        virtualNodes: virtualNodes,
    }
    for _, node := range nodes {
        ch.AddNode(node)
    }
    return ch
}

func (ch *ConsistentHash) AddNode(node string) {
    ch.mu.Lock()
    defer ch.mu.Unlock()
    
    for i := 0; i < ch.virtualNodes; i++ {
        key := fmt.Sprintf("%s#%d", node, i)
        hash := ch.hash(key)
        ch.ring[hash] = node
        ch.sortedKeys = append(ch.sortedKeys, hash)
    }
    sort.Slice(ch.sortedKeys, func(i, j int) bool {
        return ch.sortedKeys[i] < ch.sortedKeys[j]
    })
}

func (ch *ConsistentHash) GetNode(key string) string {
    ch.mu.RLock()
    defer ch.mu.RUnlock()
    
    if len(ch.ring) == 0 {
        return ""
    }
    
    hash := ch.hash(key)
    idx := sort.Search(len(ch.sortedKeys), func(i int) bool {
        return ch.sortedKeys[i] >= hash
    })
    
    if idx >= len(ch.sortedKeys) {
        idx = 0
    }
    return ch.ring[ch.sortedKeys[idx]]
}

func (ch *ConsistentHash) hash(key string) uint32 {
    h := fnv.New32a()
    h.Write([]byte(key))
    return h.Sum32()
}

缓存穿透、雪崩、击穿解决方案

// 布隆过滤器防止缓存穿透
public class BloomFilterCache {
    private final BloomFilter<String> bloomFilter;
    private final RedisTemplate<String, Object> redisTemplate;
    
    public BloomFilterCache(int expectedInsertions, double fpp) {
        this.bloomFilter = BloomFilter.create(
            Funnels.stringFunnel(Charset.defaultCharset()),
            expectedInsertions,
            fpp
        );
    }
    
    public Object get(String key, Supplier<Object> dbLoader) {
        // 1. 布隆过滤器检查
        if (!bloomFilter.mightContain(key)) {
            return null; // 一定不存在
        }
        
        // 2. 查询缓存
        Object value = redisTemplate.opsForValue().get(key);
        if (value != null) {
            return value;
        }
        
        // 3. 缓存击穿防护 - 分布式锁
        String lockKey = "lock:" + key;
        try {
            if (redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS)) {
                value = dbLoader.get();
                if (value != null) {
                    redisTemplate.opsForValue().set(key, value, 30, TimeUnit.MINUTES);
                }
                return value;
            }
        } finally {
            redisTemplate.delete(lockKey);
        }
        return null;
    }
}

Redis Cluster架构设计

Redis Cluster通过16384个哈希槽分配数据,支持水平扩展和自动故障转移。每个节点负责一部分哈希槽,客户端通过MOVED重定向找到正确的节点。