Skip to content

Commit

Permalink
Introduce interface(s) for hashing CommandObject (#3743)
Browse files Browse the repository at this point in the history
  • Loading branch information
sazzad16 authored Mar 6, 2024
1 parent 333dcd7 commit e66f498
Show file tree
Hide file tree
Showing 11 changed files with 194 additions and 85 deletions.
Original file line number Diff line number Diff line change
@@ -1,33 +1,27 @@
package redis.clients.jedis.csc.util;
package redis.clients.jedis.csc;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.concurrent.TimeUnit;
import net.openhft.hashing.LongHashFunction;

import redis.clients.jedis.CommandObject;
import redis.clients.jedis.args.Rawable;
import redis.clients.jedis.csc.ClientSideCache;
import redis.clients.jedis.csc.ClientSideCacheable;
import redis.clients.jedis.csc.DefaultClientSideCacheable;
import redis.clients.jedis.csc.hash.CommandLongHashing;
import redis.clients.jedis.csc.hash.OpenHftHashing;

public class CaffeineCSC extends ClientSideCache {

private static final LongHashFunction DEFAULT_HASH_FUNCTION = LongHashFunction.xx3();

private final Cache<Long, Object> cache;
private final LongHashFunction hashFunction;

public CaffeineCSC(Cache<Long, Object> caffeineCache, LongHashFunction hashFunction) {
super();
this.cache = caffeineCache;
this.hashFunction = hashFunction;
public CaffeineCSC(Cache<Long, Object> caffeineCache) {
this(caffeineCache, new OpenHftHashing(OpenHftHashing.DEFAULT_HASH_FUNCTION), DefaultClientSideCacheable.INSTANCE);
}

public CaffeineCSC(Cache<Long, Object> caffeineCache, ClientSideCacheable cacheable) {
this(caffeineCache, new OpenHftHashing(OpenHftHashing.DEFAULT_HASH_FUNCTION), cacheable);
}

public CaffeineCSC(Cache<Long, Object> caffeineCache, LongHashFunction function, ClientSideCacheable cacheable) {
super(cacheable);
public CaffeineCSC(Cache<Long, Object> caffeineCache, CommandLongHashing hashing, ClientSideCacheable cacheable) {
super(hashing, cacheable);
this.cache = caffeineCache;
this.hashFunction = function;
}

@Override
Expand All @@ -50,17 +44,6 @@ protected Object getValue(long hash) {
return cache.getIfPresent(hash);
}

@Override
protected final long getHash(CommandObject command) {
long[] nums = new long[command.getArguments().size() + 1];
int idx = 0;
for (Rawable raw : command.getArguments()) {
nums[idx++] = hashFunction.hashBytes(raw.getRaw());
}
nums[idx] = hashFunction.hashInt(command.getBuilder().hashCode());
return hashFunction.hashLongs(nums);
}

public static Builder builder() {
return new Builder();
}
Expand All @@ -71,7 +54,8 @@ public static class Builder {
private long expireTime = DEFAULT_EXPIRE_SECONDS;
private final TimeUnit expireTimeUnit = TimeUnit.SECONDS;

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

private ClientSideCacheable cacheable = DefaultClientSideCacheable.INSTANCE;

Expand All @@ -87,8 +71,8 @@ public Builder ttl(int seconds) {
return this;
}

public Builder hashFunction(LongHashFunction function) {
this.hashFunction = function;
public Builder hashing(CommandLongHashing hashing) {
this.longHashing = hashing;
return this;
}

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

cb.expireAfterWrite(expireTime, expireTimeUnit);

return new CaffeineCSC(cb.build(), hashFunction, cacheable);
return longHashing != null
? new CaffeineCSC(cb.build(), longHashing, cacheable)
: new CaffeineCSC(cb.build(), cacheable);
}
}
}
17 changes: 9 additions & 8 deletions src/main/java/redis/clients/jedis/csc/ClientSideCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,29 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import redis.clients.jedis.CommandObject;
import redis.clients.jedis.csc.hash.CommandLongHashing;
import redis.clients.jedis.util.SafeEncoder;

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

protected static final int DEFAULT_MAXIMUM_SIZE = 10_000;
protected static final int DEFAULT_EXPIRE_SECONDS = 100;

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

protected ClientSideCache() {
this.cacheable = DefaultClientSideCacheable.INSTANCE;
protected ClientSideCache(CommandLongHashing commandHashing) {
this(commandHashing, DefaultClientSideCacheable.INSTANCE);
}

protected ClientSideCache(ClientSideCacheable cacheable) {
protected ClientSideCache(CommandLongHashing commandHashing, ClientSideCacheable cacheable) {
this.commandHashing = commandHashing;
this.cacheable = cacheable;
}

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

protected abstract Object getValue(long hash);

protected abstract long getHash(CommandObject command);

public final void clear() {
invalidateAllKeysAndCommandHashes();
}
Expand Down Expand Up @@ -79,7 +80,7 @@ public final <T> T get(Function<CommandObject<T>, T> loader, CommandObject<T> co
return loader.apply(command);
}

final long hash = getHash(command);
final long hash = commandHashing.hash(command);

T value = (T) getValue(hash);
if (value != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,41 +1,37 @@
package redis.clients.jedis.csc.util;
package redis.clients.jedis.csc;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hasher;
import java.util.concurrent.TimeUnit;

import redis.clients.jedis.CommandObject;
import redis.clients.jedis.csc.ClientSideCache;
import redis.clients.jedis.csc.ClientSideCacheable;
import redis.clients.jedis.csc.DefaultClientSideCacheable;
import redis.clients.jedis.csc.hash.CommandLongHashing;
import redis.clients.jedis.csc.hash.GuavaHashing;

public class GuavaCSC extends ClientSideCache {

private static final HashFunction DEFAULT_HASH_FUNCTION = com.google.common.hash.Hashing.fingerprint2011();

private final Cache<Long, Object> cache;
private final HashFunction hashFunction;

public GuavaCSC(Cache<Long, Object> guavaCache) {
this(guavaCache, DEFAULT_HASH_FUNCTION);
this(guavaCache, GuavaHashing.DEFAULT_HASH_FUNCTION);
}

public GuavaCSC(Cache<Long, Object> guavaCache, HashFunction hashFunction) {
super();
this(guavaCache, new GuavaHashing(hashFunction));
}

public GuavaCSC(Cache<Long, Object> guavaCache, CommandLongHashing hashing) {
super(hashing);
this.cache = guavaCache;
this.hashFunction = hashFunction;
}

public GuavaCSC(Cache<Long, Object> guavaCache, ClientSideCacheable cacheable) {
this(guavaCache, DEFAULT_HASH_FUNCTION, cacheable);
this(guavaCache, new GuavaHashing(GuavaHashing.DEFAULT_HASH_FUNCTION), cacheable);
}

public GuavaCSC(Cache<Long, Object> cache, HashFunction function, ClientSideCacheable cacheable) {
super(cacheable);
public GuavaCSC(Cache<Long, Object> cache, CommandLongHashing hashing, ClientSideCacheable cacheable) {
super(hashing, cacheable);
this.cache = cache;
this.hashFunction = function;
}

@Override
Expand All @@ -58,14 +54,6 @@ protected Object getValue(long hash) {
return cache.getIfPresent(hash);
}

@Override
protected final long getHash(CommandObject command) {
Hasher hasher = hashFunction.newHasher();
command.getArguments().forEach(raw -> hasher.putBytes(raw.getRaw()));
hasher.putInt(command.getBuilder().hashCode());
return hasher.hash().asLong();
}

public static Builder builder() {
return new Builder();
}
Expand All @@ -76,7 +64,9 @@ public static class Builder {
private long expireTime = DEFAULT_EXPIRE_SECONDS;
private final TimeUnit expireTimeUnit = TimeUnit.SECONDS;

private HashFunction hashFunction = DEFAULT_HASH_FUNCTION;
// not using a default value to avoid an object creation like 'new GuavaHashing(hashFunction)'
private HashFunction hashFunction = null;
private CommandLongHashing longHashing = null;

private ClientSideCacheable cacheable = DefaultClientSideCacheable.INSTANCE;

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

public Builder hashFunction(HashFunction function) {
this.hashFunction = function;
this.longHashing = null;
return this;
}

public Builder hashing(CommandLongHashing hashing) {
this.longHashing = hashing;
this.hashFunction = null;
return this;
}

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

cb.expireAfterWrite(expireTime, expireTimeUnit);

return new GuavaCSC(cb.build(), hashFunction, cacheable);
return longHashing != null ? new GuavaCSC(cb.build(), longHashing, cacheable)
: hashFunction != null ? new GuavaCSC(cb.build(), new GuavaHashing(hashFunction), cacheable)
: new GuavaCSC(cb.build(), cacheable);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package redis.clients.jedis.csc.hash;

import redis.clients.jedis.Builder;
import redis.clients.jedis.CommandObject;
import redis.clients.jedis.args.Rawable;

public abstract class AbstractCommandHashing implements CommandLongHashing {

@Override
public final long hash(CommandObject command) {
long[] nums = new long[command.getArguments().size() + 1];
int idx = 0;
for (Rawable raw : command.getArguments()) {
nums[idx++] = hashRawable(raw);
}
nums[idx] = hashBuilder(command.getBuilder());
return hashLongs(nums);
}

protected abstract long hashLongs(long[] longs);

protected abstract long hashRawable(Rawable raw);

protected long hashBuilder(Builder builder) {
return builder.hashCode();
}
}
16 changes: 16 additions & 0 deletions src/main/java/redis/clients/jedis/csc/hash/CommandLongHashing.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package redis.clients.jedis.csc.hash;

import redis.clients.jedis.CommandObject;

/**
* The interface for hashing a command object for client-side caching.
*/
public interface CommandLongHashing {

/**
* Produce a 64-bit signed hash from a command object.
* @param command the command object
* @return 64-bit signed hash
*/
long hash(CommandObject command);
}
24 changes: 24 additions & 0 deletions src/main/java/redis/clients/jedis/csc/hash/GuavaHashing.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package redis.clients.jedis.csc.hash;

import com.google.common.hash.HashFunction;
import com.google.common.hash.Hasher;
import redis.clients.jedis.CommandObject;

public class GuavaHashing implements CommandLongHashing {

public static final HashFunction DEFAULT_HASH_FUNCTION = com.google.common.hash.Hashing.fingerprint2011();

private final HashFunction function;

public GuavaHashing(HashFunction function) {
this.function = function;
}

@Override
public long hash(CommandObject command) {
Hasher hasher = function.newHasher();
command.getArguments().forEach(raw -> hasher.putBytes(raw.getRaw()));
hasher.putInt(command.getBuilder().hashCode());
return hasher.hash().asLong();
}
}
29 changes: 29 additions & 0 deletions src/main/java/redis/clients/jedis/csc/hash/OpenHftHashing.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package redis.clients.jedis.csc.hash;

import net.openhft.hashing.LongHashFunction;

public class OpenHftHashing extends PrimitiveArrayHashing implements CommandLongHashing {

public static final LongHashFunction DEFAULT_HASH_FUNCTION = LongHashFunction.xx3();

private final LongHashFunction function;

public OpenHftHashing(LongHashFunction function) {
this.function = function;
}

@Override
protected long hashLongs(long[] longs) {
return function.hashLongs(longs);
}

@Override
protected long hashBytes(byte[] bytes) {
return function.hashBytes(bytes);
}

@Override
protected long hashInt(int hashCode) {
return function.hashInt(hashCode);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package redis.clients.jedis.csc.hash;

import redis.clients.jedis.Builder;
import redis.clients.jedis.args.Rawable;

public abstract class PrimitiveArrayHashing extends AbstractCommandHashing {

@Override
protected final long hashRawable(Rawable raw) {
return hashBytes(raw.getRaw());
}

@Override
protected final long hashBuilder(Builder builder) {
return hashInt(builder.hashCode());
}

protected abstract long hashBytes(byte[] bytes);

protected long hashInt(int hashCode) {
return hashCode;
}
}
6 changes: 2 additions & 4 deletions src/main/java/redis/clients/jedis/util/JedisURIHelper.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
package redis.clients.jedis.util;

import java.net.URI;

import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.Protocol;
import redis.clients.jedis.RedisProtocol;

import redis.clients.jedis.csc.CaffeineCSC;
import redis.clients.jedis.csc.ClientSideCache;
import redis.clients.jedis.csc.util.GuavaCSC;
import redis.clients.jedis.csc.util.CaffeineCSC;
import redis.clients.jedis.csc.GuavaCSC;

public final class JedisURIHelper {

Expand Down
Loading

0 comments on commit e66f498

Please sign in to comment.