-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathConsistentHashRing.h
More file actions
175 lines (149 loc) · 5.57 KB
/
ConsistentHashRing.h
File metadata and controls
175 lines (149 loc) · 5.57 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
#include <vector>
#include <memory>
#include <functional>
#include <algorithm>
#include <iostream>
#pragma once
template <typename node_t, typename key_t>
class ConsistentHashRing
{
public:
ConsistentHashRing() = default;
ConsistentHashRing(const std::vector<node_t> &nodes)
{
std::copy_if(nodes.begin(), nodes.end(), std::back_inserter(m_nodes),
[this](node_t node){return node != this->nullNode();} );
}
ConsistentHashRing(const node_t &node)
{
if(node != nullNode()) emplaceNode(node);
}
~ConsistentHashRing() = default;
uint32_t size() const { return m_nodes.size(); }
std::vector<node_t> getNodes() const
{
std::vector<node_t> ret;
std::copy_if(m_nodes.begin(), m_nodes.end(), std::back_inserter(ret),
[this](node_t node){return node != this->nullNode();} );
return ret;
}
uint32_t getNodeIndex(node_t node) const
{
for(int i=0; i<m_nodes.size(); i++)
{ //no better way unless we enforce that data will be sorted
if(m_nodes[i] != nullNode() && *node == *m_nodes[i]) return i;
} return UINT32_MAX;
}
node_t nodePredecessor(key_t key) const
{ //get the node preceding modulo of hash of this key
size_t number_nodes = m_nodes.size();
size_t i = m_key_hasher(key, number_nodes);
uint32_t index = (i + (number_nodes - 1)) % number_nodes; // i + (n-1), basically go around the ring to the node get behind... the predecessor.
if(m_nodes[index] != nullNode()) return m_nodes[index]; //need to check for gaps in the ring.
//else, walk the ring to find next non-null predecessor
int walked_nodes = 1;
int nIndex = index;
while (walked_nodes <= number_nodes)
{
nIndex = nIndex - 1;
if (nIndex < 0) nIndex = number_nodes - 1;
node_t nNode = m_nodes[nIndex];
if (nNode != nullNode()) return nNode;
walked_nodes++;
}
return nullNode(); //all null!
}
node_t nodeSuccessor(key_t key) const
{ //get node succeeding hash of this key
size_t number_nodes = m_nodes.size();
size_t i = m_key_hasher(key, number_nodes);
uint32_t index = (i + 1) % number_nodes;
if(m_nodes[index] != nullNode()) return m_nodes[index];
//else, walk the ring to find next non-null successor
int walked_nodes = 1;
int nIndex = index;
while (walked_nodes <= number_nodes)
{
nIndex = nIndex + 1;
if (nIndex >= number_nodes) nIndex = 0;
node_t nNode = m_nodes[nIndex];
if (nNode != nullNode()) return nNode;
walked_nodes++;
}
return nullNode(); //all null!
}
node_t getNodeAtIndex(uint32_t index) const
{
try { return m_nodes.at(index); }
catch(const std::out_of_range& oor) {
std::cout<<"Failed to get node at index "<<index<<"\n";
return nullNode();
}
}
void pushNode(const node_t &node) { m_nodes.push_back(node); }
void emplaceNode(const node_t &node) { m_nodes.emplace_back(node); }
bool replaceNode(const node_t &node)
{
for(int i =0; i < m_nodes.size(); i++) {
if(m_nodes[i] == nullNode()) {
m_nodes[i] = node; //since we try to keep ring size constant for consistency, remove op leaves gaps
return true;
}
}
emplaceNode(node); //if no gaps, expand ring, which is less impactful op than resizing.
return true;
}
bool replaceNode(const node_t &old_node, const node_t &new_node)
{
uint32_t index = getNodeIndex(old_node);
if (index == UINT32_MAX)
{
std::cout<<"Failed to replace node\n";
return false;
}
m_nodes[index] = new_node;
return true;
}
bool removeNode(const node_t &node)
{ //resizing the ring would mean hashes become inconsistent
uint32_t index = getNodeIndex(node);
if (index == UINT32_MAX)
{
std::cout<<"Failed to remove node\n";
return false;
}
m_nodes[index] = nullNode(); //hence we simply leave gaps
return true;
}
void trimRing()
{ //get rid of null nodes
std::vector<node_t> new_nodes_arr;
for(const auto& it : m_nodes) {
if(it != nullNode()) new_nodes_arr.emplace_back(it);
}
m_nodes = new_nodes_arr;
}
bool isRingEmpty() const
{ //is vector empty or else are all nodes null?
if(m_nodes.empty()) return true;
return std::all_of(m_nodes.begin(), m_nodes.end(), [this](node_t node) { return node == this->nullNode(); });
}
uint32_t getNumberNodes() const
{ //count non-null nodes
return std::count_if(m_nodes.begin(), m_nodes.end(), [this](node_t node) { return node != this->nullNode(); });
}
void setHasher(const std::function<size_t(key_t, uint32_t)>& key_hasher) { m_key_hasher = key_hasher; }
private:
std::vector<node_t> m_nodes; //nodes typically represent servers/endpoints for load balancing
std::hash<key_t> m_key_std_hash; //by default, std::hash
std::function<size_t(key_t, uint32_t)> m_key_hasher = [=](key_t key, uint32_t nNodes)->size_t { return m_key_std_hash(key); };
//https://stackoverflow.com/questions/41853159/how-to-detect-if-a-type-is-shared-ptr-at-compile-time
template <class T> struct is_shared_ptr : std::false_type {};
template <class T> struct is_shared_ptr<std::shared_ptr<T> > : std::true_type {};
node_t nullNode() const
{
if(is_shared_ptr<node_t>{}) return nullptr;
if(std::is_pointer<node_t>{}) return nullptr; //plain pointer
return node_t(); //if all else fails, try default constructing
}
};