一致性Hash算法实现

做缓存集群时,为缓解服务器压力会部署多台缓存服务器并均匀分配数据。分布式数据库需解决数据集分区问题。一致性哈希可在节点数目改变时少迁移数据,有多种常用算法,如KETAMA_HASH是MemeCache推荐算法,还介绍了Java实现及虚拟分区方式下数据匹配度情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

      在做缓存集群时,为了缓解服务器的压力,会部署多台缓存服务器,把数据资源均匀的分配到每个服务器上,分布式数据库首先要解决把整个数据集按照分区规则映射到多个节点的问题,即把数据集划分到多个节点上,每个节点负责整体数据的一个子集。
      一致性哈希的目的就是为了在节点数目发生改变时尽可能少的迁移数据,将所有的存储节点排列在收尾相接的Hash环上,每个key在计算Hash 后会顺时针找到临接的存储节点存放。而当有节点加入或退 时,仅影响该节点在Hash环上顺时针相邻的后续节点。
      常用一致性Hash算法:CRC32_HASHFNV1_32_HASHKETAMA_HASH 等。其中 KETAMA_HASH 是 MemeCache 推荐的一致性Hash算法

Java代码实现:

import org.springframework.util.StringUtils;

import java.util.*;

/**
 * 一致性hash算法
 * Hash算法选择:hashCode(),不够散列(舍弃),选择其他Hash算法,如 CRC32_HASH、FNV1_32_HASH、KETAMA_HASH 等
 * 其中 KETAMA_HASH 是MemeCache推荐的一致性Hash算法
 */
public class ConsistencyHash {
    // 存储服务器节点信息
    private static final List<String> SERVER_NODES = new ArrayList<>();
    // 每个节点生成虚拟节点个数
    private static final Integer VIRTUAL_NODE_NUM = 100;
    // 虚拟节点与服务器对应关系:以虚拟节点为key,服务器为value,通过虚拟节点快速定位到服务器位置
    private static final TreeMap<Integer, String> VIRTUAL_SERVER_RELATION = new TreeMap<>();

    /**
     * 添加服务器节点
     * 1.将服务器添加到 SERVER_NODES
     * 2.为服务器分配虚拟节点,并设置对应关系
     * @param node
     */
    public synchronized void addServer(String node) {
        if (StringUtils.isEmpty(node)) {// 节点为空不添加
            return;
        } else if (SERVER_NODES.contains(node)) {// 节点以及存在不添加
            return;
        }
        int count = 1;
        // 1.将服务器添加到 SERVER_NODES
        SERVER_NODES.add(node);
        // 2.分配虚拟节点,并设置对应关系
        while (count <= VIRTUAL_NODE_NUM) {
            int virtualHashValue = FNV1_32_HASH.getHash(node + "_virtual_node_" + count);
            if (VIRTUAL_SERVER_RELATION.containsKey(virtualHashValue)) {// 如果已经该虚拟节点已经存在,则重新生成
                continue;
            }
            VIRTUAL_SERVER_RELATION.put(virtualHashValue, node);
            count++;
        }
    }

    /**
     * 移除服务节点
     * 1.从 SERVER_NODES 移除服务器
     * 2.移除所有虚拟节点
     * @param node
     */
    public synchronized void removeServer(String node) {
        // 1.从 SERVER_NODES 移除服务器
        if (SERVER_NODES.remove(node)) {
            // 2.移除所有虚拟节点
            Iterator<Map.Entry<Integer, String>> iterator = VIRTUAL_SERVER_RELATION.entrySet().iterator();
            while (iterator.hasNext()) {
                if (node.equals(iterator.next().getValue())){
                    iterator.remove();
                }
            }
        }
    }

    /**
     * 根据key获取所处服务器节点
     * @param key
     * @return
     */
    public String getServer(String key) {
        int hashValue = FNV1_32_HASH.getHash(key);
        Map.Entry<Integer, String> virtualNode = VIRTUAL_SERVER_RELATION.ceilingEntry(hashValue);
        if (virtualNode == null) {// 如果为空,则为第一个虚拟节点
            return VIRTUAL_SERVER_RELATION.firstEntry().getValue();
        }
        return virtualNode.getValue();
    }

    private ConsistencyHash() {
    }

    /**
     * IoDH 单例模式
     */
    private static class IoDHSingleton {
        private static final ConsistencyHash instance = new ConsistencyHash();
    }

    public static ConsistencyHash getInstance() {
        return IoDHSingleton.instance;
    }

    /**
     * 测试
     * @param args
     */
    public static void main(String[] args) {
        ConsistencyHash consistencyHash = ConsistencyHash.getInstance();
        // 初始化六个服务器
        List<String> servers = new ArrayList<>();
        servers.add("10.11.12.1");
        servers.add("10.11.12.2");
        servers.add("10.11.12.3");
        servers.add("10.11.12.4");
        servers.add("10.11.12.5");
        servers.add("10.11.12.6");
        for (int i = 0, size = servers.size(); i < size; i++) {
            consistencyHash.addServer(servers.get(i));
        }
        // 生成 10000 条数据,并存储当前数据所在节点
        int valNum = 10000;
        Map<String, String> vals = new HashMap<>();
        String val;
        String server;
        // 记录服务器节点个数
        Map<String, Integer> countServerVal = new HashMap<>();
        for (int i = 1; i <= valNum; i++) {
            val = "node-" + i;
            server = consistencyHash.getServer(val);
            vals.put(val, server);
            countServerVal.computeIfAbsent(server, k -> 0);
            countServerVal.computeIfPresent(server, (k, v) -> v + 1);
        }
        System.out.println("当前服务器分配数据情况:" + countServerVal);
        // 计算添加一个节点后,数据匹配率:以此判断当前数据所在节点和通过一致性hash算法获取服务器节点对比
        consistencyHash.addServer("10.11.12.7");
        Iterator<Map.Entry<String, String>> iterator = vals.entrySet().iterator();
        Map.Entry<String, String> entry;
        // 记录服务器节点匹配个数
        Integer countEquals = 0;
        while (iterator.hasNext()) {
            entry = iterator.next();
            if (entry.getValue().equals(consistencyHash.getServer(entry.getKey()))) {
                countEquals++;
            }
        }
        System.out.println("当前服务器数据匹配率:" + countEquals / Double.valueOf(valNum));
        // TODO 删除一个节点数据匹配率
    }
}

运行结果:
在这里插入图片描述
采用一致性Hash算法虚拟分区方式,当加入一台服务器后,数据匹配度大约为为 n / (n + 1),当删除一台服务器后,数据匹配度大约为为( n - 1) / n。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值