/** * Copyright 2018 Nikita Koksharov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.fr.third.org.redisson; import java.math.BigDecimal; 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 com.fr.third.org.redisson.api.MapOptions; import com.fr.third.org.redisson.api.RFuture; import com.fr.third.org.redisson.api.RMapCache; import com.fr.third.org.redisson.api.RTopic; import com.fr.third.org.redisson.api.RedissonClient; import com.fr.third.org.redisson.api.listener.MessageListener; import com.fr.third.org.redisson.api.map.event.EntryCreatedListener; import com.fr.third.org.redisson.api.map.event.EntryEvent; import com.fr.third.org.redisson.api.map.event.EntryExpiredListener; import com.fr.third.org.redisson.api.map.event.EntryRemovedListener; import com.fr.third.org.redisson.api.map.event.EntryUpdatedListener; import com.fr.third.org.redisson.api.map.event.MapEntryListener; import com.fr.third.org.redisson.client.RedisClient; import com.fr.third.org.redisson.client.codec.Codec; import com.fr.third.org.redisson.client.codec.LongCodec; import com.fr.third.org.redisson.client.codec.MapScanCodec; import com.fr.third.org.redisson.client.codec.StringCodec; import com.fr.third.org.redisson.client.protocol.RedisCommand; import com.fr.third.org.redisson.client.protocol.RedisCommands; import com.fr.third.org.redisson.client.protocol.convertor.NumberConvertor; import com.fr.third.org.redisson.client.protocol.decoder.ListMultiDecoder; import com.fr.third.org.redisson.client.protocol.decoder.LongMultiDecoder; import com.fr.third.org.redisson.client.protocol.decoder.MapCacheScanResult; import com.fr.third.org.redisson.client.protocol.decoder.MapCacheScanResultReplayDecoder; import com.fr.third.org.redisson.client.protocol.decoder.MapScanResult; import com.fr.third.org.redisson.client.protocol.decoder.ObjectListDecoder; import com.fr.third.org.redisson.client.protocol.decoder.ObjectMapDecoder; import com.fr.third.org.redisson.client.protocol.decoder.ScanObjectEntry; import com.fr.third.org.redisson.codec.MapCacheEventCodec; import com.fr.third.org.redisson.api.MapOptions; import com.fr.third.org.redisson.api.RFuture; import com.fr.third.org.redisson.api.RMapCache; import com.fr.third.org.redisson.api.RTopic; import com.fr.third.org.redisson.api.RedissonClient; import com.fr.third.org.redisson.api.listener.MessageListener; import com.fr.third.org.redisson.api.map.event.EntryCreatedListener; import com.fr.third.org.redisson.api.map.event.EntryEvent; import com.fr.third.org.redisson.api.map.event.EntryExpiredListener; import com.fr.third.org.redisson.api.map.event.EntryRemovedListener; import com.fr.third.org.redisson.api.map.event.EntryUpdatedListener; import com.fr.third.org.redisson.api.map.event.MapEntryListener; import com.fr.third.org.redisson.client.RedisClient; import com.fr.third.org.redisson.client.codec.Codec; import com.fr.third.org.redisson.client.codec.LongCodec; import com.fr.third.org.redisson.client.codec.MapScanCodec; import com.fr.third.org.redisson.client.codec.StringCodec; import com.fr.third.org.redisson.client.protocol.RedisCommand; import com.fr.third.org.redisson.client.protocol.RedisCommand.ValueType; import com.fr.third.org.redisson.client.protocol.RedisCommands; import com.fr.third.org.redisson.client.protocol.convertor.NumberConvertor; import com.fr.third.org.redisson.client.protocol.decoder.ListMultiDecoder; import com.fr.third.org.redisson.client.protocol.decoder.LongMultiDecoder; import com.fr.third.org.redisson.client.protocol.decoder.MapCacheScanResult; import com.fr.third.org.redisson.client.protocol.decoder.MapCacheScanResultReplayDecoder; import com.fr.third.org.redisson.client.protocol.decoder.MapScanResult; import com.fr.third.org.redisson.client.protocol.decoder.ObjectListDecoder; import com.fr.third.org.redisson.client.protocol.decoder.ObjectMapDecoder; import com.fr.third.org.redisson.client.protocol.decoder.ScanObjectEntry; import com.fr.third.org.redisson.codec.MapCacheEventCodec; import com.fr.third.org.redisson.command.CommandAsyncExecutor; import com.fr.third.org.redisson.connection.decoder.MapGetAllDecoder; import com.fr.third.org.redisson.eviction.EvictionScheduler; import io.netty.buffer.ByteBuf; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.FutureListener; /** *

Map-based cache with ability to set TTL for each entry via * {@link #put(Object, Object, long, TimeUnit)} or {@link #putIfAbsent(Object, Object, long, TimeUnit)} methods. * And therefore has an complex lua-scripts inside.

* *

Current redis implementation doesnt have map entry eviction functionality. * Thus entries are checked for TTL expiration during any key/value/entry read operation. * If key/value/entry expired then it doesn't returns and clean task runs asynchronous. * Clean task deletes removes 100 expired entries at once. * In addition there is {@link org.redisson.eviction.EvictionScheduler}. This scheduler * deletes expired entries in time interval between 5 seconds to 2 hours.

* *

If eviction is not required then it's better to use {@link RedissonMap} object.

* * @author Nikita Koksharov * * @param key * @param value */ public class RedissonMapCache extends RedissonMap implements RMapCache { public RedissonMapCache(EvictionScheduler evictionScheduler, CommandAsyncExecutor commandExecutor, String name, RedissonClient redisson, MapOptions options) { super(commandExecutor, name, redisson, options); if (evictionScheduler != null) { evictionScheduler.schedule(getName(), getTimeoutSetName(), getIdleSetName(), getExpiredChannelName(), getLastAccessTimeSetName()); } } public RedissonMapCache(Codec codec, EvictionScheduler evictionScheduler, CommandAsyncExecutor commandExecutor, String name, RedissonClient redisson, MapOptions options) { super(codec, commandExecutor, name, redisson, options); if (evictionScheduler != null) { evictionScheduler.schedule(getName(), getTimeoutSetName(), getIdleSetName(), getExpiredChannelName(), getLastAccessTimeSetName()); } } @Override public boolean trySetMaxSize(int maxSize) { return get(trySetMaxSizeAsync(maxSize)); } @Override public RFuture trySetMaxSizeAsync(int maxSize) { if (maxSize < 0) { throw new IllegalArgumentException("maxSize should be greater than zero"); } return commandExecutor.writeAsync(getName(), codec, RedisCommands.HSETNX, getOptionsName(), "max-size", maxSize); } @Override public void setMaxSize(int maxSize) { get(setMaxSizeAsync(maxSize)); } @Override public RFuture setMaxSizeAsync(int maxSize) { if (maxSize < 0) { throw new IllegalArgumentException("maxSize should be greater than zero"); } return commandExecutor.writeAsync(getName(), LongCodec.INSTANCE, RedisCommands.HSET_VOID, getOptionsName(), "max-size", maxSize); } @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 maxSize = tonumber(redis.call('hget', KEYS[5], 'max-size')); " + " if maxSize ~= nil and 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 " + " 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), getLastAccessTimeSetNameByKey(key), getOptionsName(key)), System.currentTimeMillis(), encodeMapKey(key)); } @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 maxSize = tonumber(redis.call('hget', KEYS[5], 'max-size')); " + " if maxSize ~= nil and 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 " + " 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(), getOptionsName()), System.currentTimeMillis(), encodeMapValue(value)); } @Override public RFuture> getAllOperationAsync(Set keys) { List args = new ArrayList(keys.size() + 1); List plainKeys = new ArrayList(keys.size()); args.add(System.currentTimeMillis()); for (K key : keys) { plainKeys.add(key); args.add(encodeMapKey(key)); } return commandExecutor.evalWriteAsync(getName(), codec, new RedisCommand>("EVAL", new MapGetAllDecoder(plainKeys, 0), RedisCommand.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 maxSize = tonumber(redis.call('hget', KEYS[5], 'max-size'));" + "local map = {}; " + "for i = 1, #ARGV, 1 do " + " local value = redis.call('hget', KEYS[1], ARGV[i]); " + " map[i] = false;" + " if value ~= false then " + " local key = ARGV[i]; " + " local t, val = struct.unpack('dLc0', value); " + " map[i] = val; " + " if maxSize ~= nil and 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 " + " 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(), getOptionsName()), args.toArray()); } @Override public V putIfAbsent(K key, V value, long ttl, TimeUnit ttlUnit) { return get(putIfAbsentAsync(key, value, ttl, ttlUnit)); } @Override public RFuture putIfAbsentAsync(K key, V value, long ttl, TimeUnit ttlUnit) { return putIfAbsentAsync(key, value, ttl, ttlUnit, 0, null); } @Override public V putIfAbsent(K key, V value, long ttl, TimeUnit ttlUnit, long maxIdleTime, TimeUnit maxIdleUnit) { return get(putIfAbsentAsync(key, value, ttl, ttlUnit, maxIdleTime, maxIdleUnit)); } @Override public RFuture putIfAbsentAsync(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"); } if (maxIdleTime < 0) { throw new IllegalArgumentException("maxIdleTime can't be negative"); } if (ttl == 0 && maxIdleTime == 0) { return putIfAbsentAsync(key, value); } if (ttl > 0 && ttlUnit == null) { throw new NullPointerException("ttlUnit param can't be null"); } if (maxIdleTime > 0 && maxIdleUnit == null) { throw new NullPointerException("maxIdleUnit param can't be null"); } long ttlTimeout = 0; if (ttl > 0) { ttlTimeout = System.currentTimeMillis() + ttlUnit.toMillis(ttl); } long maxIdleTimeout = 0; long maxIdleDelta = 0; if (maxIdleTime > 0) { maxIdleDelta = maxIdleUnit.toMillis(maxIdleTime); maxIdleTimeout = System.currentTimeMillis() + maxIdleDelta; } 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; " // last access time + "local maxSize = tonumber(redis.call('hget', KEYS[7], 'max-size')); " + "if maxSize ~= nil and 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), getLastAccessTimeSetNameByKey(key), getRemovedChannelNameByKey(key), getOptionsName(key)), System.currentTimeMillis(), ttlTimeout, maxIdleTimeout, maxIdleDelta, encodeMapKey(key), encodeMapValue(value)); 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; } }; return mapWriterFuture(future, listener); } @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]); " + "local maxSize = tonumber(redis.call('hget', KEYS[6], 'max-size')); " + "if maxSize ~= nil and 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), getLastAccessTimeSetNameByKey(key), getOptionsName(key)), System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value)); } @Override public RFuture getOperationAsync(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 " + "if tonumber(expireIdle) > tonumber(ARGV[1]) then " + "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(redis.call('hget', KEYS[5], 'max-size')); " + "if maxSize ~= nil and 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), getOptionsName(key)), System.currentTimeMillis(), encodeMapKey(key)); } @Override public V put(K key, V value, long ttl, TimeUnit unit) { return get(putAsync(key, value, ttl, unit)); } @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);" + "local currentTime = tonumber(ARGV[1]);" + "local lastAccessTimeSetName = KEYS[6];" + "local maxSize = tonumber(redis.call('hget', KEYS[8], 'max-size'));" + "if exists == false then" + " if maxSize ~= nil and 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 - 1);" + " 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 ~= nil and 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), getOptionsName(key)), System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value)); } @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]); " + "local maxSize = tonumber(redis.call('hget', KEYS[7], 'max-size'));" + "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 ~= nil and 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 ~= nil and 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 - 1); " + " 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), getOptionsName(key)), System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value)); } @Override public V addAndGet(K key, Number value) { return get(addAndGetAsync(key, value)); } @Override protected RFuture addAndGetOperationAsync(K key, Number value) { ByteBuf 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 " + "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, string.len(newValue), newValue); " + "redis.call('hset', KEYS[1], ARGV[2], newValuePack); " // last access time + "local maxSize = tonumber(redis.call('hget', KEYS[8], 'max-size')); " + "if maxSize ~= nil and 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 - 1); " + " 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), getOptionsName(key)), System.currentTimeMillis(), keyState, new BigDecimal(value.toString()).toPlainString()); } @Override public boolean fastPut(K key, V value, long ttl, TimeUnit ttlUnit) { return get(fastPutAsync(key, value, ttl, ttlUnit)); } @Override public RFuture fastPutAsync(K key, V value, long ttl, TimeUnit ttlUnit) { return fastPutAsync(key, value, ttl, ttlUnit, 0, null); } @Override public boolean fastPut(K key, V value, long ttl, TimeUnit ttlUnit, long maxIdleTime, TimeUnit maxIdleUnit) { return get(fastPutAsync(key, value, ttl, ttlUnit, maxIdleTime, maxIdleUnit)); } @Override 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"); } if (maxIdleTime < 0) { throw new IllegalArgumentException("maxIdleTime can't be negative"); } if (ttl == 0 && maxIdleTime == 0) { return fastPutAsync(key, value); } if (ttl > 0 && ttlUnit == null) { throw new NullPointerException("ttlUnit param can't be null"); } if (maxIdleTime > 0 && maxIdleUnit == null) { throw new NullPointerException("maxIdleUnit param can't be null"); } RFuture future = fastPutOperationAsync(key, value, ttl, ttlUnit, maxIdleTime, maxIdleUnit); if (hasNoWriter()) { return future; } MapWriterTask listener = new MapWriterTask() { @Override protected void execute() { options.getWriter().write(key, value); } }; return mapWriterFuture(future, listener); } protected RFuture fastPutOperationAsync(K key, V value, long ttl, TimeUnit ttlUnit, long maxIdleTime, TimeUnit maxIdleUnit) { long currentTime = System.currentTimeMillis(); long ttlTimeout = 0; if (ttl > 0) { ttlTimeout = currentTime + ttlUnit.toMillis(ttl); } long maxIdleTimeout = 0; long maxIdleDelta = 0; if (maxIdleTime > 0) { maxIdleDelta = maxIdleUnit.toMillis(maxIdleTime); maxIdleTimeout = currentTime + maxIdleDelta; } 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; " + // last access time "local maxSize = tonumber(redis.call('hget', KEYS[8], 'max-size')); " + "if maxSize ~= nil and 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), getOptionsName(key)), System.currentTimeMillis(), ttlTimeout, maxIdleTimeout, maxIdleDelta, encodeMapKey(key), encodeMapValue(value)); return future; } @Override public RFuture putAsync(K key, V value, long ttl, TimeUnit ttlUnit) { return putAsync(key, value, ttl, ttlUnit, 0, null); } @Override public V put(K key, V value, long ttl, TimeUnit ttlUnit, long maxIdleTime, TimeUnit maxIdleUnit) { return get(putAsync(key, value, ttl, ttlUnit, maxIdleTime, maxIdleUnit)); } @Override 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"); } if (maxIdleTime < 0) { throw new IllegalArgumentException("maxIdleTime can't be negative"); } if (ttl == 0 && maxIdleTime == 0) { return putAsync(key, value); } if (ttl > 0 && ttlUnit == null) { throw new NullPointerException("ttlUnit param can't be null"); } if (maxIdleTime > 0 && maxIdleUnit == null) { throw new NullPointerException("maxIdleUnit param can't be null"); } long ttlTimeout = 0; if (ttl > 0) { ttlTimeout = System.currentTimeMillis() + ttlUnit.toMillis(ttl); } long maxIdleTimeout = 0; long maxIdleDelta = 0; if (maxIdleTime > 0) { maxIdleDelta = maxIdleUnit.toMillis(maxIdleTime); maxIdleTimeout = System.currentTimeMillis() + maxIdleDelta; } RFuture future = putOperationAsync(key, value, ttlTimeout, maxIdleTimeout, maxIdleDelta); if (hasNoWriter()) { return future; } MapWriterTask listener = new MapWriterTask() { @Override protected void execute() { options.getWriter().write(key, value); } }; return mapWriterFuture(future, listener); } protected RFuture putOperationAsync(final K key, final V value, long ttlTimeout, long maxIdleTimeout, 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; " // last access time + "local maxSize = tonumber(redis.call('hget', KEYS[8], 'max-size')); " + "if maxSize ~= nil and 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), getOptionsName(key)), System.currentTimeMillis(), ttlTimeout, maxIdleTimeout, maxIdleDelta, encodeMapKey(key), encodeMapValue(value)); return future; } @Override public long remainTimeToLive(K key) { return get(remainTimeToLiveAsync(key)); } @Override public RFuture remainTimeToLiveAsync(K key) { checkKey(key); return commandExecutor.evalWriteAsync(getName(key), codec, RedisCommands.EVAL_LONG, "local value = redis.call('hget', KEYS[1], ARGV[2]); " + "if value == false then " + "return -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 " + "expireDate = math.min(expireDate, tonumber(expireIdle)) " + "end; " + "end; " + "if expireDate == 92233720368547758 then " + "return -1; " + "end;" + "if expireDate > tonumber(ARGV[1]) then " + "return ARGV[1] - expireDate; " + "else " + "return -2; " + "end; " + "return val; ", Arrays.asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key)), System.currentTimeMillis(), encodeMapKey(key)); } 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()); } String getLastAccessTimeSetNameByKey(Object key) { return prefixName("redisson__map_cache__last_access__set", getName(key)); } String getLastAccessTimeSetName() { return prefixName("redisson__map_cache__last_access__set", getName()); } 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()); } String getOptionsName() { return suffixName(getName(), "redisson_options"); } String getOptionsName(Object key) { return suffixName(getName(key), "redisson_options"); } String getCreatedChannelNameByKey(Object key) { return prefixName("redisson_map_cache_created", getName(key)); } String getCreatedChannelName(String name) { return prefixName("redisson_map_cache_created", name); } String getCreatedChannelName() { return prefixName("redisson_map_cache_created", getName()); } String getUpdatedChannelNameByKey(Object key) { return prefixName("redisson_map_cache_updated", getName(key)); } String getUpdatedChannelName() { return prefixName("redisson_map_cache_updated", getName()); } String getUpdatedChannelName(String name) { return prefixName("redisson_map_cache_updated", name); } String getExpiredChannelNameByKey(Object key) { return prefixName("redisson_map_cache_expired", getName(key)); } String getExpiredChannelName(String name) { return prefixName("redisson_map_cache_expired", name); } String getExpiredChannelName() { return prefixName("redisson_map_cache_expired", getName()); } String getRemovedChannelNameByKey(Object key) { return prefixName("redisson_map_cache_removed", getName(key)); } String getRemovedChannelName() { return prefixName("redisson_map_cache_removed", getName()); } String getRemovedChannelName(String name) { return prefixName("redisson_map_cache_removed", name); } @Override protected 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('zrem', KEYS[5], 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), getLastAccessTimeSetNameByKey(key)), System.currentTimeMillis(), encodeMapKey(key)); } @Override protected RFuture> fastRemoveOperationBatchAsync(K... keys) { List args = new ArrayList(keys.length); for (K key : keys) { args.add(encodeMapKey(key)); } RFuture> future = commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_LIST, "local maxSize = tonumber(redis.call('hget', KEYS[6], 'max-size')); " + "if maxSize ~= nil and 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; " + "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(), getOptionsName()), args.toArray()); return future; } @Override protected RFuture fastRemoveOperationAsync(K ... keys) { List params = new ArrayList(keys.length); for (K key : keys) { params.add(encodeMapKey(key)); } return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_LONG, "local maxSize = tonumber(redis.call('hget', KEYS[6], 'max-size')); " + "if maxSize ~= nil and 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(), getOptionsName()), params.toArray()); } @Override public MapScanResult scanIterator(String name, RedisClient client, long startPos, String pattern) { return get(scanIteratorAsync(name, client, startPos, pattern)); } public RFuture> scanIteratorAsync(final String name, RedisClient client, long startPos, String pattern) { List params = new ArrayList(); params.add(System.currentTimeMillis()); params.add(startPos); if (pattern != null) { params.add(pattern); } RedisCommand> EVAL_HSCAN = new RedisCommand>("EVAL", new ListMultiDecoder(new LongMultiDecoder(), new ObjectMapDecoder(new MapScanCodec(codec)), new ObjectListDecoder(codec), new MapCacheScanResultReplayDecoder()), RedisCommand.ValueType.MAP); RFuture> f = commandExecutor.evalReadAsync(client, name, codec, EVAL_HSCAN, "local result = {}; " + "local idleKeys = {}; " + "local res; " + "if (#ARGV == 3) then " + " res = redis.call('hscan', KEYS[1], ARGV[2], 'match', ARGV[3]); " + "else " + " res = redis.call('hscan', KEYS[1], ARGV[2]); " + "end;" + "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); " + "end; " + "expireDate = math.min(expireDate, tonumber(expireIdle)) " + "end; " + "end; " + "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)), params.toArray()); f.addListener(new FutureListener>() { @Override public void operationComplete(Future> future) throws Exception { if (future.isSuccess()) { MapCacheScanResult res = future.getNow(); if (res.getIdleKeys().isEmpty()) { return; } List args = new ArrayList(res.getIdleKeys().size() + 1); args.add(System.currentTimeMillis()); encodeMapKeys(args, res.getIdleKeys()); commandExecutor.evalWriteAsync(name, codec, new RedisCommand>("EVAL", new MapGetAllDecoder(args, 1), RedisCommand.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 " + "redis.call('zadd', KEYS[2], t + currentTime, key); " + "end; " + "end; " + "end; " + "end; " + "end; ", Arrays.asList(name, getIdleSetName(name)), args.toArray()); } } }); return (RFuture>)(Object)f; } @Override 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); " + // last access time "local maxSize = tonumber(redis.call('hget', KEYS[8], 'max-size'));" + "if maxSize ~= nil and 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 - 1); " + " 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), getOptionsName(key)), System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value)); } @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]); " + "local lastAccessTimeSetName = KEYS[5]; " + "local maxSize = tonumber(redis.call('hget', KEYS[7], 'max-size')); " + "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 ~= nil and 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 - 1); " + " 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 ~= nil and 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 " + "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), getOptionsName(key)), System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value)); } @Override 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)); } @Override public RFuture fastPutIfAbsentAsync(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"); } if (maxIdleTime < 0) { throw new IllegalArgumentException("maxIdleTime can't be negative"); } if (ttl == 0 && maxIdleTime == 0) { return fastPutIfAbsentAsync(key, value); } if (ttl > 0 && ttlUnit == null) { throw new NullPointerException("ttlUnit param can't be null"); } if (maxIdleTime > 0 && maxIdleUnit == null) { throw new NullPointerException("maxIdleUnit param can't be null"); } long ttlTimeout = 0; if (ttl > 0) { ttlTimeout = System.currentTimeMillis() + ttlUnit.toMillis(ttl); } long maxIdleTimeout = 0; long maxIdleDelta = 0; if (maxIdleTime > 0) { maxIdleDelta = maxIdleUnit.toMillis(maxIdleTime); maxIdleTimeout = System.currentTimeMillis() + maxIdleDelta; } 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(redis.call('hget', KEYS[7], 'max-size')); " + " if maxSize ~= nil and 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), getOptionsName(key)), System.currentTimeMillis(), ttlTimeout, maxIdleTimeout, maxIdleDelta, encodeMapKey(key), encodeMapValue(value)); 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(); } }; return mapWriterFuture(future, listener); } @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)); } @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)), System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value)); } @Override protected RFuture putAllOperationAsync(Map map) { List params = new ArrayList(map.size()*2 + 1); params.add(System.currentTimeMillis()); for (java.util.Map.Entry t : map.entrySet()) { if (t.getKey() == null) { throw new NullPointerException("map key can't be null"); } 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, "local currentTime = tonumber(table.remove(ARGV, 1)); " + // index is the first parameter "local maxSize = tonumber(redis.call('hget', KEYS[8], 'max-size'));" + "for i, value in ipairs(ARGV) do " + "if i % 2 == 0 then " + "local key = ARGV[i-1];" + "local v = redis.call('hget', KEYS[1], key);" + "local exists = false;" + "if v ~= false then" + " local t, val = struct.unpack('dLc0', v);" + " 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" + " expireDate = math.min(expireDate, tonumber(expireIdle))" + " end;" + " end;" + " if expireDate > tonumber(currentTime) then" + " exists = true;" + " end;" + "end;" + "" + "local newvalue = struct.pack('dLc0', 0, string.len(value), value);" + "redis.call('hset', KEYS[1], key, newvalue);" + "local lastAccessTimeSetName = KEYS[6];" + "if exists == false then" + " if maxSize ~= nil and maxSize ~= 0 then" + " redis.call('zadd', lastAccessTimeSetName, currentTime, key);" + " local cacheSize = tonumber(redis.call('hlen', KEYS[1]));" + " if cacheSize > maxSize then" + " local lruItems = redis.call('zrange', lastAccessTimeSetName, 0, cacheSize - maxSize - 1);" + " 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(key), key, string.len(value), value);" + " redis.call('publish', KEYS[4], msg);" + "else " + "local t, val = struct.unpack('dLc0', v);" + "local msg = struct.pack('Lc0Lc0Lc0', string.len(key), key, string.len(value), value, string.len(val), val);" + "redis.call('publish', KEYS[5], msg);" + " if maxSize ~= nil and maxSize ~= 0 then " + " redis.call('zadd', lastAccessTimeSetName, currentTime, key);" + " end;" + "end;" + "end;" + "end;", Arrays.asList(getName(), getTimeoutSetName(), getIdleSetName(), getCreatedChannelName(), getUpdatedChannelName(), getLastAccessTimeSetName(), getRemovedChannelName(), getOptionsName()), params.toArray()); } private Boolean isWindows; @Override public int addListener(final MapEntryListener listener) { if (listener == null) { throw new NullPointerException(); } if (isWindows == null) { RFuture> serverFuture = commandExecutor.readAsync((String)null, StringCodec.INSTANCE, RedisCommands.INFO_SERVER); serverFuture.syncUninterruptibly(); String os = serverFuture.getNow().get("os"); isWindows = os.contains("Windows"); } if (listener instanceof EntryRemovedListener) { RTopic> topic = redisson.getTopic(getRemovedChannelName(), new MapCacheEventCodec(codec, isWindows)); return topic.addListener(new MessageListener>() { @Override public void onMessage(String channel, List msg) { EntryEvent event = new EntryEvent(RedissonMapCache.this, EntryEvent.Type.REMOVED, (K)msg.get(0), (V)msg.get(1), null); ((EntryRemovedListener) listener).onRemoved(event); } }); } if (listener instanceof EntryCreatedListener) { RTopic> topic = redisson.getTopic(getCreatedChannelName(), new MapCacheEventCodec(codec, isWindows)); return topic.addListener(new MessageListener>() { @Override public void onMessage(String channel, List msg) { EntryEvent event = new EntryEvent(RedissonMapCache.this, EntryEvent.Type.CREATED, (K)msg.get(0), (V)msg.get(1), null); ((EntryCreatedListener) listener).onCreated(event); } }); } if (listener instanceof EntryUpdatedListener) { RTopic> topic = redisson.getTopic(getUpdatedChannelName(), new MapCacheEventCodec(codec, isWindows)); return topic.addListener(new MessageListener>() { @Override public void onMessage(String channel, List msg) { EntryEvent event = new EntryEvent(RedissonMapCache.this, EntryEvent.Type.UPDATED, (K)msg.get(0), (V)msg.get(1), (V)msg.get(2)); ((EntryUpdatedListener) listener).onUpdated(event); } }); } if (listener instanceof EntryExpiredListener) { RTopic> topic = redisson.getTopic(getExpiredChannelName(), new MapCacheEventCodec(codec, isWindows)); return topic.addListener(new MessageListener>() { @Override public void onMessage(String channel, List msg) { EntryEvent event = new EntryEvent(RedissonMapCache.this, EntryEvent.Type.EXPIRED, (K)msg.get(0), (V)msg.get(1), null); ((EntryExpiredListener) listener).onExpired(event); } }); } throw new IllegalArgumentException("Wrong listener type " + listener.getClass()); } @Override public void removeListener(int listenerId) { RTopic> removedTopic = redisson.getTopic(getRemovedChannelName()); removedTopic.removeListener(listenerId); RTopic> createdTopic = redisson.getTopic(getCreatedChannelName()); createdTopic.removeListener(listenerId); RTopic> updatedTopic = redisson.getTopic(getUpdatedChannelName()); updatedTopic.removeListener(listenerId); RTopic> expiredTopic = redisson.getTopic(getExpiredChannelName()); expiredTopic.removeListener(listenerId); } @Override public RFuture deleteAsync() { return commandExecutor.writeAsync(getName(), RedisCommands.DEL_OBJECTS, getName(), getTimeoutSetName(), getIdleSetName(), getLastAccessTimeSetName(), getOptionsName()); } @Override public RFuture expireAsync(long timeToLive, TimeUnit timeUnit) { return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, "local maxSize = tonumber(redis.call('hget', KEYS[5], 'max-size')); " + "if maxSize ~= nil and maxSize ~= 0 then " + " redis.call('pexpire', KEYS[5], ARGV[1]); " + " redis.call('zadd', KEYS[4], 92233720368547758, 'redisson__expiretag'); " + " 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(), getOptionsName()), timeUnit.toMillis(timeToLive)); } @Override public RFuture expireAtAsync(long timestamp) { return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, "local maxSize = tonumber(redis.call('hget', KEYS[5], 'max-size')); " + "if maxSize ~= nil and maxSize ~= 0 then " + " redis.call('pexpire', KEYS[5], ARGV[1]); " + " redis.call('zadd', KEYS[4], 92233720368547758, 'redisson__expiretag'); " + " 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(), getOptionsName()), timestamp); } @Override public RFuture clearExpireAsync() { return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, "local maxSize = tonumber(redis.call('hget', KEYS[5], 'max-size')); " + "if maxSize ~= nil and maxSize ~= 0 then " + " redis.call('persist', KEYS[5]); " + " redis.call('zrem', KEYS[4], 92233720368547758, 'redisson__expiretag'); " + " redis.call('persist', KEYS[4]); " + "end; " + "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(), getLastAccessTimeSetName(), getOptionsName())); } @Override public RFuture> readAllKeySetAsync() { return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_MAP_KEY_SET, "local s = redis.call('hgetall', KEYS[1]); " + "local maxSize = tonumber(redis.call('hget', KEYS[5], 'max-size'));" + "local result = {}; " + "for i, v in ipairs(s) do " + "if i % 2 == 0 then " + "local t, val = struct.unpack('dLc0', v); " + "local key = s[i-1];" + "local expireDate = 92233720368547758; " + "local expireDateScore = redis.call('zscore', KEYS[2], key); " + "if expireDateScore ~= false then " + "expireDate = tonumber(expireDateScore) " + "end; " + "if t ~= 0 then " + "local expireIdle = redis.call('zscore', KEYS[3], key); " + "if expireIdle ~= false then " + "if tonumber(expireIdle) > tonumber(ARGV[1]) then " + "redis.call('zadd', KEYS[3], t + tonumber(ARGV[1]), key); " + "if maxSize ~= nil and maxSize ~= 0 then " + " 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;" + "return result;", Arrays.asList(getName(), getTimeoutSetName(), getIdleSetName(), getLastAccessTimeSetName(), getOptionsName()), System.currentTimeMillis()); } @Override public RFuture>> readAllEntrySetAsync() { return readAll(RedisCommands.EVAL_MAP_ENTRY); } private RFuture readAll(RedisCommand evalCommandType) { return commandExecutor.evalWriteAsync(getName(), codec, evalCommandType, "local s = redis.call('hgetall', KEYS[1]); " + "local result = {}; " + "local maxSize = tonumber(redis.call('hget', KEYS[5], 'max-size'));" + "for i, v in ipairs(s) do " + "if i % 2 == 0 then " + "local t, val = struct.unpack('dLc0', v); " + "local key = s[i-1];" + "local expireDate = 92233720368547758; " + "local expireDateScore = redis.call('zscore', KEYS[2], key); " + "if expireDateScore ~= false then " + "expireDate = tonumber(expireDateScore) " + "end; " + "if t ~= 0 then " + "local expireIdle = redis.call('zscore', KEYS[3], key); " + "if expireIdle ~= false then " + "if tonumber(expireIdle) > tonumber(ARGV[1]) then " + "redis.call('zadd', KEYS[3], t + tonumber(ARGV[1]), key); " + "if maxSize ~= nil and maxSize ~= 0 then " + " 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; " + "end; " + "end;" + "return result;", Arrays.asList(getName(), getTimeoutSetName(), getIdleSetName(), getLastAccessTimeSetName(), getOptionsName()), System.currentTimeMillis()); } @Override public RFuture> readAllMapAsync() { return readAll(RedisCommands.EVAL_MAP); } @Override public RFuture> readAllValuesAsync() { return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_MAP_VALUE_LIST, "local s = redis.call('hgetall', KEYS[1]); " + "local result = {}; " + "local maxSize = tonumber(redis.call('hget', KEYS[5], 'max-size')); " + "for i, v in ipairs(s) do " + "if i % 2 == 0 then " + "local t, val = struct.unpack('dLc0', v); " + "local key = s[i-1];" + "local expireDate = 92233720368547758; " + "local expireDateScore = redis.call('zscore', KEYS[2], key); " + "if expireDateScore ~= false then " + "expireDate = tonumber(expireDateScore) " + "end; " + "if t ~= 0 then " + "local expireIdle = redis.call('zscore', KEYS[3], key); " + "if expireIdle ~= false then " + "if tonumber(expireIdle) > tonumber(ARGV[1]) then " + "redis.call('zadd', KEYS[3], t + tonumber(ARGV[1]), key); " + "if maxSize ~= nil and maxSize ~= 0 then " + " 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, val); " + "end; " + "end; " + "end;" + "return result;", Arrays.asList(getName(), getTimeoutSetName(), getIdleSetName(), getLastAccessTimeSetName(), getOptionsName()), System.currentTimeMillis()); } }