Consistent hashing hash ring, a MoonBit port of hashwheel
let hr : @hashwheel.ConsistentHash[String] = @hashwheel.ConsistentHash::new()
ignore(hr.add("server1"))
ignore(hr.add("server2"))
let node = hr.get("resourceName") // Some("server1") or Some("server2")moon check # type check
moon test # run all tests
moon info # refresh pkg.generated.mbti
moon fmt # format the sources| Parameter | Type | Default | Description |
|---|---|---|---|
| range | Int | 100003 | Ring capacity (control point modulo); a prime is best |
| weight | Int | 40 | Default number of control points per node |
| distribution | Distribution | Random | Random scatter or Uniform interleave |
| order_nodes | (Array[T]) -> Array[T] | none | Uniform only: the order in which pending nodes get their points |
| cache | Int | 0 | LRU cache size for get() results, 0 disables it |
| nodes | Array[T] | none | Nodes to add right away (same as calling add in order) |
| seed | Bytes | none | 32-byte random seed to reproduce a Random ring |
// uniform distribution, 40 control points per node, 100-entry lookup cache
///|
let hr : @hashwheel.ConsistentHash[String] = @hashwheel.ConsistentHash::new(
distribution=Uniform,
cache=100,
)
// a small ring, easy to inspect: range=24, 4 control points per node
///|
let small : @hashwheel.ConsistentHash[String] = @hashwheel.ConsistentHash::new(
range=24,
weight=4,
nodes=["a", "b", "c"],
)ignore(hr.add("server1")) // default weight
ignore(hr.add("server2", weight=120)) // heavier, serves more resources
ignore(hr.add("server3", points=[10, 20, 30]))ignore(hr.remove("server1"))match hr.get("user:42") {
Some(node) => println("handled by \{node}")
None => println("no nodes on the ring")
}///|
let replicas = hr.get_many("user:42", 3) // primary plus two fallbacks| Method | Description |
|---|---|
| hr.get_nodes() -> Array[T] | All nodes currently on the ring, in insertion order |
| hr.get_points(node) -> Array[Int]? | The control points of the node, None if it is not on the ring |
| hr.node_count | Number of nodes on the ring |
| hr.key_count | Number of control points around the ring |
| Item | Description |
|---|---|
| murmur3(name) -> UInt | The MurmurHash3 hash (32-bit unsigned) used for resource names |
| default_range | Default ring capacity, 100003 |
| default_weight | Default control points per node, 40 |
| Distribution | Random / Uniform |
| JavaScript (hashwheel) | MoonBit |
|---|---|
| new ConsistentHash({ range, weight, distribution, orderNodes, cache, nodes }) | ConsistentHash::new(range=, weight=, distribution=, order_nodes=, cache=, nodes=) (plus seed=) |
| hr.add(node, n, points) | hr.add(node, weight=, points=) |
| hr.remove(node) | hr.remove(node) |
| hr.get(name) | hr.get(name) |
| hr.get(name, count) | hr.get_many(name, count) |
| hr.getNodes() | hr.get_nodes() |
| hr.getPoints(node) | hr.get_points(node) |
| hr.nodeCount / hr.keyCount | hr.node_count / hr.key_count |
moon testmoon coverage analyze > uncovered.logmoon.mod module definition
moon.pkg package dependencies (moonbitlang/core/random)
consistent_hash.mbt the ring: ConsistentHash, add/remove/get/get_many/...
hash.mbt MurmurHash3
lru_cache.mbt optional LRU lookup cache
hashwheel_test.mbt blackbox tests
hashwheel_wbtest.mbt whitebox tests
pkg.generated.mbti generated public interface (moon info)
README.mbt.md this file
LICENSE Apache-2.0pub struct ConsistentHash[T] {
node_count : Int
key_count : Int
// private fields
}test {
let hr : @hashwheel.ConsistentHash[String] = @hashwheel.ConsistentHash::new()
ignore(hr.add("server1").add("server2"))
let node = hr.get("resourceName")
assert_true(node is Some("server1" | "server2"))
}fn[T] ConsistentHash::add(self : ConsistentHash[T], node : T, weight? : Int, points? : Array[Int]) -> ConsistentHash[T]test {
let hr : @hashwheel.ConsistentHash[String] = @hashwheel.ConsistentHash::new(
range=24,
weight=4,
)
ignore(hr.add("a").add("b", weight=8))
assert_eq(hr.key_count, 12)
}test {
let hr : @hashwheel.ConsistentHash[String] = @hashwheel.ConsistentHash::new(nodes=[
"n1",
])
assert_eq(hr.get("anything"), Some("n1"))
}fn[T : Eq] ConsistentHash::get_many(self : ConsistentHash[T], name : String, count : Int) -> Array[T]?test {
let hr : @hashwheel.ConsistentHash[String] = @hashwheel.ConsistentHash::new(nodes=[
"a", "b", "c",
])
let nodes = hr.get_many("resource", 2)
assert_eq(nodes.unwrap().length(), 2)
}fn[T] ConsistentHash::new(range? : Int, weight? : Int, distribution? : Distribution, order_nodes? : (Array[T]) -> Array[T], cache? : Int, nodes? : Array[T], seed? : Bytes) -> ConsistentHash[T]test {
let hr : @hashwheel.ConsistentHash[String] = @hashwheel.ConsistentHash::new(nodes=[
"a", "b", "c",
])
assert_eq(hr.node_count, 3)
assert_eq(hr.get_nodes(), ["a", "b", "c"])
}test {
let hr : @hashwheel.ConsistentHash[String] = @hashwheel.ConsistentHash::new(nodes=[
"a", "b", "a",
])
assert_eq(hr.node_count, 3)
ignore(hr.remove("a"))
assert_eq(hr.node_count, 1)
assert_eq(hr.get_nodes(), ["b"])
}pub(all) enum Distribution {
Random
Uniform
}fn murmur3(s : String) -> UInttest {
inspect(@hashwheel.murmur3(""), content="0")
inspect(@hashwheel.murmur3("a"), content="1009084850")
inspect(@hashwheel.murmur3("abc"), content="1968171120")
inspect(@hashwheel.murmur3("resourceName"), content="2023144987")
inspect(@hashwheel.murmur3("š"), content="2007303233")
}Install
Download zipConsistent hashing hash ring, a MoonBit port of hashwheel