简体   繁体   中英

How to implement the ketama algorithm in java?

We are using nginx as a loadbalancer and distribute the load based on the first part of the url with a consistent hash . This works fine. Requests to /abc/ are always going to the same node.

On my node I want to implement this algorithm, too. (It is needed to let some jobs run on these nodes and use some preloaded cache elements)

So I need a Java Implementation of the nginx ketama algorithm.

I found a few:

None of these algorithms produce the same results as nginx. This might be because of some ketama points described in the nginx docs or some difference in the hashing algorithm.

I found the nginx hashing algorithm here: https://github.com/nginx/nginx/blob/53d655f89407af017cd193fd4d8d82118c9c2c80/src/stream/ngx_stream_upstream_hash_module.c#L279

When I compare both, I see java is using md5 and nginx is using crc32 besides a lot of other differences.

Is there any compatible implementation in java? Or is there any well defined documentation of the ketama algorithm?

This code works for me. It is not bullet proof as it does not consider weight or other factors like removing nodes. If you like use it, code is public domain.

package org.kicktipp.ketama;

import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.zip.CRC32;

public class NginxKetamaConsistentHash
{
    private final int                       KETAMA_POINTS   = 160;
    private final SortedMap<Long, String>   circle          = new TreeMap<>();

    public NginxKetamaConsistentHash ( List<String> nodes )
    {
        for (String node : nodes)
        {
            addNode(node);
        }
    }

    public String get ( String key )
    {
        if (circle.isEmpty())
        {
            return null;
        }
        long hash = getCrc32(key);
        if (!circle.containsKey(hash))
        {
            SortedMap<Long, String> tailMap = circle.tailMap(hash);
            hash = tailMap.isEmpty() ? circle.firstKey() : tailMap.firstKey();
        }
        return circle.get(hash);
    }

    private void addNode ( String node )
    {
        String server = node;
        String port = "";
        if (node.contains(":"))
        {
            String[] split = node.split(":", 2);
            server = split[0];
            port = split[1];
        }
        byte[] prevHash = new byte[4];
        for (int i = 0; i < KETAMA_POINTS; i++)
        {
            CRC32 hash = getBaseHash(server, port);
            hash.update(prevHash);
            long value = hash.getValue();
            circle.put(value, node);
            prevHash = getPrevHash(value);
        }
    }

    private byte[] getPrevHash ( long value )
    {
        byte[] array = Arrays.copyOfRange(longToBytes(value), 4, 8);
        for (int i = 0; i < array.length / 2; i++)
        {
            byte temp = array[i];
            array[i] = array[array.length - i - 1];
            array[array.length - i - 1] = temp;
        }
        return array;
    }

    private CRC32 getBaseHash ( String server, String port )
    {
        CRC32 baseHash = new CRC32();
        baseHash.update(server.getBytes(StandardCharsets.US_ASCII));
        baseHash.update("\0".getBytes(StandardCharsets.US_ASCII));
        baseHash.update(port.getBytes(StandardCharsets.US_ASCII));
        return baseHash;
    }

    private byte[] longToBytes ( long x )
    {
        ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
        buffer.putLong(x);
        return buffer.array();
    }

    private long getCrc32 ( final String k )
    {
        CRC32 hash = new CRC32();
        hash.update(k.getBytes());
        return hash.getValue();
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM