apigate_core/balancing/
consistent_hash.rs1use std::sync::atomic::{AtomicUsize, Ordering};
2
3use super::{BalanceCtx, Balancer};
4
5pub struct ConsistentHash {
6 offset: AtomicUsize,
7}
8
9impl ConsistentHash {
10 pub fn new() -> Self {
11 Self {
12 offset: AtomicUsize::new(0),
13 }
14 }
15}
16
17impl Default for ConsistentHash {
18 fn default() -> Self {
19 Self::new()
20 }
21}
22
23impl Balancer for ConsistentHash {
24 #[inline]
25 fn pick(&self, ctx: &BalanceCtx) -> Option<usize> {
26 let candidate_len = ctx.candidate_len();
27 if candidate_len == 0 {
28 return None;
29 }
30
31 match ctx.affinity {
32 Some(key) => {
33 let hash = xxhash_rust::xxh3::xxh3_64(key.as_str().as_bytes());
34 pick_candidate(hash, ctx)
35 }
36 None => {
37 let n = self.offset.fetch_add(1, Ordering::Relaxed);
38
39 let nth = if candidate_len.is_power_of_two() {
40 n & (candidate_len - 1)
41 } else {
42 n % candidate_len
43 };
44
45 ctx.candidate_index(nth)
46 }
47 }
48 }
49}
50
51#[inline]
52fn pick_candidate(hash: u64, ctx: &BalanceCtx<'_>) -> Option<usize> {
53 let bucket = jump_consistent_hash(hash, ctx.candidate_len())?;
54 ctx.candidate_index(bucket)
55}
56
57#[inline]
58fn jump_consistent_hash(mut key: u64, buckets: usize) -> Option<usize> {
59 if buckets == 0 {
60 return None;
61 }
62
63 let mut b: i64 = -1;
64 let mut j: i64 = 0;
65 let buckets = buckets as i64;
66
67 while j < buckets {
68 b = j;
69 key = key.wrapping_mul(2862933555777941757).wrapping_add(1);
70 j = ((b + 1) * (1_i64 << 31)) / (((key >> 33) + 1) as i64);
71 }
72
73 Some(b as usize)
74}