From e66f498e04da8a0f817dbf467c73c61dbb32f89b Mon Sep 17 00:00:00 2001 From: M Sazzadul Hoque <7600764+sazzad16@users.noreply.github.com> Date: Wed, 6 Mar 2024 15:51:33 +0600 Subject: [PATCH] Introduce interface(s) for hashing CommandObject (#3743) --- .../jedis/csc/{util => }/CaffeineCSC.java | 50 +++++++----------- .../clients/jedis/csc/ClientSideCache.java | 17 ++++--- .../jedis/csc/{util => }/GuavaCSC.java | 51 +++++++++---------- .../csc/hash/AbstractCommandHashing.java | 27 ++++++++++ .../jedis/csc/hash/CommandLongHashing.java | 16 ++++++ .../clients/jedis/csc/hash/GuavaHashing.java | 24 +++++++++ .../jedis/csc/hash/OpenHftHashing.java | 29 +++++++++++ .../jedis/csc/hash/PrimitiveArrayHashing.java | 23 +++++++++ .../clients/jedis/util/JedisURIHelper.java | 6 +-- .../jedis/csc/ClientSideCacheLibsTest.java | 10 ++-- .../java/redis/clients/jedis/csc/MapCSC.java | 26 ++++++---- 11 files changed, 194 insertions(+), 85 deletions(-) rename src/main/java/redis/clients/jedis/csc/{util => }/CaffeineCSC.java (52%) rename src/main/java/redis/clients/jedis/csc/{util => }/GuavaCSC.java (62%) create mode 100644 src/main/java/redis/clients/jedis/csc/hash/AbstractCommandHashing.java create mode 100644 src/main/java/redis/clients/jedis/csc/hash/CommandLongHashing.java create mode 100644 src/main/java/redis/clients/jedis/csc/hash/GuavaHashing.java create mode 100644 src/main/java/redis/clients/jedis/csc/hash/OpenHftHashing.java create mode 100644 src/main/java/redis/clients/jedis/csc/hash/PrimitiveArrayHashing.java diff --git a/src/main/java/redis/clients/jedis/csc/util/CaffeineCSC.java b/src/main/java/redis/clients/jedis/csc/CaffeineCSC.java similarity index 52% rename from src/main/java/redis/clients/jedis/csc/util/CaffeineCSC.java rename to src/main/java/redis/clients/jedis/csc/CaffeineCSC.java index 4362169550..82f4f0f9a8 100644 --- a/src/main/java/redis/clients/jedis/csc/util/CaffeineCSC.java +++ b/src/main/java/redis/clients/jedis/csc/CaffeineCSC.java @@ -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 cache; - private final LongHashFunction hashFunction; - public CaffeineCSC(Cache caffeineCache, LongHashFunction hashFunction) { - super(); - this.cache = caffeineCache; - this.hashFunction = hashFunction; + public CaffeineCSC(Cache caffeineCache) { + this(caffeineCache, new OpenHftHashing(OpenHftHashing.DEFAULT_HASH_FUNCTION), DefaultClientSideCacheable.INSTANCE); + } + + public CaffeineCSC(Cache caffeineCache, ClientSideCacheable cacheable) { + this(caffeineCache, new OpenHftHashing(OpenHftHashing.DEFAULT_HASH_FUNCTION), cacheable); } - public CaffeineCSC(Cache caffeineCache, LongHashFunction function, ClientSideCacheable cacheable) { - super(cacheable); + public CaffeineCSC(Cache caffeineCache, CommandLongHashing hashing, ClientSideCacheable cacheable) { + super(hashing, cacheable); this.cache = caffeineCache; - this.hashFunction = function; } @Override @@ -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(); } @@ -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; @@ -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; } @@ -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); } } } diff --git a/src/main/java/redis/clients/jedis/csc/ClientSideCache.java b/src/main/java/redis/clients/jedis/csc/ClientSideCache.java index 22509e4e2c..ef6b833a60 100644 --- a/src/main/java/redis/clients/jedis/csc/ClientSideCache.java +++ b/src/main/java/redis/clients/jedis/csc/ClientSideCache.java @@ -8,12 +8,13 @@ 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 { @@ -21,13 +22,15 @@ public abstract class ClientSideCache { protected static final int DEFAULT_EXPIRE_SECONDS = 100; private final Map> 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; } @@ -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(); } @@ -79,7 +80,7 @@ public final T get(Function, T> loader, CommandObject co return loader.apply(command); } - final long hash = getHash(command); + final long hash = commandHashing.hash(command); T value = (T) getValue(hash); if (value != null) { diff --git a/src/main/java/redis/clients/jedis/csc/util/GuavaCSC.java b/src/main/java/redis/clients/jedis/csc/GuavaCSC.java similarity index 62% rename from src/main/java/redis/clients/jedis/csc/util/GuavaCSC.java rename to src/main/java/redis/clients/jedis/csc/GuavaCSC.java index e1c9e4f434..c5c173f5d8 100644 --- a/src/main/java/redis/clients/jedis/csc/util/GuavaCSC.java +++ b/src/main/java/redis/clients/jedis/csc/GuavaCSC.java @@ -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 cache; - private final HashFunction hashFunction; public GuavaCSC(Cache guavaCache) { - this(guavaCache, DEFAULT_HASH_FUNCTION); + this(guavaCache, GuavaHashing.DEFAULT_HASH_FUNCTION); } public GuavaCSC(Cache guavaCache, HashFunction hashFunction) { - super(); + this(guavaCache, new GuavaHashing(hashFunction)); + } + + public GuavaCSC(Cache guavaCache, CommandLongHashing hashing) { + super(hashing); this.cache = guavaCache; - this.hashFunction = hashFunction; } public GuavaCSC(Cache guavaCache, ClientSideCacheable cacheable) { - this(guavaCache, DEFAULT_HASH_FUNCTION, cacheable); + this(guavaCache, new GuavaHashing(GuavaHashing.DEFAULT_HASH_FUNCTION), cacheable); } - public GuavaCSC(Cache cache, HashFunction function, ClientSideCacheable cacheable) { - super(cacheable); + public GuavaCSC(Cache cache, CommandLongHashing hashing, ClientSideCacheable cacheable) { + super(hashing, cacheable); this.cache = cache; - this.hashFunction = function; } @Override @@ -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(); } @@ -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; @@ -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; } @@ -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); } } } diff --git a/src/main/java/redis/clients/jedis/csc/hash/AbstractCommandHashing.java b/src/main/java/redis/clients/jedis/csc/hash/AbstractCommandHashing.java new file mode 100644 index 0000000000..561217e299 --- /dev/null +++ b/src/main/java/redis/clients/jedis/csc/hash/AbstractCommandHashing.java @@ -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(); + } +} diff --git a/src/main/java/redis/clients/jedis/csc/hash/CommandLongHashing.java b/src/main/java/redis/clients/jedis/csc/hash/CommandLongHashing.java new file mode 100644 index 0000000000..6632f46e72 --- /dev/null +++ b/src/main/java/redis/clients/jedis/csc/hash/CommandLongHashing.java @@ -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); +} diff --git a/src/main/java/redis/clients/jedis/csc/hash/GuavaHashing.java b/src/main/java/redis/clients/jedis/csc/hash/GuavaHashing.java new file mode 100644 index 0000000000..2b9ad6ff5a --- /dev/null +++ b/src/main/java/redis/clients/jedis/csc/hash/GuavaHashing.java @@ -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(); + } +} diff --git a/src/main/java/redis/clients/jedis/csc/hash/OpenHftHashing.java b/src/main/java/redis/clients/jedis/csc/hash/OpenHftHashing.java new file mode 100644 index 0000000000..b112980fd4 --- /dev/null +++ b/src/main/java/redis/clients/jedis/csc/hash/OpenHftHashing.java @@ -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); + } +} diff --git a/src/main/java/redis/clients/jedis/csc/hash/PrimitiveArrayHashing.java b/src/main/java/redis/clients/jedis/csc/hash/PrimitiveArrayHashing.java new file mode 100644 index 0000000000..385f97fdd2 --- /dev/null +++ b/src/main/java/redis/clients/jedis/csc/hash/PrimitiveArrayHashing.java @@ -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; + } +} diff --git a/src/main/java/redis/clients/jedis/util/JedisURIHelper.java b/src/main/java/redis/clients/jedis/util/JedisURIHelper.java index e8e62ae9b5..6abedd05c4 100644 --- a/src/main/java/redis/clients/jedis/util/JedisURIHelper.java +++ b/src/main/java/redis/clients/jedis/util/JedisURIHelper.java @@ -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 { diff --git a/src/test/java/redis/clients/jedis/csc/ClientSideCacheLibsTest.java b/src/test/java/redis/clients/jedis/csc/ClientSideCacheLibsTest.java index 31589458f8..b49d524f83 100644 --- a/src/test/java/redis/clients/jedis/csc/ClientSideCacheLibsTest.java +++ b/src/test/java/redis/clients/jedis/csc/ClientSideCacheLibsTest.java @@ -23,8 +23,7 @@ import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisClientConfig; import redis.clients.jedis.JedisPooled; -import redis.clients.jedis.csc.util.CaffeineCSC; -import redis.clients.jedis.csc.util.GuavaCSC; +import redis.clients.jedis.csc.hash.OpenHftHashing; public class ClientSideCacheLibsTest { @@ -93,7 +92,8 @@ public void guavaMore() { @Test public void caffeineSimple() { - CaffeineCSC caffeine = CaffeineCSC.builder().maximumSize(10).ttl(10).hashFunction(LongHashFunction.xx()).build(); + CaffeineCSC caffeine = CaffeineCSC.builder().maximumSize(10).ttl(10) + .hashing(new OpenHftHashing(LongHashFunction.xx())).build(); try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), caffeine)) { control.set("foo", "bar"); assertEquals("bar", jedis.get("foo")); @@ -107,8 +107,8 @@ public void caffeineMore() { com.github.benmanes.caffeine.cache.Cache caffeine = Caffeine.newBuilder().recordStats().build(); - try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), - new CaffeineCSC(caffeine, LongHashFunction.city_1_1()), singleConnectionPoolConfig.get())) { + try (JedisPooled jedis = new JedisPooled(hnp, clientConfig.get(), new CaffeineCSC(caffeine), + singleConnectionPoolConfig.get())) { control.set("foo", "bar"); assertEquals(0, caffeine.estimatedSize()); assertEquals("bar", jedis.get("foo")); diff --git a/src/test/java/redis/clients/jedis/csc/MapCSC.java b/src/test/java/redis/clients/jedis/csc/MapCSC.java index ee08efb588..fcb6d232a8 100644 --- a/src/test/java/redis/clients/jedis/csc/MapCSC.java +++ b/src/test/java/redis/clients/jedis/csc/MapCSC.java @@ -6,9 +6,23 @@ import redis.clients.jedis.CommandObject; import redis.clients.jedis.args.Rawable; +import redis.clients.jedis.csc.hash.PrimitiveArrayHashing; public class MapCSC extends ClientSideCache { + private static final PrimitiveArrayHashing HASHING = new PrimitiveArrayHashing() { + + @Override + protected long hashLongs(long[] longs) { + return Arrays.hashCode(longs); + } + + @Override + protected long hashBytes(byte[] bytes) { + return Arrays.hashCode(bytes); + } + }; + private final Map cache; public MapCSC() { @@ -16,11 +30,12 @@ public MapCSC() { } public MapCSC(Map map) { + super(HASHING); this.cache = map; } public MapCSC(Map cache, ClientSideCacheable cacheable) { - super(cacheable); + super(HASHING, cacheable); this.cache = cache; } @@ -43,13 +58,4 @@ protected void putValue(long hash, Object value) { protected Object getValue(long hash) { return cache.get(hash); } - - @Override - protected final long getHash(CommandObject command) { - long result = 1; - for (Rawable raw : command.getArguments()) { - result = 31 * result + Arrays.hashCode(raw.getRaw()); - } - return 31 * result + command.getBuilder().hashCode(); - } }