From e84c57d1ee561e915fccd5329cbdbc4b205fd244 Mon Sep 17 00:00:00 2001 From: Johno Crawford Date: Mon, 24 Jul 2017 12:15:27 +0200 Subject: [PATCH 1/2] Add support max size configuration for RedissonMapCache. --- .../java/org/redisson/RedissonMapCache.java | 1969 ++++++++++------- .../java/org/redisson/api/MapOptions.java | 18 +- .../org/redisson/RedissonMapCacheTest.java | 25 + 3 files changed, 1186 insertions(+), 826 deletions(-) diff --git a/redisson/src/main/java/org/redisson/RedissonMapCache.java b/redisson/src/main/java/org/redisson/RedissonMapCache.java index c052ce7a3..cd419fe36 100644 --- a/redisson/src/main/java/org/redisson/RedissonMapCache.java +++ b/redisson/src/main/java/org/redisson/RedissonMapCache.java @@ -15,16 +15,8 @@ */ package org.redisson; -import java.math.BigDecimal; -import java.net.InetSocketAddress; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.TimeUnit; - +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.FutureListener; import org.redisson.api.MapOptions; import org.redisson.api.RFuture; import org.redisson.api.RMapCache; @@ -58,8 +50,15 @@ import org.redisson.command.CommandAsyncExecutor; import org.redisson.connection.decoder.MapGetAllDecoder; import org.redisson.eviction.EvictionScheduler; -import io.netty.util.concurrent.Future; -import io.netty.util.concurrent.FutureListener; +import java.math.BigDecimal; +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; /** *

Map-based cache with ability to set TTL for each entry via @@ -82,134 +81,161 @@ import io.netty.util.concurrent.FutureListener; */ public class RedissonMapCache extends RedissonMap implements RMapCache { - public RedissonMapCache(EvictionScheduler evictionScheduler, CommandAsyncExecutor commandExecutor, - String name, RedissonClient redisson, MapOptions options) { + private final int maxSize; + + public RedissonMapCache(EvictionScheduler evictionScheduler, CommandAsyncExecutor commandExecutor, + String name, RedissonClient redisson, MapOptions options) { super(commandExecutor, name, redisson, options); evictionScheduler.schedule(getName(), getTimeoutSetName(), getIdleSetName(), getExpiredChannelName()); + + if (options != null) { + this.maxSize = options.getMaxSize(); + } else { + this.maxSize = 0; + } } - public RedissonMapCache(Codec codec, EvictionScheduler evictionScheduler, CommandAsyncExecutor commandExecutor, - String name, RedissonClient redisson, MapOptions options) { + public RedissonMapCache(Codec codec, EvictionScheduler evictionScheduler, CommandAsyncExecutor commandExecutor, + String name, RedissonClient redisson, MapOptions options) { super(codec, commandExecutor, name, redisson, options); evictionScheduler.schedule(getName(), getTimeoutSetName(), getIdleSetName(), getExpiredChannelName()); + + if (options != null) { + this.maxSize = options.getMaxSize(); + } else { + this.maxSize = 0; + } } - + @Override public RFuture containsKeyAsync(Object key) { checkKey(key); - + return commandExecutor.evalWriteAsync(getName(key), codec, RedisCommands.EVAL_BOOLEAN, - "local value = redis.call('hget', KEYS[1], ARGV[2]); " + - "local expireDate = 92233720368547758; " + - "if value ~= false then " + - "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); " - + "if expireDateScore ~= false then " - + "expireDate = tonumber(expireDateScore) " - + "end; " - + "local t, val = struct.unpack('dLc0', value); " - + "if t ~= 0 then " - + "local expireIdle = redis.call('zscore', KEYS[3], ARGV[2]); " - + "if expireIdle ~= false then " - + "if tonumber(expireIdle) > tonumber(ARGV[1]) then " - + "local value = struct.pack('dLc0', t, string.len(val), val); " - + "redis.call('hset', KEYS[1], ARGV[2], value); " - + "redis.call('zadd', KEYS[3], t + tonumber(ARGV[1]), ARGV[2]); " - + "end; " - + "expireDate = math.min(expireDate, tonumber(expireIdle)) " - + "end; " - + "end; " - + "if expireDate <= tonumber(ARGV[1]) then " - + "return 0;" - + "end; " - + "return 1;" + + "local value = redis.call('hget', KEYS[1], ARGV[2]);" + + "local expireDate = 92233720368547758;" + + "if value ~= false then" + + " local maxSize = tonumber(ARGV[3]);" + + " if maxSize ~= 0 then" + + " redis.call('zadd', KEYS[4], tonumber(ARGV[1]), ARGV[2]);" + + " end;" + + " local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]);" + + " if expireDateScore ~= false then" + + " expireDate = tonumber(expireDateScore)" + + " end;" + + " local t, val = struct.unpack('dLc0', value);" + + " if t ~= 0 then" + + " local expireIdle = redis.call('zscore', KEYS[3], ARGV[2]);" + + " if expireIdle ~= false then" + + " if tonumber(expireIdle) > tonumber(ARGV[1]) then" + + " local value = struct.pack('dLc0', t, string.len(val), val);" + + " redis.call('hset', KEYS[1], ARGV[2], value);" + + " redis.call('zadd', KEYS[3], t + tonumber(ARGV[1]), ARGV[2]);" + + " end;" + + " expireDate = math.min(expireDate, tonumber(expireIdle))" + + " end;" + + " end;" + + " if expireDate <= tonumber(ARGV[1]) then" + + " return 0;" + + " end;" + + " return 1;" + "end;" + "return 0; ", - Arrays.asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key)), - System.currentTimeMillis(), encodeMapKey(key)); + Arrays.asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key), getLastAccessTimeSetNameByKey(key)), + System.currentTimeMillis(), encodeMapKey(key), maxSize); } @Override public RFuture containsValueAsync(Object value) { checkValue(value); - + return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN, - "local s = redis.call('hgetall', KEYS[1]); " - + "for i, v in ipairs(s) do " - + "if i % 2 == 0 then " - + "local t, val = struct.unpack('dLc0', v); " - + "if ARGV[2] == val then " - + "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 " - + "local value = struct.pack('dLc0', t, string.len(val), val); " - + "redis.call('hset', KEYS[1], key, value); " - + "redis.call('zadd', KEYS[3], t + tonumber(ARGV[1]), key); " - + "end; " - + "expireDate = math.min(expireDate, tonumber(expireIdle)) " - + "end; " - + "end; " - + "if expireDate <= tonumber(ARGV[1]) then " - + "return 0;" - + "end; " - + "return 1; " - + "end; " - + "end; " - + "end;" + - "return 0;", - Arrays.asList(getName(), getTimeoutSetName(), getIdleSetName()), System.currentTimeMillis(), encodeMapValue(value)); + "local s = redis.call('hgetall', KEYS[1]);" + + "for i, v in ipairs(s) do" + + " if i % 2 == 0 then" + + " local t, val = struct.unpack('dLc0', v);" + + " if ARGV[2] == val then" + + " local key = s[i - 1];" + + "" + + " local maxSize = tonumber(ARGV[3]);" + + " if maxSize ~= 0 then" + + " redis.call('zadd', KEYS[4], tonumber(ARGV[1]), key);" + + " end;" + + "" + + " 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" + + " local value = struct.pack('dLc0', t, string.len(val), val);" + + " redis.call('hset', KEYS[1], key, value);" + + " redis.call('zadd', KEYS[3], t + tonumber(ARGV[1]), key);" + + " end;" + + " expireDate = math.min(expireDate, tonumber(expireIdle))" + + " end;" + + " end;" + + " if expireDate <= tonumber(ARGV[1]) then" + + " return 0;" + + " end;" + + " return 1;" + + " end;" + + " end;" + + "end;" + + "return 0;", + Arrays.asList(getName(), getTimeoutSetName(), getIdleSetName(), getLastAccessTimeSetName()), + System.currentTimeMillis(), encodeMapValue(value), maxSize); } @Override protected RFuture> getAllOperationAsync(Set keys) { - List args = new ArrayList(keys.size() + 1); + List args = new ArrayList(keys.size() + 2); args.add(System.currentTimeMillis()); + args.add(maxSize); args.addAll(keys); - return commandExecutor.evalWriteAsync(getName(), codec, new RedisCommand>("EVAL", new MapGetAllDecoder(args, 1), 7, ValueType.MAP_KEY, ValueType.MAP_VALUE), - "local expireHead = redis.call('zrange', KEYS[2], 0, 0, 'withscores');" + - "local currentTime = tonumber(table.remove(ARGV, 1)); " // index is the first parameter - + "local hasExpire = #expireHead == 2 and tonumber(expireHead[2]) <= currentTime; " - + "local map = redis.call('hmget', KEYS[1], unpack(ARGV)); " - + "for i = #map, 1, -1 do " - + "local value = map[i]; " - + "if value ~= false then " - + "local key = ARGV[i]; " - + "local t, val = struct.unpack('dLc0', value); " - + "map[i] = val; " - - + "if hasExpire then " - + "local expireDate = redis.call('zscore', KEYS[2], key); " - + "if expireDate ~= false and tonumber(expireDate) <= currentTime then " - + "map[i] = false; " - + "end; " - + "end; " - - + "if t ~= 0 then " - + "local expireIdle = redis.call('zscore', KEYS[3], key); " - + "if expireIdle ~= false then " - + "if tonumber(expireIdle) > currentTime then " - + "local value = struct.pack('dLc0', t, string.len(val), val); " - + "redis.call('hset', KEYS[1], key, value); " - + "redis.call('zadd', KEYS[3], t + currentTime, key); " - + "else " - + "map[i] = false; " - + "end; " - + "end; " - + "end; " - - + "end; " - + "end; " - + "return map;", + return commandExecutor.evalWriteAsync(getName(), codec, new RedisCommand>("EVAL", new MapGetAllDecoder(args, 2), 7, ValueType.MAP_KEY, ValueType.MAP_VALUE), + "local expireHead = redis.call('zrange', KEYS[2], 0, 0, 'withscores');" + + "local currentTime = tonumber(table.remove(ARGV, 1));" + // index is the first parameter + "local maxSize = tonumber(table.remove(ARGV, 1));" + // index is the first parameter + "local hasExpire = #expireHead == 2 and tonumber(expireHead[2]) <= currentTime;" + + "local map = redis.call('hmget', KEYS[1], unpack(ARGV));" + + "for i = #map, 1, -1 do" + + " local value = map[i];" + + " if value ~= false then" + + " local key = ARGV[i];" + + " local t, val = struct.unpack('dLc0', value);" + + " map[i] = val;" + + " if maxSize ~= 0 then" + + " redis.call('zadd', KEYS[4], currentTime, key);" + + " end;" + + " if hasExpire then" + + " local expireDate = redis.call('zscore', KEYS[2], key);" + + " if expireDate ~= false and tonumber(expireDate) <= currentTime then" + + " map[i] = false;" + + " end;" + + " end;" + + " if t ~= 0 then" + + " local expireIdle = redis.call('zscore', KEYS[3], key);" + + " if expireIdle ~= false then" + + " if tonumber(expireIdle) > currentTime then" + + " local value = struct.pack('dLc0', t, string.len(val), val);" + + " redis.call('hset', KEYS[1], key, value);" + + " redis.call('zadd', KEYS[3], t + currentTime, key);" + + " else" + + " map[i] = false;" + + " end;" + + " end;" + + " end;" + + " end;" + + "end;" + + "return map;", Arrays.asList(getName(), getTimeoutSetName(), getIdleSetName()), args.toArray()); } - + @Override public V putIfAbsent(K key, V value, long ttl, TimeUnit ttlUnit) { return get(putIfAbsentAsync(key, value, ttl, ttlUnit)); @@ -261,68 +287,93 @@ public class RedissonMapCache extends RedissonMap implements RMapCac } RFuture future = commandExecutor.evalWriteAsync(getName(key), codec, RedisCommands.EVAL_MAP_VALUE, - "local insertable = false; " - + "local value = redis.call('hget', KEYS[1], ARGV[5]); " - + "if value == false then " - + "insertable = true; " - + "else " - + "local t, val = struct.unpack('dLc0', value); " - + "local expireDate = 92233720368547758; " - + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[5]); " - + "if expireDateScore ~= false then " - + "expireDate = tonumber(expireDateScore) " - + "end; " - + "if t ~= 0 then " - + "local expireIdle = redis.call('zscore', KEYS[3], ARGV[5]); " - + "if expireIdle ~= false then " - + "expireDate = math.min(expireDate, tonumber(expireIdle)) " - + "end; " - + "end; " - + "if expireDate <= tonumber(ARGV[1]) then " - + "insertable = true; " - + "end; " - + "end; " - - + "if insertable == true then " - // ttl - + "if tonumber(ARGV[2]) > 0 then " - + "redis.call('zadd', KEYS[2], ARGV[2], ARGV[5]); " - + "else " - + "redis.call('zrem', KEYS[2], ARGV[5]); " - + "end; " - - // idle - + "if tonumber(ARGV[3]) > 0 then " - + "redis.call('zadd', KEYS[3], ARGV[3], ARGV[5]); " - + "else " - + "redis.call('zrem', KEYS[3], ARGV[5]); " - + "end; " - - // value - + "local val = struct.pack('dLc0', tonumber(ARGV[4]), string.len(ARGV[6]), ARGV[6]); " - + "redis.call('hset', KEYS[1], ARGV[5], val); " - - + "local msg = struct.pack('Lc0Lc0', string.len(ARGV[5]), ARGV[5], string.len(ARGV[6]), ARGV[6]); " - + "redis.call('publish', KEYS[4], msg); " - - + "return nil;" - + "else " - + "local t, val = struct.unpack('dLc0', value); " - + "redis.call('zadd', KEYS[3], t + ARGV[1], ARGV[5]); " - + "return val;" - + "end; ", - Arrays.asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key), getCreatedChannelNameByKey(key)), - System.currentTimeMillis(), ttlTimeout, maxIdleTimeout, maxIdleDelta, encodeMapKey(key), encodeMapValue(value)); + "local insertable = false; " + + "local value = redis.call('hget', KEYS[1], ARGV[5]); " + + "if value == false then " + + "insertable = true; " + + "else " + + "local t, val = struct.unpack('dLc0', value); " + + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[5]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if t ~= 0 then " + + "local expireIdle = redis.call('zscore', KEYS[3], ARGV[5]); " + + "if expireIdle ~= false then " + + "expireDate = math.min(expireDate, tonumber(expireIdle)) " + + "end; " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "insertable = true; " + + "end; " + + "end; " + + + "if insertable == true then " + // ttl + + "if tonumber(ARGV[2]) > 0 then " + + "redis.call('zadd', KEYS[2], ARGV[2], ARGV[5]); " + + "else " + + "redis.call('zrem', KEYS[2], ARGV[5]); " + + "end; " + + // idle + + "if tonumber(ARGV[3]) > 0 then " + + "redis.call('zadd', KEYS[3], ARGV[3], ARGV[5]); " + + "else " + + "redis.call('zrem', KEYS[3], ARGV[5]); " + + "end; " + + // last access time + + "local maxSize = tonumber(ARGV[7]);" + + "if maxSize ~= 0 then" + + " local currentTime = tonumber(ARGV[1]);" + + " local lastAccessTimeSetName = KEYS[5];" + + " redis.call('zadd', lastAccessTimeSetName, currentTime, ARGV[5]);" + + " local cacheSize = tonumber(redis.call('hlen', KEYS[1]));" + + " if cacheSize >= maxSize then" + + " local lruItems = redis.call('zrange', lastAccessTimeSetName, 0, cacheSize - maxSize);" + + " for index, lruItem in ipairs(lruItems) do" + + " if lruItem then" + + " local lruItemValue = redis.call('hget', KEYS[1], lruItem);" + + " redis.call('hdel', KEYS[1], lruItem);" + + " redis.call('zrem', KEYS[2], lruItem);" + + " redis.call('zrem', KEYS[3], lruItem);" + + " redis.call('zrem', lastAccessTimeSetName, lruItem);" + + " local removedChannelName = KEYS[6];" + + " local msg = struct.pack('Lc0Lc0', string.len(lruItem), lruItem, string.len(lruItemValue), lruItemValue);" + + " redis.call('publish', removedChannelName, msg);" + + " end;" + + " end" + + " end;" + + "end;" + + // value + + "local val = struct.pack('dLc0', tonumber(ARGV[4]), string.len(ARGV[6]), ARGV[6]); " + + "redis.call('hset', KEYS[1], ARGV[5], val); " + + + "local msg = struct.pack('Lc0Lc0', string.len(ARGV[5]), ARGV[5], string.len(ARGV[6]), ARGV[6]); " + + "redis.call('publish', KEYS[4], msg); " + + + "return nil;" + + "else " + + "local t, val = struct.unpack('dLc0', value); " + + "redis.call('zadd', KEYS[3], t + ARGV[1], ARGV[5]); " + + "return val;" + + "end; ", + Arrays.asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key), getCreatedChannelNameByKey(key), + getLastAccessTimeSetName(), getRemovedChannelName()), + System.currentTimeMillis(), ttlTimeout, maxIdleTimeout, maxIdleDelta, encodeMapKey(key), encodeMapValue(value), maxSize); if (hasNoWriter()) { return future; } - + MapWriterTask listener = new MapWriterTask() { @Override protected void execute() { options.getWriter().write(key, value); } - + @Override protected boolean condition(Future future) { return future.getNow() == null; @@ -334,72 +385,81 @@ public class RedissonMapCache extends RedissonMap implements RMapCac @Override protected RFuture removeOperationAsync(Object key, Object value) { return commandExecutor.evalWriteAsync(getName(key), codec, RedisCommands.EVAL_BOOLEAN, - "local value = redis.call('hget', KEYS[1], ARGV[2]); " - + "if value == false then " - + "return 0; " - + "end; " - + "local t, val = struct.unpack('dLc0', value); " - + "local expireDate = 92233720368547758; " + - "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); " - + "if expireDateScore ~= false then " - + "expireDate = tonumber(expireDateScore) " - + "end; " - + "if t ~= 0 then " - + "local expireIdle = redis.call('zscore', KEYS[3], ARGV[2]); " - + "if expireIdle ~= false then " - + "expireDate = math.min(expireDate, tonumber(expireIdle)) " - + "end; " - + "end; " - + "if expireDate <= tonumber(ARGV[1]) then " - + "return 0; " - + "end; " - - + "if val == ARGV[3] then " - + "redis.call('zrem', KEYS[2], ARGV[2]); " - + "redis.call('zrem', KEYS[3], ARGV[2]); " - + "redis.call('hdel', KEYS[1], ARGV[2]); " - + "local msg = struct.pack('Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(val), val); " - + "redis.call('publish', KEYS[4], msg); " - + "return 1; " - + "else " - + "return 0; " - + "end", - Arrays.asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key), getRemovedChannelNameByKey(key)), - System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value)); + "local value = redis.call('hget', KEYS[1], ARGV[2]); " + + "if value == false then " + + "return 0; " + + "end; " + + "local t, val = struct.unpack('dLc0', value); " + + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if t ~= 0 then " + + "local expireIdle = redis.call('zscore', KEYS[3], ARGV[2]); " + + "if expireIdle ~= false then " + + "expireDate = math.min(expireDate, tonumber(expireIdle)) " + + "end; " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return 0; " + + "end; " + + + "if val == ARGV[3] then " + + "redis.call('zrem', KEYS[2], ARGV[2]); " + + "redis.call('zrem', KEYS[3], ARGV[2]); " + + "local maxSize = tonumber(ARGV[4]);" + + "if maxSize ~= 0 then" + + " redis.call('zrem', KEYS[5], ARGV[2]);" + + "end;" + + "redis.call('hdel', KEYS[1], ARGV[2]); " + + "local msg = struct.pack('Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(val), val); " + + "redis.call('publish', KEYS[4], msg); " + + "return 1; " + + "else " + + "return 0; " + + "end", + Arrays.asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key), getRemovedChannelNameByKey(key), + getLastAccessTimeSetName()), + System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value), maxSize); } @Override protected RFuture getOperationAsync(K key) { checkKey(key); - + return commandExecutor.evalWriteAsync(getName(key), codec, RedisCommands.EVAL_MAP_VALUE, - "local value = redis.call('hget', KEYS[1], ARGV[2]); " - + "if value == false then " - + "return nil; " - + "end; " - + "local t, val = struct.unpack('dLc0', value); " - + "local expireDate = 92233720368547758; " + - "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); " - + "if expireDateScore ~= false then " - + "expireDate = tonumber(expireDateScore) " - + "end; " - + "if t ~= 0 then " - + "local expireIdle = redis.call('zscore', KEYS[3], ARGV[2]); " - + "if expireIdle ~= false then " - + "if tonumber(expireIdle) > tonumber(ARGV[1]) then " - + "local value = struct.pack('dLc0', t, string.len(val), val); " - + "redis.call('hset', KEYS[1], ARGV[2], value); " - + "redis.call('zadd', KEYS[3], t + tonumber(ARGV[1]), ARGV[2]); " - + "end; " - + "expireDate = math.min(expireDate, tonumber(expireIdle)) " - + "end; " - + "end; " - + "if expireDate <= tonumber(ARGV[1]) then " - + "return nil; " - + "end; " - + "return val; ", - Arrays.asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key)), - System.currentTimeMillis(), encodeMapKey(key)); + "local value = redis.call('hget', KEYS[1], ARGV[2]); " + + "if value == false then " + + "return nil; " + + "end; " + + "local t, val = struct.unpack('dLc0', value); " + + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if t ~= 0 then " + + "local expireIdle = redis.call('zscore', KEYS[3], ARGV[2]); " + + "if expireIdle ~= false then " + + "if tonumber(expireIdle) > tonumber(ARGV[1]) then " + + "local value = struct.pack('dLc0', t, string.len(val), val); " + + "redis.call('hset', KEYS[1], ARGV[2], value); " + + "redis.call('zadd', KEYS[3], t + tonumber(ARGV[1]), ARGV[2]); " + + "end; " + + "expireDate = math.min(expireDate, tonumber(expireIdle)) " + + "end; " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return nil; " + + "end; " + + "local maxSize = tonumber(ARGV[3]);" + + "if maxSize ~= 0 then" + + " redis.call('zadd', KEYS[4], tonumber(ARGV[1]), ARGV[2]);" + + "end;" + + "return val; ", + Arrays.asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key), getLastAccessTimeSetNameByKey(key)), + System.currentTimeMillis(), encodeMapKey(key), maxSize); } @Override @@ -410,121 +470,202 @@ public class RedissonMapCache extends RedissonMap implements RMapCac @Override protected RFuture putOperationAsync(K key, V value) { return commandExecutor.evalWriteAsync(getName(key), codec, RedisCommands.EVAL_MAP_VALUE, - "local v = redis.call('hget', KEYS[1], ARGV[2]); " + - "local exists = false;" - + "if v ~= false then " - + "local t, val = struct.unpack('dLc0', v); " - + "local expireDate = 92233720368547758; " - + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); " - + "if expireDateScore ~= false then " - + "expireDate = tonumber(expireDateScore) " - + "end; " - + "if t ~= 0 then " - + "local expireIdle = redis.call('zscore', KEYS[3], ARGV[2]); " - + "if expireIdle ~= false then " - + "expireDate = math.min(expireDate, tonumber(expireIdle)) " - + "end; " - + "end; " - + "if expireDate > tonumber(ARGV[1]) then " - + "exists = true; " - + "end; " - + "end; " - - + "local value = struct.pack('dLc0', 0, string.len(ARGV[3]), ARGV[3]); " - + "redis.call('hset', KEYS[1], ARGV[2], value); " - + "if exists == false then " - + "local msg = struct.pack('Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[3]), ARGV[3]); " - + "redis.call('publish', KEYS[4], msg); " - + "return nil; " - + "end; " - - + "local t, val = struct.unpack('dLc0', v); " - + "local msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[3]), ARGV[3], string.len(val), val); " - + "redis.call('publish', KEYS[5], msg); " - + "return val; ", - Arrays.asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key), getCreatedChannelNameByKey(key), getUpdatedChannelNameByKey(key)), - System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value)); + "local v = redis.call('hget', KEYS[1], ARGV[2]);" + + "local exists = false;" + + "if v ~= false then" + + " local t, val = struct.unpack('dLc0', v);" + + " local expireDate = 92233720368547758;" + + " local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]);" + + " if expireDateScore ~= false then" + + " expireDate = tonumber(expireDateScore)" + + " end;" + + " if t ~= 0 then" + + " local expireIdle = redis.call('zscore', KEYS[3], ARGV[2]);" + + " if expireIdle ~= false then" + + " expireDate = math.min(expireDate, tonumber(expireIdle))" + + " end;" + + " end;" + + " if expireDate > tonumber(ARGV[1]) then" + + " exists = true;" + + " end;" + + "end;" + + "local value = struct.pack('dLc0', 0, string.len(ARGV[3]), ARGV[3]);" + + "redis.call('hset', KEYS[1], ARGV[2], value);" + + "local currentTime = tonumber(ARGV[1]);" + + "local lastAccessTimeSetName = KEYS[6];" + + "local maxSize = tonumber(ARGV[4]);" + + "if exists == false then" + + " if maxSize ~= 0 then" + + " redis.call('zadd', lastAccessTimeSetName, currentTime, ARGV[2]);" + + " local cacheSize = tonumber(redis.call('hlen', KEYS[1]));" + + " if cacheSize >= maxSize then" + + " local lruItems = redis.call('zrange', lastAccessTimeSetName, 0, cacheSize - maxSize);" + + " for index, lruItem in ipairs(lruItems) do" + + " if lruItem then" + + " local lruItemValue = redis.call('hget', KEYS[1], lruItem);" + + " redis.call('hdel', KEYS[1], lruItem);" + + " redis.call('zrem', KEYS[2], lruItem);" + + " redis.call('zrem', KEYS[3], lruItem);" + + " redis.call('zrem', lastAccessTimeSetName, lruItem);" + + " local removedChannelName = KEYS[7];" + + " local msg = struct.pack('Lc0Lc0', string.len(lruItem), lruItem, string.len(lruItemValue), lruItemValue);" + + " redis.call('publish', removedChannelName, msg);" + + " end;" + + " end" + + " end;" + + " end;" + + " local msg = struct.pack('Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[3]), ARGV[3]);" + + " redis.call('publish', KEYS[4], msg);" + + " return nil;" + + "else" + + " if maxSize ~= 0 then" + + " redis.call('zadd', lastAccessTimeSetName, currentTime, ARGV[2]);" + + " end;" + + "end;" + + "" + + "local t, val = struct.unpack('dLc0', v);" + + "local msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[3]), ARGV[3], string.len(val), val);" + + "redis.call('publish', KEYS[5], msg);" + + "return val;", + Arrays.asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key), getCreatedChannelNameByKey(key), + getUpdatedChannelNameByKey(key), getLastAccessTimeSetNameByKey(key), getRemovedChannelNameByKey(key)), + System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value), maxSize); } @Override protected RFuture putIfAbsentOperationAsync(K key, V value) { return commandExecutor.evalWriteAsync(getName(key), codec, RedisCommands.EVAL_MAP_VALUE, "local value = redis.call('hget', KEYS[1], ARGV[2]); " - + "if value ~= false then " - + "local t, val = struct.unpack('dLc0', value); " - + "local expireDate = 92233720368547758; " - + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); " - + "if expireDateScore ~= false then " - + "expireDate = tonumber(expireDateScore) " - + "end; " - + "if t ~= 0 then " - + "local expireIdle = redis.call('zscore', KEYS[3], ARGV[2]); " - + "if expireIdle ~= false then " - + "expireDate = math.min(expireDate, tonumber(expireIdle)) " - + "end; " - + "end; " - + "if expireDate > tonumber(ARGV[1]) then " - + "return val; " - + "end; " - + "end; " - - + "local value = struct.pack('dLc0', 0, string.len(ARGV[3]), ARGV[3]); " - + "redis.call('hset', KEYS[1], ARGV[2], value); " - + "local msg = struct.pack('Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[3]), ARGV[3]); " - + "redis.call('publish', KEYS[4], msg); " - + "return nil;", - Arrays.asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key), getCreatedChannelNameByKey(key)), - System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value)); + + "local maxSize = tonumber(ARGV[4]);" + + "local lastAccessTimeSetName = KEYS[5];" + + "local currentTime = tonumber(ARGV[1]);" + + "if value ~= false then " + + "local t, val = struct.unpack('dLc0', value); " + + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if t ~= 0 then " + + "local expireIdle = redis.call('zscore', KEYS[3], ARGV[2]); " + + "if expireIdle ~= false then " + + "expireDate = math.min(expireDate, tonumber(expireIdle)) " + + "end; " + + "end; " + + "if expireDate > tonumber(ARGV[1]) then " + + "if maxSize ~= 0 then" + + " redis.call('zadd', lastAccessTimeSetName, currentTime, ARGV[2]);" + + "end;" + + "return val; " + + "end; " + + "end; " + + + "local value = struct.pack('dLc0', 0, string.len(ARGV[3]), ARGV[3]); " + + "redis.call('hset', KEYS[1], ARGV[2], value); " + + // last access time + + "if maxSize ~= 0 then" + + " redis.call('zadd', lastAccessTimeSetName, currentTime, ARGV[2]);" + + " local cacheSize = tonumber(redis.call('hlen', KEYS[1]));" + + " if cacheSize >= maxSize then" + + " local lruItems = redis.call('zrange', lastAccessTimeSetName, 0, cacheSize - maxSize);" + + " for index, lruItem in ipairs(lruItems) do" + + " if lruItem then" + + " local lruItemValue = redis.call('hget', KEYS[1], lruItem);" + + " redis.call('hdel', KEYS[1], lruItem);" + + " redis.call('zrem', KEYS[2], lruItem);" + + " redis.call('zrem', KEYS[3], lruItem);" + + " redis.call('zrem', lastAccessTimeSetName, lruItem);" + + " local removedChannelName = KEYS[6];" + + " local msg = struct.pack('Lc0Lc0', string.len(lruItem), lruItem, string.len(lruItemValue), lruItemValue);" + + " redis.call('publish', removedChannelName, msg);" + + " end;" + + " end" + + " end;" + + "end;" + + + "local msg = struct.pack('Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[3]), ARGV[3]); " + + "redis.call('publish', KEYS[4], msg); " + + "return nil;", + Arrays.asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key), getCreatedChannelNameByKey(key), + getLastAccessTimeSetNameByKey(key), getRemovedChannelNameByKey(key)), + System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value), maxSize); } @Override public V addAndGet(K key, Number value) { return get(addAndGetAsync(key, value)); } - + @Override public RFuture addAndGetOperationAsync(K key, Number value) { byte[] keyState = encodeMapKey(key); return commandExecutor.evalWriteAsync(getName(key), StringCodec.INSTANCE, new RedisCommand("EVAL", new NumberConvertor(value.getClass())), - "local value = redis.call('hget', KEYS[1], ARGV[2]); " - + "local expireDate = 92233720368547758; " - + "local t = 0; " - + "local val = 0; " - + "if value ~= false then " - + "t, val = struct.unpack('dLc0', value); " - + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); " - + "if expireDateScore ~= false then " - + "expireDate = tonumber(expireDateScore) " - + "end; " - + "if t ~= 0 then " - + "local expireIdle = redis.call('zscore', KEYS[3], ARGV[2]); " - + "if expireIdle ~= false then " - + "if tonumber(expireIdle) > tonumber(ARGV[1]) then " - + "local value = struct.pack('dLc0', t, string.len(val), val); " - + "redis.call('hset', KEYS[1], ARGV[2], value); " - + "redis.call('zadd', KEYS[3], t + tonumber(ARGV[1]), ARGV[2]); " - + "end; " - + "expireDate = math.min(expireDate, tonumber(expireIdle)) " - + "end; " - + "end; " - + "end; " - - + "local newValue = tonumber(ARGV[3]); " - + "if value ~= false and expireDate > tonumber(ARGV[1]) then " - + "newValue = tonumber(val) + newValue; " - - + "local msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(newValue), newValue, string.len(val), val); " - + "redis.call('publish', KEYS[5], msg); " - + "else " - + "local msg = struct.pack('Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[3]), ARGV[3]); " - + "redis.call('publish', KEYS[4], msg); " - + "end; " - + "local newValuePack = struct.pack('dLc0', t + tonumber(ARGV[1]), string.len(newValue), newValue); " - + "redis.call('hset', KEYS[1], ARGV[2], newValuePack); " - + "return tostring(newValue); ", - Arrays.asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key), getCreatedChannelNameByKey(key), getUpdatedChannelNameByKey(key)), - System.currentTimeMillis(), keyState, new BigDecimal(value.toString()).toPlainString()); + "local value = redis.call('hget', KEYS[1], ARGV[2]); " + + "local expireDate = 92233720368547758; " + + "local t = 0; " + + "local val = 0; " + + "if value ~= false then " + + "t, val = struct.unpack('dLc0', value); " + + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if t ~= 0 then " + + "local expireIdle = redis.call('zscore', KEYS[3], ARGV[2]); " + + "if expireIdle ~= false then " + + "if tonumber(expireIdle) > tonumber(ARGV[1]) then " + + "local value = struct.pack('dLc0', t, string.len(val), val); " + + "redis.call('hset', KEYS[1], ARGV[2], value); " + + "redis.call('zadd', KEYS[3], t + tonumber(ARGV[1]), ARGV[2]); " + + "end; " + + "expireDate = math.min(expireDate, tonumber(expireIdle)) " + + "end; " + + "end; " + + "end; " + + + "local newValue = tonumber(ARGV[3]); " + + "if value ~= false and expireDate > tonumber(ARGV[1]) then " + + "newValue = tonumber(val) + newValue; " + + + "local msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(newValue), newValue, string.len(val), val); " + + "redis.call('publish', KEYS[5], msg); " + + "else " + + "local msg = struct.pack('Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[3]), ARGV[3]); " + + "redis.call('publish', KEYS[4], msg); " + + "end; " + + "local newValuePack = struct.pack('dLc0', t + tonumber(ARGV[1]), string.len(newValue), newValue); " + + "redis.call('hset', KEYS[1], ARGV[2], newValuePack); " + + // last access time + + "local maxSize = tonumber(ARGV[4]);" + + "if maxSize ~= 0 then" + + " local currentTime = tonumber(ARGV[1]);" + + " local lastAccessTimeSetName = KEYS[6];" + + " redis.call('zadd', lastAccessTimeSetName, currentTime, ARGV[2]);" + + " local cacheSize = tonumber(redis.call('hlen', KEYS[1]));" + + " if cacheSize >= maxSize then" + + " local lruItems = redis.call('zrange', lastAccessTimeSetName, 0, cacheSize - maxSize);" + + " for index, lruItem in ipairs(lruItems) do" + + " if lruItem then" + + " local lruItemValue = redis.call('hget', KEYS[1], lruItem);" + + " redis.call('hdel', KEYS[1], lruItem);" + + " redis.call('zrem', KEYS[2], lruItem);" + + " redis.call('zrem', KEYS[3], lruItem);" + + " redis.call('zrem', lastAccessTimeSetName, lruItem);" + + " local removedChannelName = KEYS[7];" + + " local msg = struct.pack('Lc0Lc0', string.len(lruItem), lruItem, string.len(lruItemValue), lruItemValue);" + + " redis.call('publish', removedChannelName, msg);" + + " end;" + + " end" + + " end;" + + "end;" + + + "return tostring(newValue); ", + Arrays.asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key), getCreatedChannelNameByKey(key), + getUpdatedChannelNameByKey(key), getLastAccessTimeSetNameByKey(key), getRemovedChannelNameByKey(key)), + System.currentTimeMillis(), keyState, new BigDecimal(value.toString()).toPlainString(), maxSize); } @Override @@ -546,7 +687,7 @@ public class RedissonMapCache extends RedissonMap implements RMapCac public RFuture fastPutAsync(final K key, final V value, long ttl, TimeUnit ttlUnit, long maxIdleTime, TimeUnit maxIdleUnit) { checkKey(key); checkValue(value); - + if (ttl < 0) { throw new IllegalArgumentException("ttl can't be negative"); } @@ -566,11 +707,11 @@ public class RedissonMapCache extends RedissonMap implements RMapCac } RFuture future = fastPutOperationAsync(key, value, ttl, ttlUnit, maxIdleTime, maxIdleUnit); - + if (hasNoWriter()) { return future; } - + MapWriterTask listener = new MapWriterTask() { @Override protected void execute() { @@ -596,51 +737,77 @@ public class RedissonMapCache extends RedissonMap implements RMapCac RFuture future = commandExecutor.evalWriteAsync(getName(key), codec, RedisCommands.EVAL_BOOLEAN, "local insertable = false; " - + "local value = redis.call('hget', KEYS[1], ARGV[5]); " - + "local t, val;" - + "if value == false then " - + "insertable = true; " - + "else " - + "t, val = struct.unpack('dLc0', value); " - + "local expireDate = 92233720368547758; " - + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[5]); " - + "if expireDateScore ~= false then " - + "expireDate = tonumber(expireDateScore) " - + "end; " - + "if t ~= 0 then " - + "local expireIdle = redis.call('zscore', KEYS[3], ARGV[5]); " - + "if expireIdle ~= false then " - + "expireDate = math.min(expireDate, tonumber(expireIdle)) " - + "end; " - + "end; " - + "if expireDate <= tonumber(ARGV[1]) then " - + "insertable = true; " - + "end; " - + "end; " + - - "if tonumber(ARGV[2]) > 0 then " - + "redis.call('zadd', KEYS[2], ARGV[2], ARGV[5]); " - + "else " - + "redis.call('zrem', KEYS[2], ARGV[5]); " - + "end; " - + "if tonumber(ARGV[3]) > 0 then " - + "redis.call('zadd', KEYS[3], ARGV[3], ARGV[5]); " - + "else " - + "redis.call('zrem', KEYS[3], ARGV[5]); " - + "end; " - + "local value = struct.pack('dLc0', ARGV[4], string.len(ARGV[6]), ARGV[6]); " - + "redis.call('hset', KEYS[1], ARGV[5], value); " - + "if insertable == true then " - + "local msg = struct.pack('Lc0Lc0', string.len(ARGV[5]), ARGV[5], string.len(ARGV[6]), ARGV[6]); " - + "redis.call('publish', KEYS[4], msg); " - + "return 1;" - + "else " - + "local msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[5]), ARGV[5], string.len(ARGV[6]), ARGV[6], string.len(val), val); " - + "redis.call('publish', KEYS[5], msg); " - + "return 0;" - + "end;", - Arrays.asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key), getCreatedChannelNameByKey(key), getUpdatedChannelNameByKey(key)), - System.currentTimeMillis(), ttlTimeout, maxIdleTimeout, maxIdleDelta, encodeMapKey(key), encodeMapValue(value)); + + "local value = redis.call('hget', KEYS[1], ARGV[5]); " + + "local t, val;" + + "if value == false then " + + "insertable = true; " + + "else " + + "t, val = struct.unpack('dLc0', value); " + + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[5]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if t ~= 0 then " + + "local expireIdle = redis.call('zscore', KEYS[3], ARGV[5]); " + + "if expireIdle ~= false then " + + "expireDate = math.min(expireDate, tonumber(expireIdle)) " + + "end; " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "insertable = true; " + + "end; " + + "end; " + + + "if tonumber(ARGV[2]) > 0 then " + + "redis.call('zadd', KEYS[2], ARGV[2], ARGV[5]); " + + "else " + + "redis.call('zrem', KEYS[2], ARGV[5]); " + + "end; " + + "if tonumber(ARGV[3]) > 0 then " + + "redis.call('zadd', KEYS[3], ARGV[3], ARGV[5]); " + + "else " + + "redis.call('zrem', KEYS[3], ARGV[5]); " + + "end; " + + + // last access time + "local maxSize = tonumber(ARGV[7]);" + + "if maxSize ~= 0 then" + + " local currentTime = tonumber(ARGV[1]);" + + " local lastAccessTimeSetName = KEYS[6];" + + " redis.call('zadd', lastAccessTimeSetName, currentTime, ARGV[5]);" + + " local cacheSize = tonumber(redis.call('hlen', KEYS[1]));" + + " if cacheSize >= maxSize then" + + " local lruItems = redis.call('zrange', lastAccessTimeSetName, 0, cacheSize - maxSize);" + + " for index, lruItem in ipairs(lruItems) do" + + " if lruItem then" + + " local lruItemValue = redis.call('hget', KEYS[1], lruItem);" + + " redis.call('hdel', KEYS[1], lruItem);" + + " redis.call('zrem', KEYS[2], lruItem);" + + " redis.call('zrem', KEYS[3], lruItem);" + + " redis.call('zrem', lastAccessTimeSetName, lruItem);" + + " local removedChannelName = KEYS[7];" + + " local msg = struct.pack('Lc0Lc0', string.len(lruItem), lruItem, string.len(lruItemValue), lruItemValue);" + + " redis.call('publish', removedChannelName, msg);" + + " end;" + + " end" + + " end;" + + "end;" + + + "local value = struct.pack('dLc0', ARGV[4], string.len(ARGV[6]), ARGV[6]); " + + "redis.call('hset', KEYS[1], ARGV[5], value); " + + "if insertable == true then " + + "local msg = struct.pack('Lc0Lc0', string.len(ARGV[5]), ARGV[5], string.len(ARGV[6]), ARGV[6]); " + + "redis.call('publish', KEYS[4], msg); " + + "return 1;" + + "else " + + "local msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[5]), ARGV[5], string.len(ARGV[6]), ARGV[6], string.len(val), val); " + + "redis.call('publish', KEYS[5], msg); " + + "return 0;" + + "end;", + Arrays.asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key), getCreatedChannelNameByKey(key), + getUpdatedChannelNameByKey(key), getLastAccessTimeSetNameByKey(key), getRemovedChannelNameByKey(key)), + System.currentTimeMillis(), ttlTimeout, maxIdleTimeout, maxIdleDelta, encodeMapKey(key), encodeMapValue(value), maxSize); return future; } @@ -658,7 +825,7 @@ public class RedissonMapCache extends RedissonMap implements RMapCac public RFuture putAsync(final K key, final V value, long ttl, TimeUnit ttlUnit, long maxIdleTime, TimeUnit maxIdleUnit) { checkKey(key); checkValue(value); - + if (ttl < 0) { throw new IllegalArgumentException("ttl can't be negative"); } @@ -693,7 +860,7 @@ public class RedissonMapCache extends RedissonMap implements RMapCac if (hasNoWriter()) { return future; } - + MapWriterTask listener = new MapWriterTask() { @Override protected void execute() { @@ -707,66 +874,91 @@ public class RedissonMapCache extends RedissonMap implements RMapCac long maxIdleDelta) { RFuture future = commandExecutor.evalWriteAsync(getName(key), codec, RedisCommands.EVAL_MAP_VALUE, "local insertable = false; " - + "local v = redis.call('hget', KEYS[1], ARGV[5]); " - + "if v == false then " - + "insertable = true; " - + "else " - + "local t, val = struct.unpack('dLc0', v); " - + "local expireDate = 92233720368547758; " - + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[5]); " - + "if expireDateScore ~= false then " - + "expireDate = tonumber(expireDateScore) " - + "end; " - + "if t ~= 0 then " - + "local expireIdle = redis.call('zscore', KEYS[3], ARGV[5]); " - + "if expireIdle ~= false then " - + "expireDate = math.min(expireDate, tonumber(expireIdle)) " - + "end; " - + "end; " - + "if expireDate <= tonumber(ARGV[1]) then " - + "insertable = true; " - + "end; " - + "end; " - - + "if tonumber(ARGV[2]) > 0 then " - + "redis.call('zadd', KEYS[2], ARGV[2], ARGV[5]); " - + "else " - + "redis.call('zrem', KEYS[2], ARGV[5]); " - + "end; " - + "if tonumber(ARGV[3]) > 0 then " - + "redis.call('zadd', KEYS[3], ARGV[3], ARGV[5]); " - + "else " - + "redis.call('zrem', KEYS[3], ARGV[5]); " - + "end; " - - + "local value = struct.pack('dLc0', ARGV[4], string.len(ARGV[6]), ARGV[6]); " - + "redis.call('hset', KEYS[1], ARGV[5], value); " - - + "if insertable == true then " - + "local msg = struct.pack('Lc0Lc0', string.len(ARGV[5]), ARGV[5], string.len(ARGV[6]), ARGV[6]); " - + "redis.call('publish', KEYS[4], msg); " - + "return nil;" - + "end; " - - + "local t, val = struct.unpack('dLc0', v); " - - + "local msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[5]), ARGV[5], string.len(ARGV[6]), ARGV[6], string.len(val), val); " - + "redis.call('publish', KEYS[5], msg); " - - + "return val", - Arrays.asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key), getCreatedChannelNameByKey(key), getUpdatedChannelNameByKey(key)), - System.currentTimeMillis(), ttlTimeout, maxIdleTimeout, maxIdleDelta, encodeMapKey(key), encodeMapValue(value)); + + "local v = redis.call('hget', KEYS[1], ARGV[5]); " + + "if v == false then " + + "insertable = true; " + + "else " + + "local t, val = struct.unpack('dLc0', v); " + + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[5]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if t ~= 0 then " + + "local expireIdle = redis.call('zscore', KEYS[3], ARGV[5]); " + + "if expireIdle ~= false then " + + "expireDate = math.min(expireDate, tonumber(expireIdle)) " + + "end; " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "insertable = true; " + + "end; " + + "end; " + + + "if tonumber(ARGV[2]) > 0 then " + + "redis.call('zadd', KEYS[2], ARGV[2], ARGV[5]); " + + "else " + + "redis.call('zrem', KEYS[2], ARGV[5]); " + + "end; " + + "if tonumber(ARGV[3]) > 0 then " + + "redis.call('zadd', KEYS[3], ARGV[3], ARGV[5]); " + + "else " + + "redis.call('zrem', KEYS[3], ARGV[5]); " + + "end; " + + // last access time + + "local maxSize = tonumber(ARGV[7]);" + + "if maxSize ~= 0 then" + + " local currentTime = tonumber(ARGV[1]);" + + " local lastAccessTimeSetName = KEYS[6];" + + " redis.call('zadd', lastAccessTimeSetName, currentTime, ARGV[5]);" + + " local cacheSize = tonumber(redis.call('hlen', KEYS[1]));" + + " if cacheSize >= maxSize then" + + " local lruItems = redis.call('zrange', lastAccessTimeSetName, 0, cacheSize - maxSize);" + + " for index, lruItem in ipairs(lruItems) do" + + " if lruItem then" + + " local lruItemValue = redis.call('hget', KEYS[1], lruItem);" + + " redis.call('hdel', KEYS[1], lruItem);" + + " redis.call('zrem', KEYS[2], lruItem);" + + " redis.call('zrem', KEYS[3], lruItem);" + + " redis.call('zrem', lastAccessTimeSetName, lruItem);" + + " local removedChannelName = KEYS[7];" + + " local msg = struct.pack('Lc0Lc0', string.len(lruItem), lruItem, string.len(lruItemValue), lruItemValue);" + + " redis.call('publish', removedChannelName, msg);" + + " end;" + + " end" + + " end;" + + "end;" + + + "local value = struct.pack('dLc0', ARGV[4], string.len(ARGV[6]), ARGV[6]); " + + "redis.call('hset', KEYS[1], ARGV[5], value); " + + + "if insertable == true then " + + "local msg = struct.pack('Lc0Lc0', string.len(ARGV[5]), ARGV[5], string.len(ARGV[6]), ARGV[6]); " + + "redis.call('publish', KEYS[4], msg); " + + "return nil;" + + "end; " + + + "local t, val = struct.unpack('dLc0', v); " + + + "local msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[5]), ARGV[5], string.len(ARGV[6]), ARGV[6], string.len(val), val); " + + "redis.call('publish', KEYS[5], msg); " + + + "return val", + Arrays.asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key), getCreatedChannelNameByKey(key), + getUpdatedChannelNameByKey(key), getLastAccessTimeSetNameByKey(key), getRemovedChannelNameByKey(key)), + System.currentTimeMillis(), ttlTimeout, maxIdleTimeout, maxIdleDelta, encodeMapKey(key), encodeMapValue(value), maxSize); return future; } String getTimeoutSetNameByKey(Object key) { return prefixName("redisson__timeout__set", getName(key)); } - + String getTimeoutSetName(String name) { return prefixName("redisson__timeout__set", name); } - + String getTimeoutSetName() { return prefixName("redisson__timeout__set", getName()); } @@ -774,11 +966,11 @@ public class RedissonMapCache extends RedissonMap implements RMapCac String getIdleSetNameByKey(Object key) { return prefixName("redisson__idle__set", getName(key)); } - + String getIdleSetName(String name) { return prefixName("redisson__idle__set", name); } - + String getIdleSetName() { return prefixName("redisson__idle__set", getName()); } @@ -786,7 +978,7 @@ public class RedissonMapCache extends RedissonMap implements RMapCac String getCreatedChannelNameByKey(Object key) { return prefixName("redisson_map_cache_created", getName(key)); } - + String getCreatedChannelName(String name) { return prefixName("redisson_map_cache_created", name); } @@ -836,45 +1028,55 @@ public class RedissonMapCache extends RedissonMap implements RMapCac public RFuture removeOperationAsync(K key) { return commandExecutor.evalWriteAsync(getName(key), codec, RedisCommands.EVAL_MAP_VALUE, "local value = redis.call('hget', KEYS[1], ARGV[2]); " - + "if value == false then " - + "return nil; " - + "end; " - - + "local t, val = struct.unpack('dLc0', value); " - + "local expireDate = 92233720368547758; " + - "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); " - + "if expireDateScore ~= false then " - + "expireDate = tonumber(expireDateScore) " - + "end; " - + "if t ~= 0 then " - + "local expireIdle = redis.call('zscore', KEYS[3], ARGV[2]); " - + "if expireIdle ~= false then " - + "expireDate = math.min(expireDate, tonumber(expireIdle)) " - + "end; " - + "end; " - + "if expireDate <= tonumber(ARGV[1]) then " - + "return nil; " - + "end; " - - + "redis.call('zrem', KEYS[2], ARGV[2]); " - + "redis.call('zrem', KEYS[3], ARGV[2]); " - + "redis.call('hdel', KEYS[1], ARGV[2]); " - - + "local msg = struct.pack('Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(val), val); " - + "redis.call('publish', KEYS[4], msg); " - + "return val; ", - Arrays.asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key), getRemovedChannelNameByKey(key)), - System.currentTimeMillis(), encodeMapKey(key)); + + "if value == false then " + + "return nil; " + + "end; " + + + "local t, val = struct.unpack('dLc0', value); " + + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if t ~= 0 then " + + "local expireIdle = redis.call('zscore', KEYS[3], ARGV[2]); " + + "if expireIdle ~= false then " + + "expireDate = math.min(expireDate, tonumber(expireIdle)) " + + "end; " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return nil; " + + "end; " + + + "redis.call('zrem', KEYS[2], ARGV[2]); " + + "redis.call('zrem', KEYS[3], ARGV[2]); " + + "local maxSize = tonumber(ARGV[3])" + + "if maxSize ~= 0 then" + + " redis.call('zrem', KEYS[5], ARGV[2]); " + + "end;" + + "redis.call('hdel', KEYS[1], ARGV[2]); " + + + "local msg = struct.pack('Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(val), val); " + + "redis.call('publish', KEYS[4], msg); " + + "return val; ", + Arrays.asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key), getRemovedChannelNameByKey(key), + getLastAccessTimeSetNameByKey(key)), + System.currentTimeMillis(), encodeMapKey(key), maxSize); } @Override protected RFuture> fastRemoveOperationBatchAsync(K... keys) { - List args = new ArrayList(keys.length); + List args = new ArrayList(keys.length + 1); + args.add(maxSize); for (K key : keys) { args.add(encodeMapKey(key)); } RFuture> future = commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_LIST, + "local maxSize = tonumber(table.remove(ARGV, 1));" + + "if maxSize ~= 0 then" + + " redis.call('zrem', KEYS[5], unpack(ARGV));" + + "end;" + "redis.call('zrem', KEYS[3], unpack(ARGV)); " + "redis.call('zrem', KEYS[2], unpack(ARGV)); " + "for i, key in ipairs(ARGV) do " @@ -883,84 +1085,88 @@ public class RedissonMapCache extends RedissonMap implements RMapCac + "local t, val = struct.unpack('dLc0', v); " + "local msg = struct.pack('Lc0Lc0', string.len(key), key, string.len(val), val); " + "redis.call('publish', KEYS[4], msg); " - + "end;" + + + "end;" + "end;" + - - "local result = {}; " + - "for i = 1, #ARGV, 1 do " - + "local val = redis.call('hdel', KEYS[1], ARGV[i]); " - + "table.insert(result, val); " - + "end;" - + "return result;", - Arrays.asList(getName(), getTimeoutSetName(), getIdleSetName(), getRemovedChannelName()), - args.toArray()); + + "local result = {}; " + + "for i = 1, #ARGV, 1 do " + + "local val = redis.call('hdel', KEYS[1], ARGV[i]); " + + "table.insert(result, val); " + + "end;" + + "return result;", + Arrays.asList(getName(), getTimeoutSetName(), getIdleSetName(), getRemovedChannelName(), getLastAccessTimeSetName()), + args.toArray()); return future; } - + @Override protected RFuture fastRemoveOperationAsync(K ... keys) { - List params = new ArrayList(keys.length); + List params = new ArrayList(keys.length + 1); + params.add(maxSize); for (K key : keys) { params.add(encodeMapKey(key)); } - + return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_LONG, - "redis.call('zrem', KEYS[3], unpack(ARGV)); " + - "redis.call('zrem', KEYS[2], unpack(ARGV)); " + - "for i, key in ipairs(ARGV) do " - + "local v = redis.call('hget', KEYS[1], key); " - + "if v ~= false then " - + "local t, val = struct.unpack('dLc0', v); " - + "local msg = struct.pack('Lc0Lc0', string.len(key), key, string.len(val), val); " - + "redis.call('publish', KEYS[4], msg); " - + "end;" + - "end;" + - "return redis.call('hdel', KEYS[1], unpack(ARGV)); ", - Arrays.asList(getName(), getTimeoutSetName(), getIdleSetName(), getRemovedChannelName()), + "local maxSize = tonumber(table.remove(ARGV, 1));" + + "if maxSize ~= 0 then" + + " redis.call('zrem', KEYS[5], unpack(ARGV));" + + "end;" + + "redis.call('zrem', KEYS[3], unpack(ARGV)); " + + "redis.call('zrem', KEYS[2], unpack(ARGV)); " + + "for i, key in ipairs(ARGV) do " + + "local v = redis.call('hget', KEYS[1], key); " + + "if v ~= false then " + + "local t, val = struct.unpack('dLc0', v); " + + "local msg = struct.pack('Lc0Lc0', string.len(key), key, string.len(val), val); " + + "redis.call('publish', KEYS[4], msg); " + + "end;" + + "end;" + + "return redis.call('hdel', KEYS[1], unpack(ARGV)); ", + Arrays.asList(getName(), getTimeoutSetName(), getIdleSetName(), getRemovedChannelName(), getLastAccessTimeSetName()), params.toArray()); } - @Override MapScanResult scanIterator(String name, InetSocketAddress client, long startPos) { return get(scanIteratorAsync(name, client, startPos)); } - + public RFuture> scanIteratorAsync(final String name, InetSocketAddress client, long startPos) { - RedisCommand> EVAL_HSCAN = new RedisCommand>("EVAL", + RedisCommand> EVAL_HSCAN = new RedisCommand>("EVAL", new ListMultiDecoder(new LongMultiDecoder(), new ObjectMapDecoder(new MapScanCodec(codec)), new ObjectListDecoder(codec), new MapCacheScanResultReplayDecoder()), ValueType.MAP); RFuture> f = commandExecutor.evalReadAsync(client, name, codec, EVAL_HSCAN, "local result = {}; " - + "local idleKeys = {}; " - + "local res = redis.call('hscan', KEYS[1], ARGV[2]); " - + "local currentTime = tonumber(ARGV[1]); " - + "for i, value in ipairs(res[2]) do " - + "if i % 2 == 0 then " - + "local key = res[2][i-1]; " + - "local expireDate = 92233720368547758; " + - "local expireDateScore = redis.call('zscore', KEYS[2], key); " - + "if expireDateScore ~= false then " - + "expireDate = tonumber(expireDateScore) " - + "end; " + + "local idleKeys = {}; " + + "local res = redis.call('hscan', KEYS[1], ARGV[2]); " + + "local currentTime = tonumber(ARGV[1]); " + + "for i, value in ipairs(res[2]) do " + + "if i % 2 == 0 then " + + "local key = res[2][i-1]; " + + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[2], key); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " - + "local t, val = struct.unpack('dLc0', value); " - + "if t ~= 0 then " - + "local expireIdle = redis.call('zscore', KEYS[3], key); " - + "if expireIdle ~= false then " - + "if tonumber(expireIdle) > currentTime and expireDate > currentTime then " - + "table.insert(idleKeys, key); " + + "local t, val = struct.unpack('dLc0', value); " + + "if t ~= 0 then " + + "local expireIdle = redis.call('zscore', KEYS[3], key); " + + "if expireIdle ~= false then " + + "if tonumber(expireIdle) > currentTime and expireDate > currentTime then " + + "table.insert(idleKeys, key); " + + "end; " + + "expireDate = math.min(expireDate, tonumber(expireIdle)) " + + "end; " + + "end; " + + + "if expireDate > currentTime then " + + "table.insert(result, key); " + + "table.insert(result, val); " + "end; " - + "expireDate = math.min(expireDate, tonumber(expireIdle)) " + "end; " - + "end; " + + "end;" + + "return {res[1], result, idleKeys};", Arrays.asList(name, getTimeoutSetName(name), getIdleSetName(name)), System.currentTimeMillis(), startPos); - + "if expireDate > currentTime then " - + "table.insert(result, key); " - + "table.insert(result, val); " - + "end; " - + "end; " - + "end;" - + "return {res[1], result, idleKeys};", Arrays.asList(name, getTimeoutSetName(name), getIdleSetName(name)), System.currentTimeMillis(), startPos); - f.addListener(new FutureListener>() { @Override public void operationComplete(Future> future) @@ -970,34 +1176,34 @@ public class RedissonMapCache extends RedissonMap implements RMapCac if (res.getIdleKeys().isEmpty()) { return; } - + List args = new ArrayList(res.getIdleKeys().size() + 1); args.add(System.currentTimeMillis()); args.addAll(res.getIdleKeys()); commandExecutor.evalWriteAsync(name, codec, new RedisCommand>("EVAL", new MapGetAllDecoder(args, 1), 7, ValueType.MAP_KEY, ValueType.MAP_VALUE), - "local currentTime = tonumber(table.remove(ARGV, 1)); " // index is the first parameter - + "local map = redis.call('hmget', KEYS[1], unpack(ARGV)); " - + "for i = #map, 1, -1 do " - + "local value = map[i]; " - + "if value ~= false then " - + "local key = ARGV[i]; " - + "local t, val = struct.unpack('dLc0', value); " - - + "if t ~= 0 then " - + "local expireIdle = redis.call('zscore', KEYS[2], key); " - + "if expireIdle ~= false then " - + "if tonumber(expireIdle) > currentTime then " - + "local value = struct.pack('dLc0', t, string.len(val), val); " - + "redis.call('hset', KEYS[1], key, value); " - + "redis.call('zadd', KEYS[2], t + currentTime, key); " - + "end; " - + "end; " - + "end; " - + "end; " - + "end; ", + "local currentTime = tonumber(table.remove(ARGV, 1)); " // index is the first parameter + + "local map = redis.call('hmget', KEYS[1], unpack(ARGV)); " + + "for i = #map, 1, -1 do " + + "local value = map[i]; " + + "if value ~= false then " + + "local key = ARGV[i]; " + + "local t, val = struct.unpack('dLc0', value); " + + + "if t ~= 0 then " + + "local expireIdle = redis.call('zscore', KEYS[2], key); " + + "if expireIdle ~= false then " + + "if tonumber(expireIdle) > currentTime then " + + "local value = struct.pack('dLc0', t, string.len(val), val); " + + "redis.call('hset', KEYS[1], key, value); " + + "redis.call('zadd', KEYS[2], t + currentTime, key); " + + "end; " + + "end; " + + "end; " + + "end; " + + "end; ", Arrays.asList(name, getIdleSetName(name)), args.toArray()); - + } } }); @@ -1010,104 +1216,159 @@ public class RedissonMapCache extends RedissonMap implements RMapCac protected RFuture fastPutOperationAsync(K key, V value) { return commandExecutor.evalWriteAsync(getName(key), codec, RedisCommands.EVAL_BOOLEAN, "local insertable = false; " - + "local v = redis.call('hget', KEYS[1], ARGV[2]); " - + "if v == false then " - + "insertable = true; " - + "else " - + "local t, val = struct.unpack('dLc0', v); " - + "local expireDate = 92233720368547758; " - + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); " - + "if expireDateScore ~= false then " - + "expireDate = tonumber(expireDateScore) " - + "end; " - + "if t ~= 0 then " - + "local expireIdle = redis.call('zscore', KEYS[3], ARGV[2]); " - + "if expireIdle ~= false then " - + "expireDate = math.min(expireDate, tonumber(expireIdle)) " - + "end; " - + "end; " - + "if expireDate <= tonumber(ARGV[1]) then " - + "insertable = true; " - + "end; " - + "end; " + - - "local val = struct.pack('dLc0', 0, string.len(ARGV[3]), ARGV[3]); " - + "redis.call('hset', KEYS[1], ARGV[2], val); " - - + "if insertable == true then " - + "local msg = struct.pack('Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[3]), ARGV[3]); " - + "redis.call('publish', KEYS[4], msg); " - + "return 1;" - + "else " - + "local t, val = struct.unpack('dLc0', v); " - + "local msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[3]), ARGV[3], string.len(val), val); " - + "redis.call('publish', KEYS[5], msg); " - + "return 0;" - + "end;", - Arrays.asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key), getCreatedChannelNameByKey(key), getUpdatedChannelNameByKey(key)), - System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value)); + + "local v = redis.call('hget', KEYS[1], ARGV[2]); " + + "if v == false then " + + "insertable = true; " + + "else " + + "local t, val = struct.unpack('dLc0', v); " + + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if t ~= 0 then " + + "local expireIdle = redis.call('zscore', KEYS[3], ARGV[2]); " + + "if expireIdle ~= false then " + + "expireDate = math.min(expireDate, tonumber(expireIdle)) " + + "end; " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "insertable = true; " + + "end; " + + "end; " + + + "local val = struct.pack('dLc0', 0, string.len(ARGV[3]), ARGV[3]); " + + "redis.call('hset', KEYS[1], ARGV[2], val); " + + + // last access time + "local maxSize = tonumber(ARGV[4]);" + + "if maxSize ~= 0 then" + + " local currentTime = tonumber(ARGV[1]);" + + " local lastAccessTimeSetName = KEYS[6];" + + " redis.call('zadd', lastAccessTimeSetName, currentTime, ARGV[2]);" + + " local cacheSize = tonumber(redis.call('hlen', KEYS[1]));" + + " if cacheSize >= maxSize then" + + " local lruItems = redis.call('zrange', lastAccessTimeSetName, 0, cacheSize - maxSize);" + + " for index, lruItem in ipairs(lruItems) do" + + " if lruItem then" + + " local lruItemValue = redis.call('hget', KEYS[1], lruItem);" + + " redis.call('hdel', KEYS[1], lruItem);" + + " redis.call('zrem', KEYS[2], lruItem);" + + " redis.call('zrem', KEYS[3], lruItem);" + + " redis.call('zrem', lastAccessTimeSetName, lruItem);" + + " local removedChannelName = KEYS[7];" + + " local msg = struct.pack('Lc0Lc0', string.len(lruItem), lruItem, string.len(lruItemValue), lruItemValue);" + + " redis.call('publish', removedChannelName, msg);" + + " end;" + + " end" + + " end;" + + "end;" + + + "if insertable == true then " + + "local msg = struct.pack('Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[3]), ARGV[3]); " + + "redis.call('publish', KEYS[4], msg); " + + "return 1;" + + "else " + + "local t, val = struct.unpack('dLc0', v); " + + "local msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[3]), ARGV[3], string.len(val), val); " + + "redis.call('publish', KEYS[5], msg); " + + "return 0;" + + "end;", + Arrays.asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key), getCreatedChannelNameByKey(key), + getUpdatedChannelNameByKey(key), getLastAccessTimeSetNameByKey(key), getRemovedChannelNameByKey(key)), + System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value), maxSize); } @Override protected RFuture fastPutIfAbsentOperationAsync(K key, V value) { return commandExecutor.evalWriteAsync(getName(key), codec, RedisCommands.EVAL_BOOLEAN, "local value = redis.call('hget', KEYS[1], ARGV[2]); " - + "if value == false then " - + "local val = struct.pack('dLc0', 0, string.len(ARGV[3]), ARGV[3]); " - + "redis.call('hset', KEYS[1], ARGV[2], val); " - + "local msg = struct.pack('Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[3]), ARGV[3]); " - + "redis.call('publish', KEYS[4], msg); " - + "return 1; " - + "end; " - + "local t, val = struct.unpack('dLc0', value); " - + "local expireDate = 92233720368547758; " + - "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); " - + "if expireDateScore ~= false then " - + "expireDate = tonumber(expireDateScore) " - + "end; " - + "if t ~= 0 then " - + "local expireIdle = redis.call('zscore', KEYS[3], ARGV[2]); " - + "if expireIdle ~= false then " - + "if tonumber(expireIdle) > tonumber(ARGV[1]) then " - + "local value = struct.pack('dLc0', t, string.len(val), val); " - + "redis.call('hset', KEYS[1], ARGV[2], value); " - + "redis.call('zadd', KEYS[3], t + tonumber(ARGV[1]), ARGV[2]); " - + "end; " - + "expireDate = math.min(expireDate, tonumber(expireIdle)) " - + "end; " - + "end; " - + "if expireDate > tonumber(ARGV[1]) then " - + "return 0; " - + "end; " - - + "redis.call('zrem', KEYS[2], ARGV[2]); " - + "redis.call('zrem', KEYS[3], ARGV[2]); " - + "local val = struct.pack('dLc0', 0, string.len(ARGV[3]), ARGV[3]); " - + "redis.call('hset', KEYS[1], ARGV[2], val); " - - + "local msg = struct.pack('Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[3]), ARGV[3]); " - + "redis.call('publish', KEYS[4], msg); " - + "return 1; ", - Arrays.asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key), getCreatedChannelNameByKey(key)), - System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value)); + + "local lastAccessTimeSetName = KEYS[5];" + + "local maxSize = tonumber(ARGV[4]);" + + "local currentTime = tonumber(ARGV[1]);" + + "if value == false then " + + "local val = struct.pack('dLc0', 0, string.len(ARGV[3]), ARGV[3]); " + + "redis.call('hset', KEYS[1], ARGV[2], val); " + + "local msg = struct.pack('Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[3]), ARGV[3]); " + + "redis.call('publish', KEYS[4], msg); "+ + + // last access time + + "if maxSize ~= 0 then" + + " redis.call('zadd', lastAccessTimeSetName, currentTime, ARGV[2]);" + + " local cacheSize = tonumber(redis.call('hlen', KEYS[1]));" + + " if cacheSize >= maxSize then" + + " local lruItems = redis.call('zrange', lastAccessTimeSetName, 0, cacheSize - maxSize);" + + " for index, lruItem in ipairs(lruItems) do" + + " if lruItem then" + + " local lruItemValue = redis.call('hget', KEYS[1], lruItem);" + + " redis.call('hdel', KEYS[1], lruItem);" + + " redis.call('zrem', KEYS[2], lruItem);" + + " redis.call('zrem', KEYS[3], lruItem);" + + " redis.call('zrem', lastAccessTimeSetName, lruItem);" + + " local removedChannelName = KEYS[6];" + + " local msg = struct.pack('Lc0Lc0', string.len(lruItem), lruItem, string.len(lruItemValue), lruItemValue);" + + " redis.call('publish', removedChannelName, msg);" + + " end;" + + " end" + + " end;" + + "end;" + + + "return 1; " + + "end; " + + "if maxSize ~= 0 then" + + " redis.call('zadd', lastAccessTimeSetName, currentTime, ARGV[2]);" + + "end;" + + "local t, val = struct.unpack('dLc0', value); " + + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if t ~= 0 then " + + "local expireIdle = redis.call('zscore', KEYS[3], ARGV[2]); " + + "if expireIdle ~= false then " + + "if tonumber(expireIdle) > tonumber(ARGV[1]) then " + + "local value = struct.pack('dLc0', t, string.len(val), val); " + + "redis.call('hset', KEYS[1], ARGV[2], value); " + + "redis.call('zadd', KEYS[3], t + tonumber(ARGV[1]), ARGV[2]); " + + "end; " + + "expireDate = math.min(expireDate, tonumber(expireIdle)) " + + "end; " + + "end; " + + "if expireDate > tonumber(ARGV[1]) then " + + "return 0; " + + "end; " + + + "redis.call('zrem', KEYS[2], ARGV[2]); " + + "redis.call('zrem', KEYS[3], ARGV[2]); " + + "local val = struct.pack('dLc0', 0, string.len(ARGV[3]), ARGV[3]); " + + "redis.call('hset', KEYS[1], ARGV[2], val); " + + + "local msg = struct.pack('Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[3]), ARGV[3]); " + + "redis.call('publish', KEYS[4], msg); " + + "return 1; ", + Arrays.asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key), getCreatedChannelNameByKey(key), + getLastAccessTimeSetNameByKey(key), getRemovedChannelNameByKey(key)), + System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value), maxSize); } - + @Override - public boolean fastPutIfAbsent(K key, V value, long ttl, TimeUnit ttlUnit) { - return fastPutIfAbsent(key, value, ttl, ttlUnit, 0, null); - } - + public boolean fastPutIfAbsent(K key, V value, long ttl, TimeUnit ttlUnit) { + return fastPutIfAbsent(key, value, ttl, ttlUnit, 0, null); + } + @Override public boolean fastPutIfAbsent(K key, V value, long ttl, TimeUnit ttlUnit, long maxIdleTime, TimeUnit maxIdleUnit) { - return get(fastPutIfAbsentAsync(key, value, ttl, ttlUnit, maxIdleTime, maxIdleUnit)); + return get(fastPutIfAbsentAsync(key, value, ttl, ttlUnit, maxIdleTime, maxIdleUnit)); } - + @Override - public RFuture fastPutIfAbsentAsync(final K key, final V value, long ttl, TimeUnit ttlUnit, long maxIdleTime, TimeUnit maxIdleUnit) { + public RFuture fastPutIfAbsentAsync(final K key, final V value, long ttl, TimeUnit ttlUnit, long maxIdleTime, TimeUnit maxIdleUnit) { checkKey(key); checkValue(value); - - if (ttl < 0) { + + if (ttl < 0) { throw new IllegalArgumentException("ttl can't be negative"); } if (maxIdleTime < 0) { @@ -1138,62 +1399,87 @@ public class RedissonMapCache extends RedissonMap implements RMapCac } RFuture future = commandExecutor.evalWriteAsync(getName(key), codec, RedisCommands.EVAL_BOOLEAN, - "local insertable = false; " - + "local value = redis.call('hget', KEYS[1], ARGV[5]); " - + "if value == false then " - + "insertable = true; " - + "else " - + "if insertable == false then " - + "local t, val = struct.unpack('dLc0', value); " - + "local expireDate = 92233720368547758; " - + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[5]); " - + "if expireDateScore ~= false then " - + "expireDate = tonumber(expireDateScore) " - + "end; " - + "if t ~= 0 then " - + "local expireIdle = redis.call('zscore', KEYS[3], ARGV[5]); " - + "if expireIdle ~= false then " - + "expireDate = math.min(expireDate, tonumber(expireIdle)) " - + "end; " - + "end; " - + "if expireDate <= tonumber(ARGV[1]) then " - + "insertable = true; " - + "end; " - + "end; " - + "end; " - - + "if insertable == true then " - // ttl - + "if tonumber(ARGV[2]) > 0 then " - + "redis.call('zadd', KEYS[2], ARGV[2], ARGV[5]); " - + "else " - + "redis.call('zrem', KEYS[2], ARGV[5]); " - + "end; " - - // idle - + "if tonumber(ARGV[3]) > 0 then " - + "redis.call('zadd', KEYS[3], ARGV[3], ARGV[5]); " - + "else " - + "redis.call('zrem', KEYS[3], ARGV[5]); " - + "end; " - - // value - + "local val = struct.pack('dLc0', ARGV[4], string.len(ARGV[6]), ARGV[6]); " - + "redis.call('hset', KEYS[1], ARGV[5], val); " - - + "local msg = struct.pack('Lc0Lc0', string.len(ARGV[5]), ARGV[5], string.len(ARGV[6]), ARGV[6]); " - + "redis.call('publish', KEYS[4], msg); " - - + "return 1; " - + "else " - + "return 0; " - + "end; ", - Arrays.asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key), getCreatedChannelNameByKey(key)), - System.currentTimeMillis(), ttlTimeout, maxIdleTimeout, maxIdleDelta, encodeMapKey(key), encodeMapValue(value)); + "local insertable = false; " + + "local value = redis.call('hget', KEYS[1], ARGV[5]); " + + "if value == false then " + + "insertable = true; " + + "else " + + "if insertable == false then " + + "local t, val = struct.unpack('dLc0', value); " + + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[5]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if t ~= 0 then " + + "local expireIdle = redis.call('zscore', KEYS[3], ARGV[5]); " + + "if expireIdle ~= false then " + + "expireDate = math.min(expireDate, tonumber(expireIdle)) " + + "end; " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "insertable = true; " + + "end; " + + "end; " + + "end; " + + + "if insertable == true then " + // ttl + + "if tonumber(ARGV[2]) > 0 then " + + "redis.call('zadd', KEYS[2], ARGV[2], ARGV[5]); " + + "else " + + "redis.call('zrem', KEYS[2], ARGV[5]); " + + "end; " + + // idle + + "if tonumber(ARGV[3]) > 0 then " + + "redis.call('zadd', KEYS[3], ARGV[3], ARGV[5]); " + + "else " + + "redis.call('zrem', KEYS[3], ARGV[5]); " + + "end; " + + // last access time + + "local maxSize = tonumber(ARGV[7]);" + + "if maxSize ~= 0 then" + + " local currentTime = tonumber(ARGV[1]);" + + " local lastAccessTimeSetName = KEYS[5];" + + " redis.call('zadd', lastAccessTimeSetName, currentTime, ARGV[5]);" + + " local cacheSize = tonumber(redis.call('hlen', KEYS[1]));" + + " if cacheSize >= maxSize then" + + " local lruItems = redis.call('zrange', lastAccessTimeSetName, 0, cacheSize - maxSize);" + + " for index, lruItem in ipairs(lruItems) do" + + " if lruItem then" + + " local lruItemValue = redis.call('hget', KEYS[1], lruItem);" + + " redis.call('hdel', KEYS[1], lruItem);" + + " redis.call('zrem', KEYS[2], lruItem);" + + " redis.call('zrem', KEYS[3], lruItem);" + + " redis.call('zrem', lastAccessTimeSetName, lruItem);" + + " local removedChannelName = KEYS[6];" + + " local msg = struct.pack('Lc0Lc0', string.len(lruItem), lruItem, string.len(lruItemValue), lruItemValue);" + + " redis.call('publish', removedChannelName, msg);" + + " end;" + + " end" + + " end;" + + "end;" + + // value + + "local val = struct.pack('dLc0', ARGV[4], string.len(ARGV[6]), ARGV[6]); " + + "redis.call('hset', KEYS[1], ARGV[5], val); " + + + "local msg = struct.pack('Lc0Lc0', string.len(ARGV[5]), ARGV[5], string.len(ARGV[6]), ARGV[6]); " + + "redis.call('publish', KEYS[4], msg); " + + + "return 1; " + + "else " + + "return 0; " + + "end; ", + Arrays.asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key), getCreatedChannelNameByKey(key), + getLastAccessTimeSetNameByKey(key), getRemovedChannelNameByKey(key)), + System.currentTimeMillis(), ttlTimeout, maxIdleTimeout, maxIdleDelta, encodeMapKey(key), encodeMapValue(value), maxSize); if (hasNoWriter()) { return future; } - + MapWriterTask listener = new MapWriterTask() { @Override protected void execute() { @@ -1207,70 +1493,78 @@ public class RedissonMapCache extends RedissonMap implements RMapCac return mapWriterFuture(future, listener); } + private String getLastAccessTimeSetNameByKey(Object key) { + return prefixName("redisson__last_access__set", getName(key)); + } + + private String getLastAccessTimeSetName() { + return prefixName("redisson__last_access__set", getName()); + } + @Override protected RFuture replaceOperationAsync(K key, V oldValue, V newValue) { return commandExecutor.evalWriteAsync(getName(key), codec, RedisCommands.EVAL_BOOLEAN, - "local v = redis.call('hget', KEYS[1], ARGV[2]); " - + "if v == false then " - + "return 0;" - + "end;" - + "local expireDate = 92233720368547758; " + - "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); " - + "if expireDateScore ~= false then " - + "expireDate = tonumber(expireDateScore) " - + "end; " - - + "local t, val = struct.unpack('dLc0', v); " - + "if t ~= 0 then " - + "local expireIdle = redis.call('zscore', KEYS[3], ARGV[2]); " - + "if expireIdle ~= false then " + "local v = redis.call('hget', KEYS[1], ARGV[2]); " + + "if v == false then " + + "return 0;" + + "end;" + + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + + "local t, val = struct.unpack('dLc0', v); " + + "if t ~= 0 then " + + "local expireIdle = redis.call('zscore', KEYS[3], ARGV[2]); " + + "if expireIdle ~= false then " + "expireDate = math.min(expireDate, tonumber(expireIdle)) " - + "end; " - + "end; " - + "if expireDate > tonumber(ARGV[1]) and val == ARGV[3] then " - + "local msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[4]), ARGV[4], string.len(ARGV[3]), ARGV[3]); " - + "redis.call('publish', KEYS[4], msg); " - - + "local value = struct.pack('dLc0', t, string.len(ARGV[4]), ARGV[4]); " - + "redis.call('hset', KEYS[1], ARGV[2], value); " - + "return 1; " - + "end; " - + "return 0; ", - Arrays.asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key), getUpdatedChannelNameByKey(key)), + + "end; " + + "end; " + + "if expireDate > tonumber(ARGV[1]) and val == ARGV[3] then " + + "local msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[4]), ARGV[4], string.len(ARGV[3]), ARGV[3]); " + + "redis.call('publish', KEYS[4], msg); " + + + "local value = struct.pack('dLc0', t, string.len(ARGV[4]), ARGV[4]); " + + "redis.call('hset', KEYS[1], ARGV[2], value); " + + "return 1; " + + "end; " + + "return 0; ", + Arrays.asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key), getUpdatedChannelNameByKey(key)), System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(oldValue), encodeMapValue(newValue)); } @Override protected RFuture replaceOperationAsync(K key, V value) { return commandExecutor.evalWriteAsync(getName(key), codec, RedisCommands.EVAL_MAP_VALUE, - "local value = redis.call('hget', KEYS[1], ARGV[2]); " - + "if value == false then " - + "return nil; " - + "end; " - + "local t, val = struct.unpack('dLc0', value); " - + "local expireDate = 92233720368547758; " + - "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); " - + "if expireDateScore ~= false then " - + "expireDate = tonumber(expireDateScore) " - + "end; " - + "if t ~= 0 then " - + "local expireIdle = redis.call('zscore', KEYS[3], ARGV[2]); " - + "if expireIdle ~= false then " - + "expireDate = math.min(expireDate, tonumber(expireIdle)) " - + "end; " - + "end; " - + "if expireDate <= tonumber(ARGV[1]) then " - + "return nil; " - + "end; " - - + "local value = struct.pack('dLc0', t, string.len(ARGV[3]), ARGV[3]); " - + "redis.call('hset', KEYS[1], ARGV[2], value); " - - + "local msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[3]), ARGV[3], string.len(val), val); " - + "redis.call('publish', KEYS[4], msg); " - - + "return val; ", - Arrays.asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key), getUpdatedChannelNameByKey(key)), + "local value = redis.call('hget', KEYS[1], ARGV[2]); " + + "if value == false then " + + "return nil; " + + "end; " + + "local t, val = struct.unpack('dLc0', value); " + + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if t ~= 0 then " + + "local expireIdle = redis.call('zscore', KEYS[3], ARGV[2]); " + + "if expireIdle ~= false then " + + "expireDate = math.min(expireDate, tonumber(expireIdle)) " + + "end; " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "return nil; " + + "end; " + + + "local value = struct.pack('dLc0', t, string.len(ARGV[3]), ARGV[3]); " + + "redis.call('hset', KEYS[1], ARGV[2], value); " + + + "local msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[3]), ARGV[3], string.len(val), val); " + + "redis.call('publish', KEYS[4], msg); " + + + "return val; ", + Arrays.asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key), getUpdatedChannelNameByKey(key)), System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value)); } @@ -1285,32 +1579,32 @@ public class RedissonMapCache extends RedissonMap implements RMapCac if (t.getValue() == null) { throw new NullPointerException("map value can't be null"); } - + params.add(encodeMapKey(t.getKey())); params.add(encodeMapValue(t.getValue())); } return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_VOID, - "for i, value in ipairs(ARGV) do " - + "if i % 2 == 0 then " + "for i, value in ipairs(ARGV) do " + + "if i % 2 == 0 then " + "local val = struct.pack('dLc0', 0, string.len(value), value); " + "ARGV[i] = val; " + "local key = ARGV[i-1];" - + + "local msg = struct.pack('Lc0Lc0', string.len(key), key, string.len(value), value); " + "redis.call('publish', KEYS[2], msg); " + + "end;" + "end;" - + "end;" - + "return redis.call('hmset', KEYS[1], unpack(ARGV)); ", - Arrays.asList(getName(), getCreatedChannelName()), params.toArray()); + + "return redis.call('hmset', KEYS[1], unpack(ARGV)); ", + Arrays.asList(getName(), getCreatedChannelName()), params.toArray()); } - + @Override public int addListener(final MapEntryListener listener) { if (listener == null) { throw new NullPointerException(); } - + if (listener instanceof EntryRemovedListener) { RTopic> topic = redisson.getTopic(getRemovedChannelName(), new MapCacheEventCodec(codec)); return topic.addListener(new MessageListener>() { @@ -1321,7 +1615,7 @@ public class RedissonMapCache extends RedissonMap implements RMapCac } }); } - + if (listener instanceof EntryCreatedListener) { RTopic> topic = redisson.getTopic(getCreatedChannelName(), new MapCacheEventCodec(codec)); return topic.addListener(new MessageListener>() { @@ -1332,7 +1626,7 @@ public class RedissonMapCache extends RedissonMap implements RMapCac } }); } - + if (listener instanceof EntryUpdatedListener) { RTopic> topic = redisson.getTopic(getUpdatedChannelName(), new MapCacheEventCodec(codec)); return topic.addListener(new MessageListener>() { @@ -1343,7 +1637,7 @@ public class RedissonMapCache extends RedissonMap implements RMapCac } }); } - + if (listener instanceof EntryExpiredListener) { RTopic> topic = redisson.getTopic(getExpiredChannelName(), new MapCacheEventCodec(codec)); return topic.addListener(new MessageListener>() { @@ -1354,60 +1648,70 @@ public class RedissonMapCache extends RedissonMap implements RMapCac } }); } - + throw new IllegalArgumentException("Wrong listener type " + listener.getClass()); } - + @Override public void removeListener(int listenerId) { RTopic> removedTopic = redisson.getTopic(getRemovedChannelName(), new MapCacheEventCodec(codec)); removedTopic.removeListener(listenerId); - + RTopic> createdTopic = redisson.getTopic(getCreatedChannelName(), new MapCacheEventCodec(codec)); createdTopic.removeListener(listenerId); - + RTopic> updatedTopic = redisson.getTopic(getUpdatedChannelName(), new MapCacheEventCodec(codec)); updatedTopic.removeListener(listenerId); - + RTopic> expiredTopic = redisson.getTopic(getExpiredChannelName(), new MapCacheEventCodec(codec)); expiredTopic.removeListener(listenerId); } @Override public RFuture deleteAsync() { - return commandExecutor.writeAsync(getName(), RedisCommands.DEL_OBJECTS, getName(), getTimeoutSetName(), getIdleSetName()); + return commandExecutor.writeAsync(getName(), RedisCommands.DEL_OBJECTS, getName(), getTimeoutSetName(), getIdleSetName(), getLastAccessTimeSetName()); } @Override public RFuture expireAsync(long timeToLive, TimeUnit timeUnit) { return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, - "redis.call('zadd', KEYS[2], 92233720368547758, 'redisson__expiretag');" + - "redis.call('pexpire', KEYS[2], ARGV[1]); " + - "redis.call('zadd', KEYS[3], 92233720368547758, 'redisson__expiretag');" + - "redis.call('pexpire', KEYS[3], ARGV[1]); " + - "return redis.call('pexpire', KEYS[1], ARGV[1]); ", - Arrays.asList(getName(), getTimeoutSetName(), getIdleSetName()), timeUnit.toMillis(timeToLive)); + "local maxSize = tonumber(ARGV[2]);" + + "if maxSize ~= 0 then" + + " redis.call('pexpire', KEYS[4], ARGV[1]);" + + "end;" + + "redis.call('zadd', KEYS[2], 92233720368547758, 'redisson__expiretag');" + + "redis.call('pexpire', KEYS[2], ARGV[1]); " + + "redis.call('zadd', KEYS[3], 92233720368547758, 'redisson__expiretag');" + + "redis.call('pexpire', KEYS[3], ARGV[1]); " + + "return redis.call('pexpire', KEYS[1], ARGV[1]); ", + Arrays.asList(getName(), getTimeoutSetName(), getIdleSetName(), getLastAccessTimeSetName()), + timeUnit.toMillis(timeToLive), maxSize); } @Override public RFuture expireAtAsync(long timestamp) { return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, - "redis.call('zadd', KEYS[2], 92233720368547758, 'redisson__expiretag');" + - "redis.call('pexpireat', KEYS[2], ARGV[1]); " + - "redis.call('zadd', KEYS[3], 92233720368547758, 'redisson__expiretag');" + - "redis.call('pexpire', KEYS[3], ARGV[1]); " + - "return redis.call('pexpireat', KEYS[1], ARGV[1]); ", - Arrays.asList(getName(), getTimeoutSetName(), getIdleSetName()), timestamp); + "local maxSize = tonumber(ARGV[2]);" + + "if maxSize ~= 0 then" + + " redis.call('pexpire', KEYS[4], ARGV[1]);" + + "end;" + + "redis.call('zadd', KEYS[2], 92233720368547758, 'redisson__expiretag');" + + "redis.call('pexpireat', KEYS[2], ARGV[1]); " + + "redis.call('zadd', KEYS[3], 92233720368547758, 'redisson__expiretag');" + + "redis.call('pexpire', KEYS[3], ARGV[1]); " + + "return redis.call('pexpireat', KEYS[1], ARGV[1]); ", + Arrays.asList(getName(), getTimeoutSetName(), getIdleSetName(), getLastAccessTimeSetName()), + timestamp, maxSize); } @Override public RFuture clearExpireAsync() { return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, - "redis.call('zrem', KEYS[2], 'redisson__expiretag'); " + - "redis.call('persist', KEYS[2]); " + - "redis.call('zrem', KEYS[3], 'redisson__expiretag'); " + - "redis.call('persist', KEYS[3]); " + - "return redis.call('persist', KEYS[1]); ", + "redis.call('zrem', KEYS[2], 'redisson__expiretag'); " + + "redis.call('persist', KEYS[2]); " + + "redis.call('zrem', KEYS[3], 'redisson__expiretag'); " + + "redis.call('persist', KEYS[3]); " + + "return redis.call('persist', KEYS[1]); ", Arrays.asList(getName(), getTimeoutSetName(), getIdleSetName())); } @@ -1415,36 +1719,41 @@ public class RedissonMapCache extends RedissonMap implements RMapCac public RFuture> readAllKeySetAsync() { return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_MAP_KEY_SET, "local s = redis.call('hgetall', KEYS[1]); " - + "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 " + + "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 " + "local value = struct.pack('dLc0', t, string.len(val), val); " + "redis.call('hset', KEYS[1], key, value); " + "redis.call('zadd', KEYS[3], t + tonumber(ARGV[1]), key); " + + "local maxSize = tonumber(ARGV[2]);" + + "if maxSize ~= 0 then" + + " redis.call('zadd', KEYS[4], tonumber(ARGV[1]), key);" + + "end;" + "end; " + "expireDate = math.min(expireDate, tonumber(expireIdle)) " + + "end; " + + "end; " + + "if expireDate > tonumber(ARGV[1]) then " + + "table.insert(result, key); " + + "end; " + "end; " - + "end; " - + "if expireDate > tonumber(ARGV[1]) then " - + "table.insert(result, key); " - + "end; " - + "end; " - + "end;" + - "return result;", - Arrays.asList(getName(), getTimeoutSetName(), getIdleSetName()), System.currentTimeMillis()); + + "end;" + + "return result;", + Arrays.asList(getName(), getTimeoutSetName(), getIdleSetName(), getLastAccessTimeSetName()), + System.currentTimeMillis(), maxSize); } - + @Override public RFuture>> readAllEntrySetAsync() { return readAll(RedisCommands.EVAL_MAP_ENTRY); @@ -1453,35 +1762,40 @@ public class RedissonMapCache extends RedissonMap implements RMapCac private RFuture readAll(RedisCommand evalCommandType) { return commandExecutor.evalWriteAsync(getName(), codec, evalCommandType, "local s = redis.call('hgetall', KEYS[1]); " - + "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 " - + "local value = struct.pack('dLc0', t, string.len(val), val); " - + "redis.call('hset', KEYS[1], key, value); " - + "redis.call('zadd', KEYS[3], t + tonumber(ARGV[1]), key); " + + "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 " + + "local value = struct.pack('dLc0', t, string.len(val), val); " + + "redis.call('hset', KEYS[1], key, value); " + + "redis.call('zadd', KEYS[3], t + tonumber(ARGV[1]), key); " + + "local maxSize = tonumber(ARGV[2]);" + + "if maxSize ~= 0 then" + + " redis.call('zadd', KEYS[4], tonumber(ARGV[1]), key);" + + "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; " - + "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()), System.currentTimeMillis()); + + "end;" + + "return result;", + Arrays.asList(getName(), getTimeoutSetName(), getIdleSetName(), getLastAccessTimeSetName()), + System.currentTimeMillis(), maxSize); } @Override @@ -1494,23 +1808,27 @@ public class RedissonMapCache extends RedissonMap implements RMapCac public RFuture> readAllValuesAsync() { return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_MAP_VALUE_LIST, "local s = redis.call('hgetall', KEYS[1]); " - + "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 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 " + "local value = struct.pack('dLc0', t, string.len(val), val); " + "redis.call('hset', KEYS[1], key, value); " + "redis.call('zadd', KEYS[3], t + tonumber(ARGV[1]), key); " + + "local maxSize = tonumber(ARGV[2]);" + + "if maxSize ~= 0 then" + + " redis.call('zadd', KEYS[4], tonumber(ARGV[1]), key);" + + "end;" + "end; " + "expireDate = math.min(expireDate, tonumber(expireIdle)) " + "end; " @@ -1518,9 +1836,10 @@ public class RedissonMapCache extends RedissonMap implements RMapCac + "if expireDate > tonumber(ARGV[1]) then " + "table.insert(result, val); " + "end; " - + "end; " - + "end;" + - "return result;", - Arrays.asList(getName(), getTimeoutSetName(), getIdleSetName()), System.currentTimeMillis()); + + "end; " + + "end;" + + "return result;", + Arrays.asList(getName(), getTimeoutSetName(), getIdleSetName(), getLastAccessTimeSetName()), + System.currentTimeMillis(), maxSize); } } diff --git a/redisson/src/main/java/org/redisson/api/MapOptions.java b/redisson/src/main/java/org/redisson/api/MapOptions.java index 0f49a3fcb..2b45a69ba 100644 --- a/redisson/src/main/java/org/redisson/api/MapOptions.java +++ b/redisson/src/main/java/org/redisson/api/MapOptions.java @@ -49,6 +49,7 @@ public class MapOptions { private MapWriter writer; private WriteMode writeMode = WriteMode.WRITE_THROUGH; private int writeBehindThreads = 1; + private int maxSize = 0; protected MapOptions() { } @@ -134,5 +135,20 @@ public class MapOptions { public MapLoader getLoader() { return loader; } - + + /** + * Sets max size of the map. + *

+ * Currently only RedissonMapCache is supported. + * + * @param maxSize - max size + * @return MapOptions instance + */ + public MapOptions maxSize(int maxSize) { + this.maxSize = maxSize; + return this; + } + public int getMaxSize() { + return maxSize; + } } diff --git a/redisson/src/test/java/org/redisson/RedissonMapCacheTest.java b/redisson/src/test/java/org/redisson/RedissonMapCacheTest.java index f8bf50aeb..af6fdc76a 100644 --- a/redisson/src/test/java/org/redisson/RedissonMapCacheTest.java +++ b/redisson/src/test/java/org/redisson/RedissonMapCacheTest.java @@ -213,6 +213,31 @@ public class RedissonMapCacheTest extends BaseMapTest { expected.put("3", "33"); assertThat(store).isEqualTo(expected); } + + @Test + public void testFastPutMaxSize() { + final int maxSize = 2; + Map store = new LinkedHashMap() { + @Override + protected boolean removeEldestEntry(Entry eldest) { + return size() > maxSize; + } + }; + MapOptions options = MapOptions.defaults().writer(createMapWriter(store)); + options.maxSize(maxSize); + RMapCache map = redisson.getMapCache("test", options); + + map.fastPut("1", "11", 10, TimeUnit.SECONDS); + map.fastPut("2", "22", 10, TimeUnit.SECONDS); + map.fastPut("3", "33", 10, TimeUnit.SECONDS); + + assertThat(map.size()).isEqualTo(maxSize); + + Map expected = new HashMap<>(); + expected.put("2", "22"); + expected.put("3", "33"); + assertThat(store).isEqualTo(expected); + } @Test public void testOrdering() { From 8e906ba2252d811c9771d8170024acfd89054665 Mon Sep 17 00:00:00 2001 From: Zun Date: Tue, 29 Aug 2017 14:38:01 +0300 Subject: [PATCH 2/2] fix bugs and lua-script format --- .../java/org/redisson/RedissonMapCache.java | 325 +++++++++--------- 1 file changed, 158 insertions(+), 167 deletions(-) diff --git a/redisson/src/main/java/org/redisson/RedissonMapCache.java b/redisson/src/main/java/org/redisson/RedissonMapCache.java index cd419fe36..626f91637 100644 --- a/redisson/src/main/java/org/redisson/RedissonMapCache.java +++ b/redisson/src/main/java/org/redisson/RedissonMapCache.java @@ -198,42 +198,42 @@ public class RedissonMapCache extends RedissonMap implements RMapCac args.addAll(keys); return commandExecutor.evalWriteAsync(getName(), codec, new RedisCommand>("EVAL", new MapGetAllDecoder(args, 2), 7, ValueType.MAP_KEY, ValueType.MAP_VALUE), - "local expireHead = redis.call('zrange', KEYS[2], 0, 0, 'withscores');" + - "local currentTime = tonumber(table.remove(ARGV, 1));" + // index is the first parameter - "local maxSize = tonumber(table.remove(ARGV, 1));" + // index is the first parameter - "local hasExpire = #expireHead == 2 and tonumber(expireHead[2]) <= currentTime;" + - "local map = redis.call('hmget', KEYS[1], unpack(ARGV));" + - "for i = #map, 1, -1 do" + - " local value = map[i];" + - " if value ~= false then" + - " local key = ARGV[i];" + - " local t, val = struct.unpack('dLc0', value);" + - " map[i] = val;" + - " if maxSize ~= 0 then" + - " redis.call('zadd', KEYS[4], currentTime, key);" + - " end;" + - " if hasExpire then" + - " local expireDate = redis.call('zscore', KEYS[2], key);" + - " if expireDate ~= false and tonumber(expireDate) <= currentTime then" + - " map[i] = false;" + - " end;" + - " end;" + - " if t ~= 0 then" + - " local expireIdle = redis.call('zscore', KEYS[3], key);" + - " if expireIdle ~= false then" + - " if tonumber(expireIdle) > currentTime then" + - " local value = struct.pack('dLc0', t, string.len(val), val);" + - " redis.call('hset', KEYS[1], key, value);" + - " redis.call('zadd', KEYS[3], t + currentTime, key);" + - " else" + - " map[i] = false;" + - " end;" + - " end;" + - " end;" + - " end;" + - "end;" + - "return map;", - Arrays.asList(getName(), getTimeoutSetName(), getIdleSetName()), args.toArray()); + "local expireHead = redis.call('zrange', KEYS[2], 0, 0, 'withscores');" + + "local currentTime = tonumber(table.remove(ARGV, 1));" + // index is the first parameter + "local maxSize = tonumber(table.remove(ARGV, 1));" + // index is the first parameter + "local hasExpire = #expireHead == 2 and tonumber(expireHead[2]) <= currentTime;" + + "local map = redis.call('hmget', KEYS[1], unpack(ARGV));" + + "for i = #map, 1, -1 do" + + " local value = map[i];" + + " if value ~= false then" + + " local key = ARGV[i];" + + " local t, val = struct.unpack('dLc0', value);" + + " map[i] = val;" + + " if maxSize ~= 0 then" + + " redis.call('zadd', KEYS[4], currentTime, key);" + + " end;" + + " if hasExpire then" + + " local expireDate = redis.call('zscore', KEYS[2], key);" + + " if expireDate ~= false and tonumber(expireDate) <= currentTime then" + + " map[i] = false;" + + " end;" + + " end;" + + " if t ~= 0 then" + + " local expireIdle = redis.call('zscore', KEYS[3], key);" + + " if expireIdle ~= false then" + + " if tonumber(expireIdle) > currentTime then" + + " local value = struct.pack('dLc0', t, string.len(val), val);" + + " redis.call('hset', KEYS[1], key, value);" + + " redis.call('zadd', KEYS[3], t + currentTime, key);" + + " else" + + " map[i] = false;" + + " end;" + + " end;" + + " end;" + + " end;" + + "end;" + + "return map;", + Arrays.asList(getName(), getTimeoutSetName(), getIdleSetName(), getLastAccessTimeSetName()), args.toArray()); } @Override @@ -362,7 +362,7 @@ public class RedissonMapCache extends RedissonMap implements RMapCac + "return val;" + "end; ", Arrays.asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key), getCreatedChannelNameByKey(key), - getLastAccessTimeSetName(), getRemovedChannelName()), + getLastAccessTimeSetNameByKey(key), getRemovedChannelNameByKey(key)), System.currentTimeMillis(), ttlTimeout, maxIdleTimeout, maxIdleDelta, encodeMapKey(key), encodeMapValue(value), maxSize); if (hasNoWriter()) { return future; @@ -420,7 +420,7 @@ public class RedissonMapCache extends RedissonMap implements RMapCac + "return 0; " + "end", Arrays.asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key), getRemovedChannelNameByKey(key), - getLastAccessTimeSetName()), + getLastAccessTimeSetNameByKey(key)), System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value), maxSize); } @@ -1399,80 +1399,74 @@ public class RedissonMapCache extends RedissonMap implements RMapCac } RFuture future = commandExecutor.evalWriteAsync(getName(key), codec, RedisCommands.EVAL_BOOLEAN, - "local insertable = false; " - + "local value = redis.call('hget', KEYS[1], ARGV[5]); " - + "if value == false then " - + "insertable = true; " - + "else " - + "if insertable == false then " - + "local t, val = struct.unpack('dLc0', value); " - + "local expireDate = 92233720368547758; " - + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[5]); " - + "if expireDateScore ~= false then " - + "expireDate = tonumber(expireDateScore) " - + "end; " - + "if t ~= 0 then " - + "local expireIdle = redis.call('zscore', KEYS[3], ARGV[5]); " - + "if expireIdle ~= false then " - + "expireDate = math.min(expireDate, tonumber(expireIdle)) " - + "end; " - + "end; " - + "if expireDate <= tonumber(ARGV[1]) then " - + "insertable = true; " - + "end; " - + "end; " - + "end; " - - + "if insertable == true then " - // ttl - + "if tonumber(ARGV[2]) > 0 then " - + "redis.call('zadd', KEYS[2], ARGV[2], ARGV[5]); " - + "else " - + "redis.call('zrem', KEYS[2], ARGV[5]); " - + "end; " - - // idle - + "if tonumber(ARGV[3]) > 0 then " - + "redis.call('zadd', KEYS[3], ARGV[3], ARGV[5]); " - + "else " - + "redis.call('zrem', KEYS[3], ARGV[5]); " - + "end; " - - // last access time - + "local maxSize = tonumber(ARGV[7]);" + - "if maxSize ~= 0 then" + - " local currentTime = tonumber(ARGV[1]);" + - " local lastAccessTimeSetName = KEYS[5];" + - " redis.call('zadd', lastAccessTimeSetName, currentTime, ARGV[5]);" + - " local cacheSize = tonumber(redis.call('hlen', KEYS[1]));" + - " if cacheSize >= maxSize then" + - " local lruItems = redis.call('zrange', lastAccessTimeSetName, 0, cacheSize - maxSize);" + - " for index, lruItem in ipairs(lruItems) do" + - " if lruItem then" + - " local lruItemValue = redis.call('hget', KEYS[1], lruItem);" + - " redis.call('hdel', KEYS[1], lruItem);" + - " redis.call('zrem', KEYS[2], lruItem);" + - " redis.call('zrem', KEYS[3], lruItem);" + - " redis.call('zrem', lastAccessTimeSetName, lruItem);" + - " local removedChannelName = KEYS[6];" + - " local msg = struct.pack('Lc0Lc0', string.len(lruItem), lruItem, string.len(lruItemValue), lruItemValue);" + - " redis.call('publish', removedChannelName, msg);" + - " end;" + - " end" + - " end;" + - "end;" - - // value - + "local val = struct.pack('dLc0', ARGV[4], string.len(ARGV[6]), ARGV[6]); " - + "redis.call('hset', KEYS[1], ARGV[5], val); " - - + "local msg = struct.pack('Lc0Lc0', string.len(ARGV[5]), ARGV[5], string.len(ARGV[6]), ARGV[6]); " - + "redis.call('publish', KEYS[4], msg); " - - + "return 1; " - + "else " - + "return 0; " - + "end; ", + "local insertable = false;" + + "local value = redis.call('hget', KEYS[1], ARGV[5]);" + + "if value == false then" + + " insertable = true;" + + "else" + + " if insertable == false then" + + " local t, val = struct.unpack('dLc0', value);" + + " local expireDate = 92233720368547758;" + + " local expireDateScore = redis.call('zscore', KEYS[2], ARGV[5]);" + + " if expireDateScore ~= false then" + + " expireDate = tonumber(expireDateScore)" + + " end;" + + " if t ~= 0 then" + + " local expireIdle = redis.call('zscore', KEYS[3], ARGV[5]);" + + " if expireIdle ~= false then" + + " expireDate = math.min(expireDate, tonumber(expireIdle))" + + " end;" + + " end;" + + " if expireDate <= tonumber(ARGV[1]) then" + + " insertable = true;" + + " end;" + + " end;" + + "end;" + + "if insertable == true then" + + " -- ttl" + + " if tonumber(ARGV[2]) > 0 then" + + " redis.call('zadd', KEYS[2], ARGV[2], ARGV[5]);" + + " else" + + " redis.call('zrem', KEYS[2], ARGV[5]);" + + " end;" + + " -- idle" + + " if tonumber(ARGV[3]) > 0 then" + + " redis.call('zadd', KEYS[3], ARGV[3], ARGV[5]);" + + " else" + + " redis.call('zrem', KEYS[3], ARGV[5]);" + + " end;" + + " -- last access time" + + " local maxSize = tonumber(ARGV[7]);" + + " if maxSize ~= 0 then" + + " local currentTime = tonumber(ARGV[1]);" + + " local lastAccessTimeSetName = KEYS[5];" + + " redis.call('zadd', lastAccessTimeSetName, currentTime, ARGV[5]);" + + " local cacheSize = tonumber(redis.call('hlen', KEYS[1]));" + + " if cacheSize >= maxSize then" + + " local lruItems = redis.call('zrange', lastAccessTimeSetName, 0, cacheSize - maxSize);" + + " for index, lruItem in ipairs(lruItems) do" + + " if lruItem then" + + " local lruItemValue = redis.call('hget', KEYS[1], lruItem);" + + " redis.call('hdel', KEYS[1], lruItem);" + + " redis.call('zrem', KEYS[2], lruItem);" + + " redis.call('zrem', KEYS[3], lruItem);" + + " redis.call('zrem', lastAccessTimeSetName, lruItem);" + + " local removedChannelName = KEYS[6];" + + " local msg = struct.pack('Lc0Lc0', string.len(lruItem), lruItem, string.len(lruItemValue), lruItemValue);" + + " redis.call('publish', removedChannelName, msg);" + + " end;" + + " end" + + " end;" + + " end;" + + " -- value" + + " local val = struct.pack('dLc0', ARGV[4], string.len(ARGV[6]), ARGV[6]);" + + " redis.call('hset', KEYS[1], ARGV[5], val);" + + " local msg = struct.pack('Lc0Lc0', string.len(ARGV[5]), ARGV[5], string.len(ARGV[6]), ARGV[6]);" + + " redis.call('publish', KEYS[4], msg);" + + " return 1;" + + "else" + + " return 0;" + + "end;", Arrays.asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key), getCreatedChannelNameByKey(key), getLastAccessTimeSetNameByKey(key), getRemovedChannelNameByKey(key)), System.currentTimeMillis(), ttlTimeout, maxIdleTimeout, maxIdleDelta, encodeMapKey(key), encodeMapValue(value), maxSize); @@ -1504,66 +1498,63 @@ public class RedissonMapCache extends RedissonMap implements RMapCac @Override protected RFuture replaceOperationAsync(K key, V oldValue, V newValue) { return commandExecutor.evalWriteAsync(getName(key), codec, RedisCommands.EVAL_BOOLEAN, - "local v = redis.call('hget', KEYS[1], ARGV[2]); " - + "if v == false then " - + "return 0;" - + "end;" - + "local expireDate = 92233720368547758; " + - "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); " - + "if expireDateScore ~= false then " - + "expireDate = tonumber(expireDateScore) " - + "end; " - - + "local t, val = struct.unpack('dLc0', v); " - + "if t ~= 0 then " - + "local expireIdle = redis.call('zscore', KEYS[3], ARGV[2]); " - + "if expireIdle ~= false then " - + "expireDate = math.min(expireDate, tonumber(expireIdle)) " - + "end; " - + "end; " - + "if expireDate > tonumber(ARGV[1]) and val == ARGV[3] then " - + "local msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[4]), ARGV[4], string.len(ARGV[3]), ARGV[3]); " - + "redis.call('publish', KEYS[4], msg); " - - + "local value = struct.pack('dLc0', t, string.len(ARGV[4]), ARGV[4]); " - + "redis.call('hset', KEYS[1], ARGV[2], value); " - + "return 1; " - + "end; " - + "return 0; ", - Arrays.asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key), getUpdatedChannelNameByKey(key)), - System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(oldValue), encodeMapValue(newValue)); + "local v = redis.call('hget', KEYS[1], ARGV[2]);" + + "if v == false then" + + " return 0;" + + "end;" + + "local expireDate = 92233720368547758;" + + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]);" + + "if expireDateScore ~= false then" + + " expireDate = tonumber(expireDateScore)" + + "end;" + + "" + + "local t, val = struct.unpack('dLc0', v);" + + "if t ~= 0 then" + + " local expireIdle = redis.call('zscore', KEYS[3], ARGV[2]);" + + " if expireIdle ~= false then" + + " expireDate = math.min(expireDate, tonumber(expireIdle))" + + " end;" + + "end;" + + "if expireDate > tonumber(ARGV[1]) and val == ARGV[3] then" + + " local msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[4]), ARGV[4], string.len(ARGV[3]), ARGV[3]);" + + " redis.call('publish', KEYS[4], msg);" + + "" + + " local value = struct.pack('dLc0', t, string.len(ARGV[4]), ARGV[4]);" + + " redis.call('hset', KEYS[1], ARGV[2], value);" + + " return 1;" + + "end;" + + "return 0;", + Arrays.asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key), getUpdatedChannelNameByKey(key)), + System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(oldValue), encodeMapValue(newValue)); } @Override protected RFuture replaceOperationAsync(K key, V value) { return commandExecutor.evalWriteAsync(getName(key), codec, RedisCommands.EVAL_MAP_VALUE, - "local value = redis.call('hget', KEYS[1], ARGV[2]); " - + "if value == false then " - + "return nil; " - + "end; " - + "local t, val = struct.unpack('dLc0', value); " - + "local expireDate = 92233720368547758; " + - "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); " - + "if expireDateScore ~= false then " - + "expireDate = tonumber(expireDateScore) " - + "end; " - + "if t ~= 0 then " - + "local expireIdle = redis.call('zscore', KEYS[3], ARGV[2]); " - + "if expireIdle ~= false then " - + "expireDate = math.min(expireDate, tonumber(expireIdle)) " - + "end; " - + "end; " - + "if expireDate <= tonumber(ARGV[1]) then " - + "return nil; " - + "end; " - - + "local value = struct.pack('dLc0', t, string.len(ARGV[3]), ARGV[3]); " - + "redis.call('hset', KEYS[1], ARGV[2], value); " - - + "local msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[3]), ARGV[3], string.len(val), val); " - + "redis.call('publish', KEYS[4], msg); " - - + "return val; ", + "local value = redis.call('hget', KEYS[1], ARGV[2]);" + + "if value == false then" + + " return nil;" + + "end;" + + "local t, val = struct.unpack('dLc0', value);" + + "local expireDate = 92233720368547758;" + + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]);" + + "if expireDateScore ~= false then" + + " expireDate = tonumber(expireDateScore)" + + "end;" + + "if t ~= 0 then" + + " local expireIdle = redis.call('zscore', KEYS[3], ARGV[2]);" + + " if expireIdle ~= false then" + + " expireDate = math.min(expireDate, tonumber(expireIdle))" + + " end;" + + "end;" + + "if expireDate <= tonumber(ARGV[1]) then" + + " return nil;" + + "end;" + + "local value = struct.pack('dLc0', t, string.len(ARGV[3]), ARGV[3]);" + + "redis.call('hset', KEYS[1], ARGV[2], value);" + + "local msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[3]), ARGV[3], string.len(val), val);" + + "redis.call('publish', KEYS[4], msg);" + + "return val;", Arrays.asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key), getUpdatedChannelNameByKey(key)), System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value));