Feature - add randomKeys() and randomEntries() methods to RMap object #3415

pull/3417/head
Nikita Koksharov 4 years ago
parent 6e9e96edb5
commit cdd3d28bfb

@ -568,6 +568,26 @@ public class RedissonMap<K, V> extends RedissonExpirable implements RMap<K, V> {
return get(getAllAsync(keys)); return get(getAllAsync(keys));
} }
@Override
public Set<K> randomKeys(int count) {
return get(randomKeysAsync(count));
}
@Override
public Map<K, V> randomEntries(int count) {
return get(randomEntriesAsync(count));
}
@Override
public RFuture<Set<K>> randomKeysAsync(int count) {
return commandExecutor.readAsync(getName(), codec, RedisCommands.HRANDFIELD_KEYS, getName(), count);
}
@Override
public RFuture<Map<K, V>> randomEntriesAsync(int count) {
return commandExecutor.readAsync(getName(), codec, RedisCommands.HRANDFIELD, getName(), count, "WITHVALUES");
}
@Override @Override
public RFuture<Map<K, V>> getAllAsync(Set<K> keys) { public RFuture<Map<K, V>> getAllAsync(Set<K> keys) {
if (keys.isEmpty()) { if (keys.isEmpty()) {

@ -2367,6 +2367,97 @@ public class RedissonMapCache<K, V> extends RedissonMap<K, V> implements RMapCac
System.currentTimeMillis()); System.currentTimeMillis());
} }
@Override
public RFuture<Set<K>> randomKeysAsync(int count) {
return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_MAP_KEY_SET,
"local s = redis.call('hrandfield', KEYS[1], ARGV[2], 'withvalues'); " +
"if s == false then " +
"return {};" +
"end; " +
"local maxSize = tonumber(redis.call('hget', KEYS[5], 'max-size'));"
+ "local result = {}; "
+ "for i, v in ipairs(s) do "
+ "if i % 2 == 0 then "
+ "local t, val = struct.unpack('dLc0', v); "
+ "local key = s[i-1];" +
"local expireDate = 92233720368547758; " +
"local expireDateScore = redis.call('zscore', KEYS[2], key); "
+ "if expireDateScore ~= false then "
+ "expireDate = tonumber(expireDateScore) "
+ "end; "
+ "if t ~= 0 then "
+ "local expireIdle = redis.call('zscore', KEYS[3], key); "
+ "if expireIdle ~= false then "
+ "if tonumber(expireIdle) > tonumber(ARGV[1]) then "
+ "redis.call('zadd', KEYS[3], t + tonumber(ARGV[1]), key); "
+ "if maxSize ~= nil and maxSize ~= 0 then " +
"local mode = redis.call('hget', KEYS[5], 'mode'); " +
"if mode == false or mode == 'LRU' then " +
"redis.call('zadd', KEYS[4], tonumber(ARGV[1]), key); " +
"else " +
"redis.call('zincrby', KEYS[4], 1, key); " +
"end; "
+ "end; "
+ "end; "
+ "expireDate = math.min(expireDate, tonumber(expireIdle)) "
+ "end; "
+ "end; "
+ "if expireDate > tonumber(ARGV[1]) then "
+ "table.insert(result, key); "
+ "end; "
+ "end; "
+ "end;" +
"return result;",
Arrays.asList(getName(), getTimeoutSetName(), getIdleSetName(), getLastAccessTimeSetName(), getOptionsName()),
System.currentTimeMillis(), count);
}
@Override
public RFuture<Map<K, V>> randomEntriesAsync(int count) {
return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_MAP,
"local s = redis.call('hrandfield', KEYS[1], ARGV[2], 'withvalues'); " +
"if s == false then " +
"return {};" +
"end; "
+ "local result = {}; "
+ "local maxSize = tonumber(redis.call('hget', KEYS[5], 'max-size'));"
+ "for i, v in ipairs(s) do "
+ "if i % 2 == 0 then "
+ "local t, val = struct.unpack('dLc0', v); "
+ "local key = s[i-1];" +
"local expireDate = 92233720368547758; " +
"local expireDateScore = redis.call('zscore', KEYS[2], key); "
+ "if expireDateScore ~= false then "
+ "expireDate = tonumber(expireDateScore) "
+ "end; "
+ "if t ~= 0 then "
+ "local expireIdle = redis.call('zscore', KEYS[3], key); "
+ "if expireIdle ~= false then "
+ "if tonumber(expireIdle) > tonumber(ARGV[1]) then "
+ "redis.call('zadd', KEYS[3], t + tonumber(ARGV[1]), key); "
+ "if maxSize ~= nil and maxSize ~= 0 then " +
"local mode = redis.call('hget', KEYS[5], 'mode'); " +
"if mode == false or mode == 'LRU' then " +
"redis.call('zadd', KEYS[4], tonumber(ARGV[1]), key); " +
"else " +
"redis.call('zincrby', KEYS[4], 1, key); " +
"end; "
+ "end; "
+ "end; "
+ "expireDate = math.min(expireDate, tonumber(expireIdle)) "
+ "end; "
+ "end; "
+ "if expireDate > tonumber(ARGV[1]) then "
+ "table.insert(result, key); "
+ "table.insert(result, val); "
+ "end; "
+ "end; "
+ "end;" +
"return result;",
Arrays.asList(getName(), getTimeoutSetName(), getIdleSetName(), getLastAccessTimeSetName(), getOptionsName()),
System.currentTimeMillis(), count);
}
@Override @Override
public RFuture<Set<java.util.Map.Entry<K, V>>> readAllEntrySetAsync() { public RFuture<Set<java.util.Map.Entry<K, V>>> readAllEntrySetAsync() {
return readAll(RedisCommands.EVAL_MAP_ENTRY); return readAll(RedisCommands.EVAL_MAP_ENTRY);

@ -107,6 +107,22 @@ public interface RMap<K, V> extends ConcurrentMap<K, V>, RExpirable, RMapAsync<K
*/ */
V putIfExists(K key, V value); V putIfExists(K key, V value);
/**
* Returns random keys from this map limited by <code>count</code>
*
* @param count - keys amount to return
* @return random keys
*/
Set<K> randomKeys(int count);
/**
* Returns random map entries from this map limited by <code>count</code>
*
* @param count - entries amount to return
* @return random entries
*/
Map<K, V> randomEntries(int count);
/** /**
* Returns <code>RMapReduce</code> object associated with this map * Returns <code>RMapReduce</code> object associated with this map
* *

@ -145,6 +145,22 @@ public interface RMapAsync<K, V> extends RExpirableAsync {
*/ */
RFuture<Void> putAllAsync(Map<? extends K, ? extends V> map, int batchSize); RFuture<Void> putAllAsync(Map<? extends K, ? extends V> map, int batchSize);
/**
* Returns random keys from this map limited by <code>count</code>
*
* @param count - keys amount to return
* @return random keys
*/
RFuture<Set<K>> randomKeysAsync(int count);
/**
* Returns random map entries from this map limited by <code>count</code>
*
* @param count - entries amount to return
* @return random entries
*/
RFuture<Map<K, V>> randomEntriesAsync(int count);
/** /**
* Adds the given <code>delta</code> to the current value * Adds the given <code>delta</code> to the current value
* by mapped <code>key</code>. * by mapped <code>key</code>.

@ -347,6 +347,22 @@ public interface RMapReactive<K, V> extends RExpirableReactive {
*/ */
Mono<V> putIfExists(K key, V value); Mono<V> putIfExists(K key, V value);
/**
* Returns random keys from this map limited by <code>count</code>
*
* @param count - keys amount to return
* @return random keys
*/
Mono<Set<K>> randomKeys(int count);
/**
* Returns random map entries from this map limited by <code>count</code>
*
* @param count - entries amount to return
* @return random entries
*/
Mono<Map<K, V>> randomEntries(int count);
/** /**
* Stores the specified <code>value</code> mapped by <code>key</code> * Stores the specified <code>value</code> mapped by <code>key</code>
* only if mapping already exists. * only if mapping already exists.

@ -350,6 +350,22 @@ public interface RMapRx<K, V> extends RExpirableRx {
*/ */
Maybe<V> putIfExists(K key, V value); Maybe<V> putIfExists(K key, V value);
/**
* Returns random keys from this map limited by <code>count</code>
*
* @param count - keys amount to return
* @return random keys
*/
Single<Set<K>> randomKeys(int count);
/**
* Returns random map entries from this map limited by <code>count</code>
*
* @param count - entries amount to return
* @return random entries
*/
Single<Map<K, V>> randomEntries(int count);
/** /**
* Stores the specified <code>value</code> mapped by <code>key</code> * Stores the specified <code>value</code> mapped by <code>key</code>
* only if mapping already exists. * only if mapping already exists.

@ -87,7 +87,7 @@ public class RedisCommand<R> {
} }
public RedisCommand(String name, Convertor<R> convertor) { public RedisCommand(String name, Convertor<R> convertor) {
this(name, null, null, null); this(name, (String) null, null, null);
this.convertor = convertor; this.convertor = convertor;
} }
@ -104,6 +104,12 @@ public class RedisCommand<R> {
this.outParamType = outParamType; this.outParamType = outParamType;
} }
public RedisCommand(String name, MultiDecoder<R> replayMultiDecoder, ValueType outParamType, Convertor convertor) {
this(name, null, replayMultiDecoder);
this.outParamType = outParamType;
this.convertor = convertor;
}
public RedisCommand(String name, MultiDecoder<R> replayMultiDecoder) { public RedisCommand(String name, MultiDecoder<R> replayMultiDecoder) {
this(name, null, replayMultiDecoder); this(name, null, replayMultiDecoder);
} }

@ -21,25 +21,7 @@ import org.redisson.api.StreamInfo;
import org.redisson.api.StreamMessageId; import org.redisson.api.StreamMessageId;
import org.redisson.client.codec.StringCodec; import org.redisson.client.codec.StringCodec;
import org.redisson.client.protocol.RedisCommand.ValueType; import org.redisson.client.protocol.RedisCommand.ValueType;
import org.redisson.client.protocol.convertor.BitsSizeReplayConvertor; import org.redisson.client.protocol.convertor.*;
import org.redisson.client.protocol.convertor.BooleanAmountReplayConvertor;
import org.redisson.client.protocol.convertor.BooleanNotNullReplayConvertor;
import org.redisson.client.protocol.convertor.BooleanNullReplayConvertor;
import org.redisson.client.protocol.convertor.BooleanNullSafeReplayConvertor;
import org.redisson.client.protocol.convertor.BooleanNumberReplayConvertor;
import org.redisson.client.protocol.convertor.BooleanReplayConvertor;
import org.redisson.client.protocol.convertor.ByteReplayConvertor;
import org.redisson.client.protocol.convertor.DoubleNullSafeReplayConvertor;
import org.redisson.client.protocol.convertor.DoubleReplayConvertor;
import org.redisson.client.protocol.convertor.IntegerReplayConvertor;
import org.redisson.client.protocol.convertor.LongReplayConvertor;
import org.redisson.client.protocol.convertor.ShortReplayConvertor;
import org.redisson.client.protocol.convertor.StreamIdConvertor;
import org.redisson.client.protocol.convertor.StringToListConvertor;
import org.redisson.client.protocol.convertor.TimeObjectDecoder;
import org.redisson.client.protocol.convertor.TrueReplayConvertor;
import org.redisson.client.protocol.convertor.TypeConvertor;
import org.redisson.client.protocol.convertor.VoidReplayConvertor;
import org.redisson.client.protocol.decoder.*; import org.redisson.client.protocol.decoder.*;
import org.redisson.client.protocol.pubsub.PubSubStatusDecoder; import org.redisson.client.protocol.pubsub.PubSubStatusDecoder;
import org.redisson.cluster.ClusterNodeInfo; import org.redisson.cluster.ClusterNodeInfo;
@ -289,6 +271,8 @@ public interface RedisCommands {
RedisStrictCommand<Void> HSET_VOID = new RedisStrictCommand<Void>("HSET", new VoidReplayConvertor()); RedisStrictCommand<Void> HSET_VOID = new RedisStrictCommand<Void>("HSET", new VoidReplayConvertor());
RedisCommand<MapScanResult<Object, Object>> HSCAN = new RedisCommand<MapScanResult<Object, Object>>("HSCAN", RedisCommand<MapScanResult<Object, Object>> HSCAN = new RedisCommand<MapScanResult<Object, Object>>("HSCAN",
new ListMultiDecoder2(new MapScanResultReplayDecoder(), new ObjectMapReplayDecoder()), ValueType.MAP); new ListMultiDecoder2(new MapScanResultReplayDecoder(), new ObjectMapReplayDecoder()), ValueType.MAP);
RedisCommand<Map<Object, Object>> HRANDFIELD = new RedisCommand<Map<Object, Object>>("HRANDFIELD", new ObjectMapReplayDecoder(), ValueType.MAP, new EmptyMapConvertor());
RedisCommand<Set<Object>> HRANDFIELD_KEYS = new RedisCommand<>("HRANDFIELD", new ObjectSetReplayDecoder<>(), ValueType.MAP_KEY, new EmptySetConvertor());
RedisCommand<Map<Object, Object>> HGETALL = new RedisCommand<Map<Object, Object>>("HGETALL", new ObjectMapReplayDecoder(), ValueType.MAP); RedisCommand<Map<Object, Object>> HGETALL = new RedisCommand<Map<Object, Object>>("HGETALL", new ObjectMapReplayDecoder(), ValueType.MAP);
RedisCommand<Set<Entry<Object, Object>>> HGETALL_ENTRY = new RedisCommand<Set<Entry<Object, Object>>>("HGETALL", new ObjectMapEntryReplayDecoder(), ValueType.MAP); RedisCommand<Set<Entry<Object, Object>>> HGETALL_ENTRY = new RedisCommand<Set<Entry<Object, Object>>>("HGETALL", new ObjectMapEntryReplayDecoder(), ValueType.MAP);
RedisCommand<List<Object>> HVALS = new RedisCommand<List<Object>>("HVALS", new ObjectListReplayDecoder<Object>(), ValueType.MAP_VALUE); RedisCommand<List<Object>> HVALS = new RedisCommand<List<Object>>("HVALS", new ObjectListReplayDecoder<Object>(), ValueType.MAP_VALUE);

@ -0,0 +1,33 @@
/**
* Copyright (c) 2013-2020 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.client.protocol.convertor;
import java.util.Collections;
/**
* @author Nikita Koksharov
*/
public class EmptyMapConvertor implements Convertor<Object> {
@Override
public Object convert(Object obj) {
if (obj == null) {
return Collections.emptyMap();
}
return obj;
}
}

@ -0,0 +1,33 @@
/**
* Copyright (c) 2013-2020 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.client.protocol.convertor;
import java.util.Collections;
/**
* @author Nikita Koksharov
*/
public class EmptySetConvertor implements Convertor<Object> {
@Override
public Object convert(Object obj) {
if (obj == null) {
return Collections.emptySet();
}
return obj;
}
}

@ -132,6 +132,32 @@ public abstract class BaseMapTest extends BaseTest {
} }
} }
@Test
public void testRandomKeys() {
RMap<Integer, Integer> map = getMap("map");
Set<Integer> e1 = map.randomKeys(1);
assertThat(e1).isEmpty();
map.put(1, 11);
map.put(2, 21);
map.put(3, 31);
map.put(4, 41);
Set<Integer> e = map.randomKeys(2);
assertThat(e).containsAnyOf(1, 2, 3, 4).hasSize(2);
}
@Test
public void testRandomEntries() {
RMap<Integer, Integer> map = getMap("map");
Map<Integer, Integer> e1 = map.randomEntries(1);
assertThat(e1).isEmpty();
map.put(1, 11);
map.put(2, 21);
map.put(3, 31);
map.put(4, 41);
Map<Integer, Integer> e = map.randomEntries(2);
assertThat(e.keySet()).containsAnyOf(1, 2, 3, 4).hasSize(2);
}
@Test @Test
public void testComputeIfPresent() { public void testComputeIfPresent() {
RMap<String, String> map = getMap("map"); RMap<String, String> map = getMap("map");

Loading…
Cancel
Save