consist/lib.rs
1//! consist: An implementation of consistent hashing in Rust.
2//! The goal of consistent hashing is to partition entries in such a way that the addition or
3//! removal of buckets minimizes the number of items that must be shifted between buckets, i.e. it
4//! optimizes the rehashing stage that is usually needed for hash tables.
5//! The algorithm was originally put forth by David Karger et al. in their 1997 paper
6//! "Consistent Hashing and Random Trees".
7//! As of version 0.3.0, we use CRC64 with ECMA polynomial, so that updating the rust version does
8//! not change behavior.
9#![feature(btree_range, collections_bound)]
10
11extern crate crc;
12
13use crc::crc64::{ECMA, Digest, Hasher64};
14
15use std::collections::BTreeMap;
16use std::hash::{Hash, Hasher};
17use std::collections::Bound::{Included, Excluded, Unbounded};
18
19/// A newtype that wraps a Hasher64 and provides implementation of std::hash::Hasher methods.
20struct Hasher64StdHashHasher<T: Hasher64>(T);
21
22impl<T> Hasher for Hasher64StdHashHasher<T>
23 where T: Hasher64
24{
25 fn write(&mut self, bytes: &[u8]) {
26 self.0.write(bytes);
27 }
28
29 fn finish(&self) -> u64 {
30 self.0.sum64()
31 }
32}
33
34/// HashRing is a type that tracks a set of buckets corresponding to a collection of resources,
35/// usually servers. Items are hashed to these buckets in such a way that few items change their
36/// bucket mapping when one is eliminated.
37pub struct HashRing<B: Hash> {
38 buckets: BTreeMap<u64, B>,
39}
40
41impl<B> HashRing<B>
42 where B: Hash
43{
44 /// Get a new HashRing.
45 pub fn new() -> HashRing<B> {
46 HashRing::<B> { buckets: BTreeMap::new() }
47 }
48
49 /// Adds the specified bucket to the hash ring.
50 pub fn add_bucket(&mut self, bucket: B) {
51 let hash_code = HashRing::<B>::get_hash_code(&bucket);
52 self.buckets.insert(hash_code, bucket);
53 }
54
55 /// Finds the corresponding bucket for this item.
56 pub fn get_bucket<T>(&self, item: &T) -> Option<&B>
57 where T: Hash
58 {
59 let hash_code = HashRing::<B>::get_hash_code(item);
60 // If there are no buckets in the end of the circle from here to the end, we wrap around
61 // and try from the beginning.
62 let back_half = self.buckets.range((Included(&hash_code), Unbounded)).next().map(|val| val.1);
63 let front_half = self.buckets
64 .range((Included(&0), Excluded(&hash_code)))
65 .next()
66 .map(|val| val.1);
67 back_half.or(front_half)
68 }
69
70 /// Eliminate a bucket from the ring. All items mapped to this bucket will be mapped to the
71 /// next bucket in hash order (with wraparound).
72 /// Returns true if removal is successful, or false if the removal has no effect.
73 pub fn remove_bucket(&mut self, bucket: &B) -> bool {
74 let code = HashRing::<B>::get_hash_code(bucket);
75 match self.buckets.remove(&code) {
76 None => false,
77 Some(..) => true,
78 }
79 }
80
81 fn get_hash_code<H>(value: &H) -> u64
82 where H: Hash
83 {
84 let mut hasher = Hasher64StdHashHasher(Digest::new(ECMA));
85 value.hash(&mut hasher);
86 hasher.finish()
87 }
88}