You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Java中实现Nginx Ketama一致性哈希算法?

Nginx Ketama一致性哈希的Java实现方案

刚好之前在做类似的负载均衡缓存复用场景,给你梳理下完全适配Nginx Ketama算法的Java实现方案,完美匹配你现在的需求——让节点能复用预加载缓存,确保/abc/这类请求不管是Nginx路由还是节点内部计算,都指向同一个节点。

1. 成熟开源实现选型

有几个靠谱的开源实现可以直接用,它们都严格遵循Ketama算法规范,和Nginx的实现完全对齐,适配不同场景:

  • 一个是支持动态节点增减的版本,能应对集群节点在线扩容/缩容的场景,适合规模经常变化的集群
  • 另一个是轻量级实现,代码极简,不需要额外依赖,很适合嵌入到现有服务中快速集成
  • 还有一个附带完整单元测试的版本,能快速验证和Nginx哈希结果的一致性,避免踩坑

这些实现的核心逻辑都是Ketama的标准流程:

  • 给每个真实节点生成多个虚拟节点(默认160个,和Nginx保持一致)
  • 用MD5哈希计算虚拟节点的哈希值,排序后存在哈希环上
  • 对请求的URL前缀(比如/abc/)计算哈希值,在环上找到第一个大于等于该哈希值的虚拟节点,对应的真实节点就是目标节点

2. 极简版Java实现示例

如果不想引入外部依赖,这里给你一个简化但严格对齐Nginx Ketama算法的实现,你可以直接集成到代码里:

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.SortedMap;
import java.util.TreeMap;

public class KetamaConsistentHash {
    // 哈希环,存储虚拟节点哈希值到真实节点的映射
    private final SortedMap<Long, String> hashCircle = new TreeMap<>();
    // 每个真实节点对应的虚拟节点数量,和Nginx保持一致用160
    private final int virtualNodesPerServer;

    public KetamaConsistentHash(int virtualNodesPerServer, String... servers) {
        this.virtualNodesPerServer = virtualNodesPerServer;
        for (String server : servers) {
            addServerNode(server);
        }
    }

    // 添加真实节点,同时生成对应的虚拟节点
    public void addServerNode(String server) {
        for (int i = 0; i < virtualNodesPerServer; i++) {
            long virtualNodeHash = calculateKetamaHash(server + ":" + i);
            hashCircle.put(virtualNodeHash, server);
        }
    }

    // 移除真实节点及对应的虚拟节点
    public void removeServerNode(String server) {
        for (int i = 0; i < virtualNodesPerServer; i++) {
            long virtualNodeHash = calculateKetamaHash(server + ":" + i);
            hashCircle.remove(virtualNodeHash);
        }
    }

    // 根据key(比如URL前缀)获取对应的真实节点
    public String getTargetServer(String key) {
        if (hashCircle.isEmpty()) {
            return null;
        }
        long keyHash = calculateKetamaHash(key);
        // 找到哈希环上第一个大于等于keyHash的虚拟节点
        SortedMap<Long, String> tailMap = hashCircle.tailMap(keyHash);
        long targetHash = tailMap.isEmpty() ? hashCircle.firstKey() : tailMap.firstKey();
        return hashCircle.get(targetHash);
    }

    // 严格按照Ketama算法计算哈希值:MD5取前4字节大端序
    private long calculateKetamaHash(String input) {
        try {
            MessageDigest md5Digest = MessageDigest.getInstance("MD5");
            byte[] md5Bytes = md5Digest.digest(input.getBytes());
            // 取MD5结果的前4字节,转成大端序的long值
            return ((long) (md5Bytes[3] & 0xFF) << 24)
                    | ((long) (md5Bytes[2] & 0xFF) << 16)
                    | ((long) (md5Bytes[1] & 0xFF) << 8)
                    | (md5Bytes[0] & 0xFF);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("MD5算法不可用", e);
        }
    }

    // 测试用例:验证同一URL前缀始终路由到同一节点
    public static void main(String[] args) {
        KetamaConsistentHash hashRouter = new KetamaConsistentHash(160, "node-1", "node-2", "node-3");
        String urlPrefix = "/abc/";
        // 连续两次请求,结果应该一致
        System.out.println(hashRouter.getTargetServer(urlPrefix));
        System.out.println(hashRouter.getTargetServer(urlPrefix));
    }
}

关键注意事项:

  • 虚拟节点数量:一定要和Nginx配置的保持一致(默认是160),否则哈希分布会不一样,导致路由结果不匹配
  • 哈希计算逻辑:必须用MD5取前4字节的大端序,这是Ketama算法的标准,也是Nginx实现的逻辑,确保同一key的计算结果和Nginx完全相同
  • URL前缀处理:计算哈希时的key要和Nginx侧使用的完全一致——比如Nginx是取$uri的第一部分(/abc/),那你这里也要传入相同的字符串到getTargetServer方法

用这个实现,你的节点侧就能和Nginx的路由结果100%匹配,预加载的缓存就能被正确复用啦。

内容的提问来源于stack exchange,提问作者Janning Vygen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 09:39:58