Install uhashring
masterYou can install uhashring via PyPI using pip, or via Gentoo Linux using emerge.
$ pip install uhashring
# Gentoo Linux
$ sudo emerge -a uhashringrepository·master·Indexed 42 days ago
https://github.com/ultrabug/uhashringA pure Python implementation of consistent hashing designed for distributed systems like caches and databases to minimize key reshuffling when nodes change. It is compatible with the Ketama algorithm and supports custom hash functions, dynamic node weights via weight_fn, and monkey patching for python-memcached.
You can install uhashring via PyPI using pip, or via Gentoo Linux using emerge.
$ pip install uhashring
# Gentoo Linux
$ sudo emerge -a uhashringYou can use uhashring to provide consistent hashing for python-memcached by using the monkey module.
import memcache
from uhashring import monkey
monkey.patch_memcache()
mc = memcache.Client(['node1:11211', 'node2:11211'])You can pass a dictionary to the nodes parameter to associate each node with specific configuration like hostname, port, vnodes, weight, or a service instance (e.g., a Redis client).
When using the advanced dictionary configuration, HashRing supports dictionary-like access to interact directly with the node's instance.
from uhashring import HashRing
# Mapping of dict configs
nodes = {
'node1': {
'hostname': 'node1.fqdn',
'instance': redis.StrictRedis(host='node1.fqdn'),
'port': 6379,
'vnodes': 40,
'weight': 1
},
'node2': {
'hostname': 'node2.fqdn',
'instance': redis.StrictRedis(host='node2.fqdn'),
'port': 6379,
'vnodes': 40
},
'node3': {
'hostname': 'node3.fqdn',
'port': 6379
}
}
# create a new consistent hash ring with the nodes
hr = HashRing(nodes)
# set the 'coconut' key/value on the right host's redis instance
hr['coconut'].set('coconut', 'my_value')
# get the 'coconut' key from the right host's redis instance
hr['coconut'].get('coconut')
# delete the 'coconut' key on the right host's redis instance
hr['coconut'].delete('coconut')
# get the node config for the 'coconut' key
conf = hr.get('coconut')To use uhashring for simple consistent hashing, import HashRing and initialize it with a list of node names. You can then retrieve the target node for a specific key using .get_node(key).
from uhashring import HashRing
# create a consistent hash ring of 3 nodes of weight 1
hr = HashRing(nodes=['node1', 'node2', 'node3'])
# get the node name for the 'coconut' key
target_node = hr.get_node('coconut')When initializing HashRing, you can use the following options:
nodes: The nodes used to create the continuum.hash_fn: A callable used to hash keys. Set to 'ketama' for Ketama compatibility.vnodes: The default number of virtual nodes per node.weight_fn: A user-provided function to calculate a node's weight (receives node config as kwargs).replicas: Changes the number of Ketama ring replicas (defaults to 4).If you do not require Ketama compatibility, you can provide any callable to the hash_fn parameter to use a custom hashing algorithm (e.g., MurmurHash).
from uhashring import HashRing
# import your own hash function (must be a callable)
# in this example, MurmurHash v3
from mmh3 import hash as m3h
# this is a 3 nodes consistent hash ring with user defined hash function
hr = HashRing(nodes=['node1', 'node2', 'node3'], hash_fn=m3h)
# now all lookup operations will use the m3h hash function
print(hr.get_node('my key hashed by your function'))If you require compatibility with the Ketama algorithm, set the hash_fn parameter to 'ketama' when initializing the HashRing.
from uhashring import HashRing
# create a consistent hash ring of 3 nodes of weight 1
hr = HashRing(nodes=['node1', 'node2', 'node3'], hash_fn='ketama')
# get the node name for the 'coconut' key
target_node = hr.get_node('coconut')You can provide a custom weight_fn to the HashRing constructor. This function receives the node's configuration dictionary as keyword arguments and should return a numeric weight. This is useful for dynamic weight assignment based on node properties.
from uhashring import HashRing
def weight_fn(**conf):
"""Returns the last digit of the node name as its weight.
:param conf: node configuration in the ring, example:
{
'hostname': 'node3',
'instance': None,
'nodename': 'node3',
'port': None,
'vnodes': 40,
'weight': 1
}
"""
return int(conf['nodename'][-1])
# this is a 3 nodes consistent hash ring with user defined weight function
hr = HashRing(nodes=['node1', 'node2', 'node3'], weight_fn=weight_fn)
# distribution with custom weight assignment
print(hr.distribution)The HashRing allows you to modify the ring topology at runtime using add_node(nodename, conf) and remove_node(nodename).
from uhashring import HashRing
# this is a 3 nodes consistent hash ring
hr = HashRing(nodes=['node1', 'node2', 'node3'])
# this becomes a 2 nodes consistent hash ring
hr.remove_node('node2')
# add back node2
hr.add_node('node2')
# add node4 with a weight of 10
hr.add_node('node4', {'weight': 10})The HashRing class exposes the following properties:
conf: Dictionary of all nodes and their configurations.continuum: Alias for ring.distribution: A counter of the nodes' distribution in the ring.nodes: Alias for conf.ring: The hash key/node mapping of the consistent hash ring.size: The size of the consistent hash ring.The HashRing class provides the following methods:
add_node(nodename, conf): Add or overwrite a node with the given config.get(key): Returns the node object dict matching the hashed key.get_key(key): Alias for the hashing method; returns the hash of the given key.get_instances(): Returns a list of instances of all configured nodes.get_node(key): Returns the node name matching the hashed key.get_node_hostname(key): Returns the hostname of the node matching the hashed key.get_node_instance(key): Returns the instance of the node matching the hashed key.get_node_port(key): Returns the port of the node matching the hashed key.get_node_pos(key): Returns the index position of the node matching the hashed key.get_node_weight(key): Returns the weight of the node matching the hashed key.get_nodes(): Returns a list of names of all configured nodes.get_points(): Returns a Ketama-compatible list of (position, nodename) tuples.get_server(key): Returns a Ketama-compatible (position, nodename) tuple.hashi(key): Returns the hash of the given key (on Ketama mode, matches libketama).iterate_nodes(key, distinct): Returns a generator of tuples (Ketama compatibility implementation).print_continuum(): Prints a Ketama-compatible continuum report.range(key, size, unique): Returns a list of up to size nodes' configurations available in the ring.regenerate: Regenerates the ring from current configuration (required when using weight_fn).remove_node(nodename): Removes the given node from the ring.The HashRing class is the primary interface for implementing consistent hashing. It allows you to manage a set of nodes and map keys to those nodes consistently, which is useful for distributed systems and load balancing.
from uhashring import HashRing
# Example initialization (actual parameters depend on HashRing implementation)
ring = HashRing(nodes=...)