27
27
import net.spy.memcached.util.DefaultKetamaNodeLocatorConfiguration;
28
28
import net.spy.memcached.util.KetamaNodeLocatorConfiguration;
29
29
30
+ import java.net.InetSocketAddress;
30
31
import java.util.ArrayList;
31
32
import java.util.Collection;
33
+ import java.util.HashMap;
32
34
import java.util.Iterator;
33
35
import java.util.List;
34
36
import java.util.Map;
@@ -51,6 +53,8 @@ public final class KetamaNodeLocator extends SpyObject implements NodeLocator {
51
53
private volatile Collection<MemcachedNode> allNodes;
52
54
53
55
private final HashAlgorithm hashAlg;
56
+ private final Map<InetSocketAddress, Integer> weights;
57
+ private final boolean isWeightedKetama;
54
58
private final KetamaNodeLocatorConfiguration config;
55
59
56
60
/**
@@ -63,7 +67,26 @@ public final class KetamaNodeLocator extends SpyObject implements NodeLocator {
63
67
* consistent hash continuum
64
68
*/
65
69
public KetamaNodeLocator(List<MemcachedNode> nodes, HashAlgorithm alg) {
66
- this(nodes, alg, new DefaultKetamaNodeLocatorConfiguration());
70
+ this(nodes, alg, KetamaNodeKeyFormatter.Format.SPYMEMCACHED, new HashMap<InetSocketAddress, Integer>());
71
+ }
72
+
73
+ /**
74
+ * Create a new KetamaNodeLocator with specific nodes, hash, node key format,
75
+ * and weight
76
+ *
77
+ * @param nodes The List of nodes to use in the Ketama consistent hash
78
+ * continuum
79
+ * @param alg The hash algorithm to use when choosing a node in the Ketama
80
+ * consistent hash continuum
81
+ * @param nodeKeyFormat the format used to name the nodes in Ketama, either
82
+ * SPYMEMCACHED or LIBMEMCACHED
83
+ * @param weights node weights for ketama, a map from InetSocketAddress to
84
+ * weight as Integer
85
+ */
86
+ public KetamaNodeLocator(List<MemcachedNode> nodes, HashAlgorithm alg,
87
+ KetamaNodeKeyFormatter.Format nodeKeyFormat,
88
+ Map<InetSocketAddress, Integer> weights) {
89
+ this(nodes, alg, weights, new DefaultKetamaNodeLocatorConfiguration(new KetamaNodeKeyFormatter(nodeKeyFormat)));
67
90
}
68
91
69
92
/**
@@ -78,21 +101,44 @@ public KetamaNodeLocator(List<MemcachedNode> nodes, HashAlgorithm alg) {
78
101
*/
79
102
public KetamaNodeLocator(List<MemcachedNode> nodes, HashAlgorithm alg,
80
103
KetamaNodeLocatorConfiguration conf) {
104
+ this(nodes, alg, new HashMap<InetSocketAddress, Integer>(), conf);
105
+ }
106
+
107
+ /**
108
+ * Create a new KetamaNodeLocator with specific nodes, hash, node key format,
109
+ * and weight
110
+ *
111
+ * @param nodes The List of nodes to use in the Ketama consistent hash
112
+ * continuum
113
+ * @param alg The hash algorithm to use when choosing a node in the Ketama
114
+ * consistent hash continuum
115
+ * @param weights node weights for ketama, a map from InetSocketAddress to
116
+ * weight as Integer
117
+ * @param configuration node locator configuration
118
+ */
119
+ public KetamaNodeLocator(List<MemcachedNode> nodes, HashAlgorithm alg,
120
+ Map<InetSocketAddress, Integer> nodeWeights,
121
+ KetamaNodeLocatorConfiguration configuration) {
81
122
super();
82
123
allNodes = nodes;
83
124
hashAlg = alg;
84
- config = conf;
125
+ config = configuration;
126
+ weights = nodeWeights;
127
+ isWeightedKetama = !weights.isEmpty();
85
128
setKetamaNodes(nodes);
86
129
}
87
130
88
131
private KetamaNodeLocator(TreeMap<Long, MemcachedNode> smn,
89
132
Collection<MemcachedNode> an, HashAlgorithm alg,
133
+ Map<InetSocketAddress, Integer> nodeWeights,
90
134
KetamaNodeLocatorConfiguration conf) {
91
135
super();
92
136
ketamaNodes = smn;
93
137
allNodes = an;
94
138
hashAlg = alg;
95
139
config = conf;
140
+ weights = nodeWeights;
141
+ isWeightedKetama = !weights.isEmpty();
96
142
}
97
143
98
144
public Collection<MemcachedNode> getAll() {
@@ -147,7 +193,7 @@ public NodeLocator getReadonlyCopy() {
147
193
an.add(new MemcachedNodeROImpl(n));
148
194
}
149
195
150
- return new KetamaNodeLocator(smn, an, hashAlg, config);
196
+ return new KetamaNodeLocator(smn, an, hashAlg, weights, config);
151
197
}
152
198
153
199
@Override
@@ -172,30 +218,62 @@ protected TreeMap<Long, MemcachedNode> getKetamaNodes() {
172
218
*/
173
219
protected void setKetamaNodes(List<MemcachedNode> nodes) {
174
220
TreeMap<Long, MemcachedNode> newNodeMap =
175
- new TreeMap<Long, MemcachedNode>();
221
+ new TreeMap<Long, MemcachedNode>();
176
222
int numReps = config.getNodeRepetitions();
223
+ int nodeCount = nodes.size();
224
+ int totalWeight = 0;
225
+
226
+ if (isWeightedKetama) {
227
+ for (MemcachedNode node : nodes) {
228
+ totalWeight += weights.get(node.getSocketAddress());
229
+ }
230
+ }
231
+
177
232
for (MemcachedNode node : nodes) {
178
- // Ketama does some special work with md5 where it reuses chunks.
179
- if (hashAlg == DefaultHashAlgorithm.KETAMA_HASH) {
180
- for (int i = 0; i < numReps / 4; i++) {
181
- byte[] digest =
182
- DefaultHashAlgorithm.computeMd5(config.getKeyForNode(node, i));
183
- for (int h = 0; h < 4; h++) {
184
- Long k = ((long) (digest[3 + h * 4] & 0xFF) << 24)
185
- | ((long) (digest[2 + h * 4] & 0xFF) << 16)
186
- | ((long) (digest[1 + h * 4] & 0xFF) << 8)
187
- | (digest[h * 4] & 0xFF);
188
- newNodeMap.put(k, node);
189
- getLogger().debug("Adding node %s in position %d", node, k);
233
+ if (isWeightedKetama) {
234
+
235
+ int thisWeight = weights.get(node.getSocketAddress());
236
+ float percent = (float)thisWeight / (float)totalWeight;
237
+ int pointerPerServer = (int)((Math.floor((float)(percent * (float)config.getNodeRepetitions() / 4 * (float)nodeCount + 0.0000000001))) * 4);
238
+ for (int i = 0; i < pointerPerServer / 4; i++) {
239
+ for(long position : ketamaNodePositionsAtIteration(node, i)) {
240
+ newNodeMap.put(position, node);
241
+ getLogger().debug("Adding node %s with weight %s in position %d", node, thisWeight, position);
242
+ }
190
243
}
191
- }
192
244
} else {
193
- for (int i = 0; i < numReps; i++) {
194
- newNodeMap.put(hashAlg.hash(config.getKeyForNode(node, i)), node);
195
- }
245
+ // Ketama does some special work with md5 where it reuses chunks.
246
+ // Check to be backwards compatible, the hash algorithm does not
247
+ // matter for Ketama, just the placement should always be done using
248
+ // MD5
249
+ if (hashAlg == DefaultHashAlgorithm.KETAMA_HASH) {
250
+ for (int i = 0; i < numReps / 4; i++) {
251
+ for(long position : ketamaNodePositionsAtIteration(node, i)) {
252
+ newNodeMap.put(position, node);
253
+ getLogger().debug("Adding node %s in position %d", node, position);
254
+ }
255
+ }
256
+ } else {
257
+ for (int i = 0; i < numReps; i++) {
258
+ newNodeMap.put(hashAlg.hash(config.getKeyForNode(node, i)), node);
259
+ }
260
+ }
196
261
}
197
262
}
198
263
assert newNodeMap.size() == numReps * nodes.size();
199
264
ketamaNodes = newNodeMap;
200
265
}
266
+
267
+ private List<Long> ketamaNodePositionsAtIteration(MemcachedNode node, int iteration) {
268
+ List<Long> positions = new ArrayList<Long>();
269
+ byte[] digest = DefaultHashAlgorithm.computeMd5(config.getKeyForNode(node, iteration));
270
+ for (int h = 0; h < 4; h++) {
271
+ Long k = ((long) (digest[3 + h * 4] & 0xFF) << 24)
272
+ | ((long) (digest[2 + h * 4] & 0xFF) << 16)
273
+ | ((long) (digest[1 + h * 4] & 0xFF) << 8)
274
+ | (digest[h * 4] & 0xFF);
275
+ positions.add(k);
276
+ }
277
+ return positions;
278
+ }
201
279
}
0 commit comments