Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce interface(s) for hashing CommandObject #3743

Merged
merged 10 commits into from
Mar 6, 2024
Merged
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);
sazzad16 marked this conversation as resolved.
Show resolved Hide resolved
}
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
Loading