【算法】一致性哈希

一、引言

在分布式系统中,数据存储和访问的均匀性、高可用性以及可扩展性一直是核心问题。一致性哈希算法(Consistent Hashing)是一种分布式算法,因其出色的分布式数据存储特性,被广泛应用于缓存、负载均衡、数据库分片等场景。主要用于解决缓存和负载均衡等问题。

二、算法原理

一致性哈希算法的核心思想是将数据映射到一个固定范围的哈希环上,服务器节点也映射到这个哈希环上。数据根据哈希值顺时针查找距离最近的服务器节点,从而完成数据的存储和访问。

哈希环

一致性哈希算法使用一个长度为2^32的环形哈希空间,通常使用MD5或SHA-1等哈希函数将数据映射到这个空间。

虚拟节点

为了解决服务器节点分布不均匀的问题,一致性哈希引入了虚拟节点的概念。每个物理节点对应多个虚拟节点,数据映射到虚拟节点上,从而实现数据的均匀分布。

三、数据结构

一致性哈希算法主要使用的数据结构为哈希环和节点映射表。哈希环用于存储虚拟节点,节点映射表用于存储虚拟节点与物理节点的对应关系。

哈希环:通过哈希函数计算的一圈环状空间,用来分布数据节点和数据对象。

节点:数据存储的实际位置,通过节点的哈希值在哈希环上定位。

数据对象:需要进行负载均衡或分布式存储的实际数据。

四、使用场景

一致性哈希算法广泛应用于以下场景:

分布式缓存:如Memcached、Redis等。

负载均衡:如LVS、Nginx等。

数据库分片:如MySQL分片、MongoDB分片等。

五、算法实现

基本步骤:

初始化节点:将每个节点通过哈希函数映射到哈希环上。

数据分配:计算数据对象的哈希值,将其分配给顺时针最近的节点。

一致性哈希算法的伪代码实现:

rust 复制代码
初始化哈希环
初始化节点映射表

哈希函数:hash(key)
{
    return MD5(key) % 2^32
}

添加物理节点:addNode(physicalNode)
{
    for (i = 0; i < 虚拟节点数; i++)
    {
        virtualNode = hash(physicalNode + i)
        哈希环[virtualNode] = physicalNode
        节点映射表[virtualNode] = physicalNode
    }
}

删除物理节点:removeNode(physicalNode)
{
    for (virtualNode in 节点映射表)
    {
        if (节点映射表[virtualNode] == physicalNode)
        {
            哈希环[virtualNode] = null
            节点映射表[virtualNode] = null
        }
    }
}

查找节点:findNode(data)
{
    dataHash = hash(data)
    while (哈希环[dataHash] == null)
    {
        dataHash = (dataHash + 1) % 2^32
    }
    return 哈希环[dataHash]
}

六、其他同类算法对比

  1. 简单哈希算法:将数据直接映射到固定数量的服务器节点,当节点数量变化时,大部分数据需要重新映射,不够灵活。
  2. 带有限负载的一致性哈希算法:在一致性哈希基础上,考虑节点负载,实现更均匀的数据分布。

七、多语言实现

Java

java 复制代码
// 省略部分代码,仅展示关键方法
public class ConsistentHashing {
    private SortedMap<Integer, String> circle = new TreeMap<>();

    public void addNode(String node) {
        for (int i = 0; i < VIRTUAL_NODES; i++) {
            int hash = getHash(node + "#" + i);
            circle.put(hash, node);
        }
    }

    public void removeNode(String node) {
        for (int i = 0; i < VIRTUAL_NODES; i++) {
            int hash = getHash(node + "#" + i);
            circle.remove(hash);
        }
    }

    public String findNode(String key) {
        if (circle.isEmpty()) {
            return null;
        }
        int hash = getHash(key);
        if (!circle.containsKey(hash)) {
            SortedMap<Integer, String> tailMap = circle.tailMap(hash);
            hash = tailMap.isEmpty() ? circle.firstKey() : tailMap.firstKey();
        }
        return circle.get(hash);
    }

    private int getHash(String key) {
        // 使用MD5散列函数
        MessageDigest md5 = MessageDigest.getInstance("MD5");
        md5.reset();
        md5.update(key.getBytes());
        byte[] digest = md5.digest();
        BigInteger bigInt = new BigInteger(1, digest);
        return bigInt.intValue        return bigInt.intValue() & 0x7fffffff;
    }
}

Python

python 复制代码
class ConsistentHashing:
    def __init__(self):
        self.circle = {}
        self.virtual_nodes = 100

    def _hash(self, key):
        return hash(key)

    def add_node(self, node):
        for i in range(self.virtual_nodes):
            virtual_node = f"{node}-{i}"
            hash_value = self._hash(virtual_node)
            self.circle[hash_value] = node

    def remove_node(self, node):
        for i in range(self.virtual_nodes):
            virtual_node = f"{node}-{i}"
            hash_value = self._hash(virtual_node)
            self.circle.pop(hash_value, None)

    def get_node(self, key):
        hash_value = self._hash(key)
        nodes = sorted(self.circle.keys())
        for node in nodes:
            if hash_value < node:
                return self.circle[node]
        return self.circle[nodes[0]]

C++

cpp 复制代码
#include <iostream>
#include <string>
#include <unordered_map>
#include <vector>
#include <algorithm>

