Skip to content

Commit e66f498

Browse files
authored
Introduce interface(s) for hashing CommandObject (#3743)
1 parent 333dcd7 commit e66f498

11 files changed

+194
-85
lines changed

Diff for: src/main/java/redis/clients/jedis/csc/util/CaffeineCSC.java renamed to src/main/java/redis/clients/jedis/csc/CaffeineCSC.java

+18-32
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,27 @@
1-
package redis.clients.jedis.csc.util;
1+
package redis.clients.jedis.csc;
22

33
import com.github.benmanes.caffeine.cache.Cache;
44
import com.github.benmanes.caffeine.cache.Caffeine;
55
import java.util.concurrent.TimeUnit;
6-
import net.openhft.hashing.LongHashFunction;
76

8-
import redis.clients.jedis.CommandObject;
9-
import redis.clients.jedis.args.Rawable;
10-
import redis.clients.jedis.csc.ClientSideCache;
11-
import redis.clients.jedis.csc.ClientSideCacheable;
12-
import redis.clients.jedis.csc.DefaultClientSideCacheable;
7+
import redis.clients.jedis.csc.hash.CommandLongHashing;
8+
import redis.clients.jedis.csc.hash.OpenHftHashing;
139

1410
public class CaffeineCSC extends ClientSideCache {
1511

16-
private static final LongHashFunction DEFAULT_HASH_FUNCTION = LongHashFunction.xx3();
17-
1812
private final Cache<Long, Object> cache;
19-
private final LongHashFunction hashFunction;
2013

21-
public CaffeineCSC(Cache<Long, Object> caffeineCache, LongHashFunction hashFunction) {
22-
super();
23-
this.cache = caffeineCache;
24-
this.hashFunction = hashFunction;
14+
public CaffeineCSC(Cache<Long, Object> caffeineCache) {
15+
this(caffeineCache, new OpenHftHashing(OpenHftHashing.DEFAULT_HASH_FUNCTION), DefaultClientSideCacheable.INSTANCE);
16+
}
17+
18+
public CaffeineCSC(Cache<Long, Object> caffeineCache, ClientSideCacheable cacheable) {
19+
this(caffeineCache, new OpenHftHashing(OpenHftHashing.DEFAULT_HASH_FUNCTION), cacheable);
2520
}
2621

27-
public CaffeineCSC(Cache<Long, Object> caffeineCache, LongHashFunction function, ClientSideCacheable cacheable) {
28-
super(cacheable);
22+
public CaffeineCSC(Cache<Long, Object> caffeineCache, CommandLongHashing hashing, ClientSideCacheable cacheable) {
23+
super(hashing, cacheable);
2924
this.cache = caffeineCache;
30-
this.hashFunction = function;
3125
}
3226

3327
@Override
@@ -50,17 +44,6 @@ protected Object getValue(long hash) {
5044
return cache.getIfPresent(hash);
5145
}
5246

53-
@Override
54-
protected final long getHash(CommandObject command) {
55-
long[] nums = new long[command.getArguments().size() + 1];
56-
int idx = 0;
57-
for (Rawable raw : command.getArguments()) {
58-
nums[idx++] = hashFunction.hashBytes(raw.getRaw());
59-
}
60-
nums[idx] = hashFunction.hashInt(command.getBuilder().hashCode());
61-
return hashFunction.hashLongs(nums);
62-
}
63-
6447
public static Builder builder() {
6548
return new Builder();
6649
}
@@ -71,7 +54,8 @@ public static class Builder {
7154
private long expireTime = DEFAULT_EXPIRE_SECONDS;
7255
private final TimeUnit expireTimeUnit = TimeUnit.SECONDS;
7356

74-
private LongHashFunction hashFunction = DEFAULT_HASH_FUNCTION;
57+
// not using a default value to avoid an object creation like 'new OpenHftHashing(hashFunction)'
58+
private CommandLongHashing longHashing = null;
7559

7660
private ClientSideCacheable cacheable = DefaultClientSideCacheable.INSTANCE;
7761

@@ -87,8 +71,8 @@ public Builder ttl(int seconds) {
8771
return this;
8872
}
8973

90-
public Builder hashFunction(LongHashFunction function) {
91-
this.hashFunction = function;
74+
public Builder hashing(CommandLongHashing hashing) {
75+
this.longHashing = hashing;
9276
return this;
9377
}
9478

@@ -104,7 +88,9 @@ public CaffeineCSC build() {
10488

10589
cb.expireAfterWrite(expireTime, expireTimeUnit);
10690

107-
return new CaffeineCSC(cb.build(), hashFunction, cacheable);
91+
return longHashing != null
92+
? new CaffeineCSC(cb.build(), longHashing, cacheable)
93+
: new CaffeineCSC(cb.build(), cacheable);
10894
}
10995
}
11096
}

Diff for: src/main/java/redis/clients/jedis/csc/ClientSideCache.java

+9-8
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,29 @@
88
import java.util.concurrent.ConcurrentHashMap;
99
import java.util.function.Function;
1010
import redis.clients.jedis.CommandObject;
11+
import redis.clients.jedis.csc.hash.CommandLongHashing;
1112
import redis.clients.jedis.util.SafeEncoder;
1213

1314
/**
1415
* The class to manage the client-side caching. User can provide any of implementation of this class to the client
15-
* object; e.g. {@link redis.clients.jedis.csc.util.CaffeineCSC CaffeineCSC} or
16-
* {@link redis.clients.jedis.csc.util.GuavaCSC GuavaCSC} or a custom implementation of their own.
16+
* object; e.g. {@link redis.clients.jedis.csc.CaffeineCSC CaffeineCSC} or
17+
* {@link redis.clients.jedis.csc.GuavaCSC GuavaCSC} or a custom implementation of their own.
1718
*/
1819
public abstract class ClientSideCache {
1920

2021
protected static final int DEFAULT_MAXIMUM_SIZE = 10_000;
2122
protected static final int DEFAULT_EXPIRE_SECONDS = 100;
2223

2324
private final Map<ByteBuffer, Set<Long>> keyToCommandHashes = new ConcurrentHashMap<>();
25+
private final CommandLongHashing commandHashing;
2426
private final ClientSideCacheable cacheable;
2527

26-
protected ClientSideCache() {
27-
this.cacheable = DefaultClientSideCacheable.INSTANCE;
28+
protected ClientSideCache(CommandLongHashing commandHashing) {
29+
this(commandHashing, DefaultClientSideCacheable.INSTANCE);
2830
}
2931

30-
protected ClientSideCache(ClientSideCacheable cacheable) {
32+
protected ClientSideCache(CommandLongHashing commandHashing, ClientSideCacheable cacheable) {
33+
this.commandHashing = commandHashing;
3134
this.cacheable = cacheable;
3235
}
3336

@@ -39,8 +42,6 @@ protected ClientSideCache(ClientSideCacheable cacheable) {
3942

4043
protected abstract Object getValue(long hash);
4144

42-
protected abstract long getHash(CommandObject command);
43-
4445
public final void clear() {
4546
invalidateAllKeysAndCommandHashes();
4647
}
@@ -79,7 +80,7 @@ public final <T> T get(Function<CommandObject<T>, T> loader, CommandObject<T> co
7980
return loader.apply(command);
8081
}
8182

82-
final long hash = getHash(command);
83+
final long hash = commandHashing.hash(command);
8384

8485
T value = (T) getValue(hash);
8586
if (value != null) {

Diff for: src/main/java/redis/clients/jedis/csc/util/GuavaCSC.java renamed to src/main/java/redis/clients/jedis/csc/GuavaCSC.java

+25-26
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,37 @@
1-
package redis.clients.jedis.csc.util;
1+
package redis.clients.jedis.csc;
22

33
import com.google.common.cache.Cache;
44
import com.google.common.cache.CacheBuilder;
55
import com.google.common.hash.HashFunction;
6-
import com.google.common.hash.Hasher;
76
import java.util.concurrent.TimeUnit;
87

9-
import redis.clients.jedis.CommandObject;
10-
import redis.clients.jedis.csc.ClientSideCache;
11-
import redis.clients.jedis.csc.ClientSideCacheable;
12-
import redis.clients.jedis.csc.DefaultClientSideCacheable;
8+
import redis.clients.jedis.csc.hash.CommandLongHashing;
9+
import redis.clients.jedis.csc.hash.GuavaHashing;
1310

1411
public class GuavaCSC extends ClientSideCache {
1512

16-
private static final HashFunction DEFAULT_HASH_FUNCTION = com.google.common.hash.Hashing.fingerprint2011();
17-
1813
private final Cache<Long, Object> cache;
19-
private final HashFunction hashFunction;
2014

2115
public GuavaCSC(Cache<Long, Object> guavaCache) {
22-
this(guavaCache, DEFAULT_HASH_FUNCTION);
16+
this(guavaCache, GuavaHashing.DEFAULT_HASH_FUNCTION);
2317
}
2418

2519
public GuavaCSC(Cache<Long, Object> guavaCache, HashFunction hashFunction) {
26-
super();
20+
this(guavaCache, new GuavaHashing(hashFunction));
21+
}
22+
23+
public GuavaCSC(Cache<Long, Object> guavaCache, CommandLongHashing hashing) {
24+
super(hashing);
2725
this.cache = guavaCache;
28-
this.hashFunction = hashFunction;
2926
}
3027

3128
public GuavaCSC(Cache<Long, Object> guavaCache, ClientSideCacheable cacheable) {
32-
this(guavaCache, DEFAULT_HASH_FUNCTION, cacheable);
29+
this(guavaCache, new GuavaHashing(GuavaHashing.DEFAULT_HASH_FUNCTION), cacheable);
3330
}
3431

35-
public GuavaCSC(Cache<Long, Object> cache, HashFunction function, ClientSideCacheable cacheable) {
36-
super(cacheable);
32+
public GuavaCSC(Cache<Long, Object> cache, CommandLongHashing hashing, ClientSideCacheable cacheable) {
33+
super(hashing, cacheable);
3734
this.cache = cache;
38-
this.hashFunction = function;
3935
}
4036

4137
@Override
@@ -58,14 +54,6 @@ protected Object getValue(long hash) {
5854
return cache.getIfPresent(hash);
5955
}
6056

61-
@Override
62-
protected final long getHash(CommandObject command) {
63-
Hasher hasher = hashFunction.newHasher();
64-
command.getArguments().forEach(raw -> hasher.putBytes(raw.getRaw()));
65-
hasher.putInt(command.getBuilder().hashCode());
66-
return hasher.hash().asLong();
67-
}
68-
6957
public static Builder builder() {
7058
return new Builder();
7159
}
@@ -76,7 +64,9 @@ public static class Builder {
7664
private long expireTime = DEFAULT_EXPIRE_SECONDS;
7765
private final TimeUnit expireTimeUnit = TimeUnit.SECONDS;
7866

79-
private HashFunction hashFunction = DEFAULT_HASH_FUNCTION;
67+
// not using a default value to avoid an object creation like 'new GuavaHashing(hashFunction)'
68+
private HashFunction hashFunction = null;
69+
private CommandLongHashing longHashing = null;
8070

8171
private ClientSideCacheable cacheable = DefaultClientSideCacheable.INSTANCE;
8272

@@ -94,6 +84,13 @@ public Builder ttl(int seconds) {
9484

9585
public Builder hashFunction(HashFunction function) {
9686
this.hashFunction = function;
87+
this.longHashing = null;
88+
return this;
89+
}
90+
91+
public Builder hashing(CommandLongHashing hashing) {
92+
this.longHashing = hashing;
93+
this.hashFunction = null;
9794
return this;
9895
}
9996

@@ -109,7 +106,9 @@ public GuavaCSC build() {
109106

110107
cb.expireAfterWrite(expireTime, expireTimeUnit);
111108

112-
return new GuavaCSC(cb.build(), hashFunction, cacheable);
109+
return longHashing != null ? new GuavaCSC(cb.build(), longHashing, cacheable)
110+
: hashFunction != null ? new GuavaCSC(cb.build(), new GuavaHashing(hashFunction), cacheable)
111+
: new GuavaCSC(cb.build(), cacheable);
113112
}
114113
}
115114
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package redis.clients.jedis.csc.hash;
2+
3+
import redis.clients.jedis.Builder;
4+
import redis.clients.jedis.CommandObject;
5+
import redis.clients.jedis.args.Rawable;
6+
7+
public abstract class AbstractCommandHashing implements CommandLongHashing {
8+
9+
@Override
10+
public final long hash(CommandObject command) {
11+
long[] nums = new long[command.getArguments().size() + 1];
12+
int idx = 0;
13+
for (Rawable raw : command.getArguments()) {
14+
nums[idx++] = hashRawable(raw);
15+
}
16+
nums[idx] = hashBuilder(command.getBuilder());
17+
return hashLongs(nums);
18+
}
19+
20+
protected abstract long hashLongs(long[] longs);
21+
22+
protected abstract long hashRawable(Rawable raw);
23+
24+
protected long hashBuilder(Builder builder) {
25+
return builder.hashCode();
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package redis.clients.jedis.csc.hash;
2+
3+
import redis.clients.jedis.CommandObject;
4+
5+
/**
6+
* The interface for hashing a command object for client-side caching.
7+
*/
8+
public interface CommandLongHashing {
9+
10+
/**
11+
* Produce a 64-bit signed hash from a command object.
12+
* @param command the command object
13+
* @return 64-bit signed hash
14+
*/
15+
long hash(CommandObject command);
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package redis.clients.jedis.csc.hash;
2+
3+
import com.google.common.hash.HashFunction;
4+
import com.google.common.hash.Hasher;
5+
import redis.clients.jedis.CommandObject;
6+
7+
public class GuavaHashing implements CommandLongHashing {
8+
9+
public static final HashFunction DEFAULT_HASH_FUNCTION = com.google.common.hash.Hashing.fingerprint2011();
10+
11+
private final HashFunction function;
12+
13+
public GuavaHashing(HashFunction function) {
14+
this.function = function;
15+
}
16+
17+
@Override
18+
public long hash(CommandObject command) {
19+
Hasher hasher = function.newHasher();
20+
command.getArguments().forEach(raw -> hasher.putBytes(raw.getRaw()));
21+
hasher.putInt(command.getBuilder().hashCode());
22+
return hasher.hash().asLong();
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package redis.clients.jedis.csc.hash;
2+
3+
import net.openhft.hashing.LongHashFunction;
4+
5+
public class OpenHftHashing extends PrimitiveArrayHashing implements CommandLongHashing {
6+
7+
public static final LongHashFunction DEFAULT_HASH_FUNCTION = LongHashFunction.xx3();
8+
9+
private final LongHashFunction function;
10+
11+
public OpenHftHashing(LongHashFunction function) {
12+
this.function = function;
13+
}
14+
15+
@Override
16+
protected long hashLongs(long[] longs) {
17+
return function.hashLongs(longs);
18+
}
19+
20+
@Override
21+
protected long hashBytes(byte[] bytes) {
22+
return function.hashBytes(bytes);
23+
}
24+
25+
@Override
26+
protected long hashInt(int hashCode) {
27+
return function.hashInt(hashCode);
28+
}
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package redis.clients.jedis.csc.hash;
2+
3+
import redis.clients.jedis.Builder;
4+
import redis.clients.jedis.args.Rawable;
5+
6+
public abstract class PrimitiveArrayHashing extends AbstractCommandHashing {
7+
8+
@Override
9+
protected final long hashRawable(Rawable raw) {
10+
return hashBytes(raw.getRaw());
11+
}
12+
13+
@Override
14+
protected final long hashBuilder(Builder builder) {
15+
return hashInt(builder.hashCode());
16+
}
17+
18+
protected abstract long hashBytes(byte[] bytes);
19+
20+
protected long hashInt(int hashCode) {
21+
return hashCode;
22+
}
23+
}

Diff for: src/main/java/redis/clients/jedis/util/JedisURIHelper.java

+2-4
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
package redis.clients.jedis.util;
22

33
import java.net.URI;
4-
54
import redis.clients.jedis.HostAndPort;
65
import redis.clients.jedis.Protocol;
76
import redis.clients.jedis.RedisProtocol;
8-
7+
import redis.clients.jedis.csc.CaffeineCSC;
98
import redis.clients.jedis.csc.ClientSideCache;
10-
import redis.clients.jedis.csc.util.GuavaCSC;
11-
import redis.clients.jedis.csc.util.CaffeineCSC;
9+
import redis.clients.jedis.csc.GuavaCSC;
1210

1311
public final class JedisURIHelper {
1412

0 commit comments

Comments
 (0)