From 9e1b60f86fa72f44a49da2739813bfd2d584d314 Mon Sep 17 00:00:00 2001 From: seakider Date: Wed, 12 Feb 2025 00:08:12 +0800 Subject: [PATCH] Feature - Support for OBJECT command Signed-off-by: seakider --- .../java/org/redisson/RedissonObject.java | 31 +++++ .../main/java/org/redisson/api/RObject.java | 20 +++ .../java/org/redisson/api/RObjectAsync.java | 21 ++++ .../org/redisson/api/RObjectReactive.java | 21 ++++ .../main/java/org/redisson/api/RObjectRx.java | 21 ++++ .../org/redisson/api/RedisObjectEncoding.java | 119 ++++++++++++++++++ .../client/protocol/RedisCommands.java | 10 ++ .../java/org/redisson/RedisDockerTest.java | 2 + .../java/org/redisson/RedissonBucketTest.java | 59 ++++++++- 9 files changed, 300 insertions(+), 4 deletions(-) create mode 100644 redisson/src/main/java/org/redisson/api/RedisObjectEncoding.java diff --git a/redisson/src/main/java/org/redisson/RedissonObject.java b/redisson/src/main/java/org/redisson/RedissonObject.java index dc2fe2433..7415b8434 100644 --- a/redisson/src/main/java/org/redisson/RedissonObject.java +++ b/redisson/src/main/java/org/redisson/RedissonObject.java @@ -27,6 +27,7 @@ import org.redisson.client.protocol.RedisCommands; import org.redisson.client.protocol.pubsub.PubSubType; import org.redisson.command.BatchService; import org.redisson.command.CommandAsyncExecutor; +import org.redisson.api.RedisObjectEncoding; import org.redisson.connection.ServiceManager; import org.redisson.misc.CompletableFutureWrapper; import org.redisson.misc.Hash; @@ -606,11 +607,41 @@ public abstract class RedissonObject implements RObject { return get(getIdleTimeAsync()); } + @Override + public int getReferenceCount() { + return get(getReferenceCountAsync()); + } + + @Override + public int getAccessFrequency() { + return get(getAccessFrequencyAsync()); + } + + @Override + public RedisObjectEncoding getInternalEncoding() { + return get(getInternalEncodingAsync()); + } + @Override public RFuture getIdleTimeAsync() { return commandExecutor.writeAsync(getRawName(), StringCodec.INSTANCE, RedisCommands.OBJECT_IDLETIME, getRawName()); } + @Override + public RFuture getReferenceCountAsync() { + return commandExecutor.readAsync(getRawName(), StringCodec.INSTANCE, RedisCommands.OBJECT_REFCOUNT, getRawName()); + } + + @Override + public RFuture getAccessFrequencyAsync() { + return commandExecutor.readAsync(getRawName(), StringCodec.INSTANCE, RedisCommands.OBJECT_FREQ, getRawName()); + } + + @Override + public RFuture getInternalEncodingAsync() { + return commandExecutor.readAsync(getRawName(), StringCodec.INSTANCE, RedisCommands.OBJECT_ENCODING, getRawName()); + } + protected final void removeListener(int listenerId, String... names) { for (String name : names) { RPatternTopic topic = new RedissonPatternTopic(StringCodec.INSTANCE, commandExecutor, name); diff --git a/redisson/src/main/java/org/redisson/api/RObject.java b/redisson/src/main/java/org/redisson/api/RObject.java index 6bab032fa..859324e8d 100644 --- a/redisson/src/main/java/org/redisson/api/RObject.java +++ b/redisson/src/main/java/org/redisson/api/RObject.java @@ -34,6 +34,26 @@ public interface RObject extends RObjectAsync { */ Long getIdleTime(); + /** + * Returns count of references over this object. + * + * @return count of reference + */ + int getReferenceCount(); + + /** + * Returns the logarithmic access frequency counter over this object. + * + * @return frequency counter + */ + int getAccessFrequency(); + + /** + * Returns the internal encoding for the Redis object + * + * @return internal encoding + */ + RedisObjectEncoding getInternalEncoding(); /** * Returns bytes amount used by object in Redis memory. * diff --git a/redisson/src/main/java/org/redisson/api/RObjectAsync.java b/redisson/src/main/java/org/redisson/api/RObjectAsync.java index 6903974d9..79ef75afa 100644 --- a/redisson/src/main/java/org/redisson/api/RObjectAsync.java +++ b/redisson/src/main/java/org/redisson/api/RObjectAsync.java @@ -32,6 +32,27 @@ public interface RObjectAsync { */ RFuture getIdleTimeAsync(); + /** + * Returns count of references over this object. + * + * @return count of reference + */ + RFuture getReferenceCountAsync(); + + /** + * Returns the logarithmic access frequency counter over this object. + * + * @return frequency counter + */ + RFuture getAccessFrequencyAsync(); + + /** + * Returns the internal encoding for the Redis object + * + * @return internal encoding + */ + RFuture getInternalEncodingAsync(); + /** * Returns bytes amount used by object in Redis memory. * diff --git a/redisson/src/main/java/org/redisson/api/RObjectReactive.java b/redisson/src/main/java/org/redisson/api/RObjectReactive.java index c4f682ea5..978d6d74f 100644 --- a/redisson/src/main/java/org/redisson/api/RObjectReactive.java +++ b/redisson/src/main/java/org/redisson/api/RObjectReactive.java @@ -36,6 +36,27 @@ public interface RObjectReactive { */ Mono getIdleTime(); + /** + * Returns count of references over this object. + * + * @return count of reference + */ + Mono getReferenceCount(); + + /** + * Returns the logarithmic access frequency counter over this object. + * + * @return frequency counter + */ + Mono getAccessFrequency(); + + /** + * Returns the internal encoding for the Redis object + * + * @return internal encoding + */ + Mono getInternalEncoding(); + String getName(); Codec getCodec(); diff --git a/redisson/src/main/java/org/redisson/api/RObjectRx.java b/redisson/src/main/java/org/redisson/api/RObjectRx.java index 6a3e0a58e..7c8f102aa 100644 --- a/redisson/src/main/java/org/redisson/api/RObjectRx.java +++ b/redisson/src/main/java/org/redisson/api/RObjectRx.java @@ -37,6 +37,27 @@ public interface RObjectRx { */ Single getIdleTime(); + /** + * Returns count of references over this object. + * + * @return count of reference + */ + Single getReferenceCount(); + + /** + * Returns the logarithmic access frequency counter over this object. + * + * @return frequency counter + */ + Single getAccessFrequency(); + + /** + * Returns the internal encoding for the Redis object + * + * @return internal encoding + */ + Single getInternalEncoding(); + String getName(); Codec getCodec(); diff --git a/redisson/src/main/java/org/redisson/api/RedisObjectEncoding.java b/redisson/src/main/java/org/redisson/api/RedisObjectEncoding.java new file mode 100644 index 000000000..51962756d --- /dev/null +++ b/redisson/src/main/java/org/redisson/api/RedisObjectEncoding.java @@ -0,0 +1,119 @@ +/** + * Copyright (c) 2013-2024 Nikita Koksharov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson.api; + +/** + * enum type from https://redis.io/docs/latest/commands/object-encoding/ + * + * @author seakider + */ +public enum RedisObjectEncoding { + /** + * Normal string encoding. + */ + RAW("raw"), + + /** + * Strings representing integers in a 64-bit signed interval. + */ + INT("int"), + + /** + * Strings with lengths up to the hardcoded limit of OBJ_ENCODING_EMBSTR_SIZE_LIMIT or 44 bytes. + */ + EMBSTR("embstr"), + + /** + * An old list encoding. + * No longer used. + */ + LINKEDLIST("linkedlist"), + + /** + * A space-efficient encoding used for small lists. + * Redis <= 6.2 + */ + ZIPLIST("ziplist"), + + /** + * A space-efficient encoding used for small lists. + * Redis >= 7.0 + */ + LISTPACK("listpack"), + + /** + * Encoded as linkedlist of ziplists or listpacks. + */ + QUICKLIST("quicklist"), + + /** + * Normal set encoding. + */ + HASHTABLE("hashtable"), + + /** + * Small sets composed solely of integers encoding. + */ + INTSET("intset"), + + /** + * An old hash encoding. + * No longer used + */ + ZIPMAP("zipmap"), + + /** + * Normal sorted set encoding + */ + SKIPLIST("skiplist"), + + /** + * Encoded as a radix tree of listpacks + */ + STREAM("stream"), + + /** + * Key is not exist. + */ + NULL("nonexistence"), + + /** + * This means redis support new type and this Enum not defined. + */ + UNKNOWN("unknown"); + + private final String type; + + RedisObjectEncoding(String type) { + this.type = type; + } + + public String getType() { + return type; + } + + public static RedisObjectEncoding valueOfEncoding(Object object) { + if (object == null) { + return NULL; + } + String value = (String) object; + for (RedisObjectEncoding encoding : RedisObjectEncoding.values()) { + if (value.equals(encoding.getType())) + return encoding; + } + return UNKNOWN; + } +} diff --git a/redisson/src/main/java/org/redisson/client/protocol/RedisCommands.java b/redisson/src/main/java/org/redisson/client/protocol/RedisCommands.java index 28716d4bc..e30e2e1c6 100644 --- a/redisson/src/main/java/org/redisson/client/protocol/RedisCommands.java +++ b/redisson/src/main/java/org/redisson/client/protocol/RedisCommands.java @@ -26,6 +26,7 @@ import org.redisson.client.protocol.decoder.*; import org.redisson.client.protocol.pubsub.PubSubStatusDecoder; import org.redisson.cluster.ClusterNodeInfo; import org.redisson.codec.CompositeCodec; +import org.redisson.api.RedisObjectEncoding; import java.time.Duration; import java.time.Instant; @@ -733,6 +734,15 @@ public interface RedisCommands { RedisStrictCommand NOT_EXISTS = new RedisStrictCommand("EXISTS", new BooleanNumberReplayConvertor(1L)); RedisStrictCommand OBJECT_IDLETIME = new RedisStrictCommand("OBJECT", "IDLETIME", new LongReplayConvertor()); + RedisStrictCommand OBJECT_REFCOUNT = new RedisStrictCommand("OBJECT", "REFCOUNT", new IntegerReplayConvertor(0)); + RedisStrictCommand OBJECT_FREQ = new RedisStrictCommand("OBJECT", "FREQ", new IntegerReplayConvertor(0)); + RedisStrictCommand OBJECT_ENCODING = new RedisStrictCommand<>("OBJECT", "ENCODING", new Convertor() { + @Override + public RedisObjectEncoding convert(Object obj) { + return RedisObjectEncoding.valueOfEncoding(obj); + } + }); + RedisStrictCommand MEMORY_USAGE = new RedisStrictCommand("MEMORY", "USAGE", new LongReplayConvertor()); RedisStrictCommand> MEMORY_STATS = new RedisStrictCommand<>("MEMORY", "STATS", new StringMapReplayDecoder()); RedisStrictCommand RENAMENX = new RedisStrictCommand("RENAMENX", new BooleanReplayConvertor()); diff --git a/redisson/src/test/java/org/redisson/RedisDockerTest.java b/redisson/src/test/java/org/redisson/RedisDockerTest.java index 525db392e..7ddac2bd3 100644 --- a/redisson/src/test/java/org/redisson/RedisDockerTest.java +++ b/redisson/src/test/java/org/redisson/RedisDockerTest.java @@ -34,6 +34,8 @@ public class RedisDockerTest { protected static final String NOTIFY_KEYSPACE_EVENTS = "--notify-keyspace-events"; + protected static final String MAXMEMORY_POLICY = "--maxmemory-policy"; + protected static final GenericContainer REDIS = createRedis(); protected static final Protocol protocol = Protocol.RESP2; diff --git a/redisson/src/test/java/org/redisson/RedissonBucketTest.java b/redisson/src/test/java/org/redisson/RedissonBucketTest.java index f23503947..97c368155 100755 --- a/redisson/src/test/java/org/redisson/RedissonBucketTest.java +++ b/redisson/src/test/java/org/redisson/RedissonBucketTest.java @@ -6,14 +6,13 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -import org.redisson.api.DeletedObjectListener; -import org.redisson.api.ExpiredObjectListener; -import org.redisson.api.RBucket; -import org.redisson.api.RedissonClient; +import org.redisson.api.*; import org.redisson.api.listener.SetObjectListener; import org.redisson.api.listener.TrackingListener; import org.redisson.api.options.PlainOptions; import org.redisson.client.RedisResponseTimeoutException; +import org.redisson.client.codec.IntegerCodec; +import org.redisson.client.codec.LongCodec; import org.redisson.client.codec.StringCodec; import org.redisson.config.Config; import org.redisson.config.Protocol; @@ -298,6 +297,58 @@ public class RedissonBucketTest extends RedisDockerTest { assertThat(al.getIdleTime()).isBetween(4L, 6L); } + @Test + public void testReferenceCount() { + RBucket al = redisson.getBucket("test"); + assertThat(al.getReferenceCount()).isEqualTo(0); + + al.set(10000); + assertThat(al.getReferenceCount()).isEqualTo(1); + } + + @Test + public void testAccessFrequency() { + testWithParams(redisson -> { + RBucket al = redisson.getBucket("test"); + assertThat(al.getAccessFrequency()).isEqualTo(0); + al.set(10000); + al.get(); + assertThat(al.getAccessFrequency()).isGreaterThan(1); + }, MAXMEMORY_POLICY, "allkeys-lfu"); + } + + @Test + public void testInternalEncoding() { + RBucket al = redisson.getBucket("test"); + assertThat(al.getInternalEncoding()).isEqualTo(RedisObjectEncoding.NULL); + al.set(123); + assertThat(al.getInternalEncoding()).isEqualTo(RedisObjectEncoding.EMBSTR); + + RList list=redisson.getList("list"); + list.addAll(Arrays.asList("a","b","c")); + assertThat(list.getInternalEncoding()).isEqualTo(RedisObjectEncoding.LISTPACK); + + RMap map = redisson.getMap("map"); + map.put(1, "12"); + map.put(2, "33"); + map.put(3, "43"); + assertThat(map.getInternalEncoding()).isEqualTo(RedisObjectEncoding.LISTPACK); + + RSet set = redisson.getSet("set", IntegerCodec.INSTANCE); + set.add(1); + set.add(2); + set.add(3); + assertThat(set.getInternalEncoding()).isEqualTo(RedisObjectEncoding.INTSET); + + RSortedSet sortedSet = redisson.getSortedSet("sortedSet", LongCodec.INSTANCE); + sortedSet.add(2L); + sortedSet.add(0L); + sortedSet.add(1L); + sortedSet.add(5L); + assertThat(sortedSet.getInternalEncoding()).isEqualTo(RedisObjectEncoding.LISTPACK); + + } + @Test public void testDeletedListener() { testWithParams(redisson -> {