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