class ConsistentHashing {
private:
    std::unordered_map<int, std::string> circle;
    int virtual_nodes;

    int _hash(const std::string& key) {
        std::hash<std::string> hash_fn;
        return hash_fn(key);
    }

public:
    ConsistentHashing(int virtual_nodes) : virtual_nodes(virtual_nodes) {}

    void addNode(const std::string& node) {
        for (int i = 0; i < virtual_nodes; ++i) {
            std::string virtual_node = node + "-" + std::to_string(i);
            int hash_value = _hash(virtual_node);
            circle[hash_value] = node;
        }
    }

    void removeNode(const std::string& node) {
        for (int i = 0; i < virtual_nodes; ++i) {
            std::string virtual_node = node + "-" + std::to_string(i);
            int hash_value = _hash(virtual_node);
            circle.erase(hash_value);
        }
    }

    std::string getNode(const std::string& key) {
        int hash_value = _hash(key);
        auto it = circle.lower_bound(hash_value);
        if (it == circle.end()) {
            return circle.begin()->second;
        }
        return it->second;
    }
};

Go

Go 复制代码
package main

import (
    "crypto/md5"
    "fmt"
    "sort"
    "strconv"
)

type ConsistentHashing struct {
    circle    map[int]string
    virtualNodes int
}

func NewConsistentHashing(virtualNodes int) *ConsistentHashing {
    return &ConsistentHashing{
        circle:    make(map[int]string),
        virtualNodes: virtualNodes,
    }
}

func (ch *ConsistentHashing) hash(key string) int {
    hash := md5.Sum([]byte(key))
    return int(hash[0]) | int(hash[1])<<8 | int(hash[2])<<16 | int(hash[3])<<24
}

func (ch *ConsistentHashing) AddNode(node string) {
    for i := 0; i < ch.virtualNodes; i++ {
        hash := ch.hash(node + strconv.Itoa(i))
        ch.circle[hash] = node
    }
}

func (ch *ConsistentHashing) RemoveNode(node string) {
    for i := 0; i < ch.virtualNodes; i++ {
        hash := ch.hash(node + strconv.Itoa(i))
        delete(ch.circle, hash)
    }
}

func (ch *ConsistentHashing) GetNode(key string) string {
    hash := ch.hash(key)
    var keys []int
    for k := range ch.circle {
        keys = append(keys, k)
    }
    sort.Ints(keys)
    for _, k := range keys {
        if hash < k {
            return ch.circle[k]
        }
    }
    return ch.circle[keys[0]]
}

func main() {
    // Example usage
}

八、实际服务应用场景代码框架

java 复制代码
// CacheServer.java
public class CacheServer {
    private ConsistentHashing consistentHashing;

    public CacheServer() {
        consistentHashing = new ConsistentHashing();
        // 初始化服务器节点
        consistentHashing.addNode("Server1");
        consistentHashing.addNode("Server2");
        // ... 添加更多服务器节点
    }

    public void put(String key, String value) {
        String server = consistentHashing.findNode(key);
        // 将数据存储到对应的服务器
        storeData(server, key, value);
    }

        public String get(String key) {
        String server = consistentHashing.findNode(key);
        // 从对应的服务器获取数据
        return getData(server, key);
    }

    private void storeData(String server, String key, String value) {
        // 实现数据存储逻辑,例如通过网络发送到指定服务器
    }

    private String getData(String server, String key) {
        // 实现数据获取逻辑,例如通过网络从指定服务器获取数据
        return "value"; // 示例返回值
    }

    public void addServer(String server) {
        consistentHashing.addNode(server);
    }

    public void removeServer(String server) {
        consistentHashing.removeNode(server);
    }

    public static void main(String[] args) {
        CacheServer cacheServer = new CacheServer();
        cacheServer.put("key1", "value1");
        String value = cacheServer.get("key1");
        System.out.println("Retrieved value: " + value);

        // 动态添加和删除服务器
        cacheServer.addServer("Server3");
        cacheServer.removeServer("Server1");
    }
}
相关推荐
从以前1 小时前
【算法题解】Bindian 山丘信号问题(E. Bindian Signaling)
开发语言·python·算法
不白兰1 小时前
[代码随想录23回溯]回溯的组合问题+分割子串
算法
御风@户外2 小时前
质数生成函数、质数判断备份
算法·acm
Cosmoshhhyyy2 小时前
LeetCode:3083. 字符串及其反转中是否存在同一子字符串(哈希 Java)
java·leetcode·哈希算法
闻缺陷则喜何志丹2 小时前
【C++动态规划】1105. 填充书架|2104
c++·算法·动态规划·力扣·高度·最小·书架
Dong雨2 小时前
六大排序算法:插入排序、希尔排序、选择排序、冒泡排序、堆排序、快速排序
数据结构·算法·排序算法
达帮主2 小时前
7.C语言 宏(Macro) 宏定义,宏函数
linux·c语言·算法
是十一月末2 小时前
机器学习之KNN算法预测数据和数据可视化
人工智能·python·算法·机器学习·信息可视化
chenziang13 小时前
leetcode hot100 路径总和
算法
lyx1426063 小时前
leetcode 3083. 字符串及其反转中是否存在同一子字符串
算法·leetcode·职场和发展