Skip to content

Commit bc58a36

Browse files
committed
Add the implementation......
1 parent 0226208 commit bc58a36

File tree

4 files changed

+162
-0
lines changed

4 files changed

+162
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,6 @@
1010

1111
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
1212
hs_err_pid*
13+
14+
*.iml
15+
.idea

pom.xml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
7+
<groupId>com.xqbase.java</groupId>
8+
<artifactId>hash</artifactId>
9+
<version>1.0-SNAPSHOT</version>
10+
11+
<dependencies>
12+
<dependency>
13+
<groupId>junit</groupId>
14+
<artifactId>junit</artifactId>
15+
<version>4.8.2</version>
16+
<scope>test</scope>
17+
</dependency>
18+
<dependency>
19+
<groupId>com.google.guava</groupId>
20+
<artifactId>guava</artifactId>
21+
<version>19.0</version>
22+
<scope>test</scope>
23+
</dependency>
24+
</dependencies>
25+
</project>
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package com.xqbase.java.hash;
2+
3+
/**
4+
* Jump Consistent Hash implementation.
5+
*/
6+
public final class Hashing {
7+
8+
/**
9+
* Assigns to {@code input} a "bucket" in the range {@code [0, buckets)}, in a uniform
10+
* manner that minimizes the need for remapping as {@code buckets} grows. That is,
11+
* {@code consistentHash(h, n)} equals:
12+
*
13+
* <ul>
14+
* <li>{@code n - 1}, with approximate probability {@code 1/n}
15+
* <li>{@code consistentHash(h, n - 1)}, otherwise (probability {@code 1 - 1/n})
16+
* </ul>
17+
*
18+
* <p>See the <a href="http://en.wikipedia.org/wiki/Consistent_hashing">wikipedia
19+
* article on consistent hashing</a> for more information.
20+
*/
21+
public static int consistentHash(long input, int buckets) {
22+
if (buckets <= 0) {
23+
throw new IllegalArgumentException("buckets must be positive");
24+
}
25+
LinearCongruentialGenerator generator = new LinearCongruentialGenerator(input);
26+
int candidate = 0;
27+
int next;
28+
29+
while (true) {
30+
next = (int) ((candidate + 1) / generator.nextDouble());
31+
if (next >=0 && next < buckets) {
32+
candidate = next;
33+
} else {
34+
return candidate;
35+
}
36+
}
37+
}
38+
39+
/**
40+
* Linear CongruentialGenerator to use for consistent hashing.
41+
* See http://en.wikipedia.org/wiki/Linear_congruential_generator
42+
*/
43+
private static final class LinearCongruentialGenerator {
44+
private long state;
45+
46+
public LinearCongruentialGenerator(long seed) {
47+
this.state = seed;
48+
}
49+
50+
public double nextDouble() {
51+
state = 2862933555777941757L * state + 1;
52+
return ((double) ((int) (state >>> 33) + 1)) / (0x1.0p31);
53+
}
54+
}
55+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package com.xqbase.java.hash;
2+
3+
import com.google.common.util.concurrent.AtomicLongMap;
4+
import junit.framework.TestCase;
5+
6+
import java.util.Random;
7+
8+
/**
9+
* Consistent Hash Test Case.
10+
*/
11+
public class HashingTest extends TestCase {
12+
13+
private static final int ITERS = 10000;
14+
private static final int MAX_SHARDS = 500;
15+
16+
public void testConsistentHash_correctness() {
17+
long[] interestingValues = { -1, 0, 1, 2, Long.MAX_VALUE, Long.MIN_VALUE };
18+
for (long h : interestingValues) {
19+
checkConsistentHashCorrectness(h);
20+
}
21+
Random r = new Random(7);
22+
for (int i = 0; i < 20; i++) {
23+
checkConsistentHashCorrectness(r.nextLong());
24+
}
25+
}
26+
27+
private void checkConsistentHashCorrectness(long hashCode) {
28+
int last = 0;
29+
for (int shards = 1; shards <= 100000; shards++) {
30+
int b = Hashing.consistentHash(hashCode, shards);
31+
if (b != last) {
32+
assertEquals(shards - 1, b);
33+
last = b;
34+
}
35+
}
36+
}
37+
38+
public void testConsistentHash_probabilities() {
39+
AtomicLongMap<Integer> map = AtomicLongMap.create();
40+
Random r = new Random(9);
41+
for (int i = 0; i < ITERS; i++) {
42+
countRemaps(r.nextLong(), map);
43+
}
44+
for (int shard = 2; shard <= MAX_SHARDS; shard++) {
45+
// Rough: don't exceed 1.2x the expected number of remaps by more than 20
46+
assertTrue(map.get(shard) <= 1.2 * ITERS / shard + 20);
47+
}
48+
}
49+
50+
private void countRemaps(long h, AtomicLongMap<Integer> map) {
51+
int last = 0;
52+
for (int shards = 2; shards <= MAX_SHARDS; shards++) {
53+
int chosen = Hashing.consistentHash(h, shards);
54+
if (chosen != last) {
55+
map.incrementAndGet(shards);
56+
last = chosen;
57+
}
58+
}
59+
}
60+
61+
/**
62+
* Check a few "golden" values to see that implementations across languages
63+
* are equivalent.
64+
*/
65+
public void testConsistentHash_linearCongruentialGeneratorCompatibility() {
66+
int[] golden100 =
67+
{ 0, 55, 62, 8, 45, 59, 86, 97, 82, 59,
68+
73, 37, 17, 56, 86, 21, 90, 37, 38, 83 };
69+
for (int i = 0; i < golden100.length; i++) {
70+
assertEquals(golden100[i], Hashing.consistentHash(i, 100));
71+
}
72+
assertEquals(6, Hashing.consistentHash(10863919174838991L, 11));
73+
assertEquals(3, Hashing.consistentHash(2016238256797177309L, 11));
74+
assertEquals(5, Hashing.consistentHash(1673758223894951030L, 11));
75+
assertEquals(80343, Hashing.consistentHash(2, 100001));
76+
assertEquals(22152, Hashing.consistentHash(2201, 100001));
77+
assertEquals(15018, Hashing.consistentHash(2202, 100001));
78+
}
79+
}

0 commit comments

Comments
 (0)