All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.redisson.RedissonMapCache Maven / Gradle / Ivy

Go to download

Easy Redis Java client and Real-Time Data Platform. Valkey compatible. Sync/Async/RxJava3/Reactive API. Client side caching. Over 50 Redis based Java objects and services: JCache API, Apache Tomcat, Hibernate, Spring, Set, Multimap, SortedSet, Map, List, Queue, Deque, Semaphore, Lock, AtomicLong, Map Reduce, Bloom filter, Scheduler, RPC

There is a newer version: 3.40.2
Show newest version
/**
 * Copyright (c) 2013-2024 Nikita Koksharov
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.redisson;

import io.netty.buffer.ByteBuf;
import org.redisson.api.*;
import org.redisson.api.listener.MessageListener;
import org.redisson.api.map.event.*;
import org.redisson.client.RedisClient;
import org.redisson.client.codec.Codec;
import org.redisson.client.codec.IntegerCodec;
import org.redisson.client.codec.LongCodec;
import org.redisson.client.codec.StringCodec;
import org.redisson.client.protocol.RedisCommand;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.client.protocol.convertor.NumberConvertor;
import org.redisson.client.protocol.decoder.*;
import org.redisson.codec.BaseEventCodec;
import org.redisson.codec.MapCacheEventCodec;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.connection.decoder.MapGetAllDecoder;
import org.redisson.eviction.EvictionScheduler;
import org.redisson.misc.CompletableFutureWrapper;

import java.math.BigDecimal;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

/**
 * 

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 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 { private final EvictionScheduler evictionScheduler; protected String publishCommand; private String timeoutSetName; private String idleSetName; private String lastAccessTimeSetName; private String optionsName; public RedissonMapCache(EvictionScheduler evictionScheduler, CommandAsyncExecutor commandExecutor, String name, RedissonClient redisson, MapCacheOptions options, WriteBehindService writeBehindService) { super(commandExecutor, name, redisson, options, writeBehindService); this.timeoutSetName = getTimeoutSetName(getRawName()); this.idleSetName = getIdleSetName(getRawName()); this.lastAccessTimeSetName = getLastAccessTimeSetName(getRawName()); this.optionsName = getOptionsName(getRawName()); if (evictionScheduler != null) { evictionScheduler.schedule(getRawName(), timeoutSetName, idleSetName, getExpiredChannelName(), lastAccessTimeSetName, options, commandExecutor.getConnectionManager().getSubscribeService().getPublishCommand()); } this.evictionScheduler = evictionScheduler; this.publishCommand = commandExecutor.getConnectionManager().getSubscribeService().getPublishCommand(); } public RedissonMapCache(Codec codec, EvictionScheduler evictionScheduler, CommandAsyncExecutor commandExecutor, String name, RedissonClient redisson, MapCacheOptions options, WriteBehindService writeBehindService) { super(codec, commandExecutor, name, redisson, options, writeBehindService); this.timeoutSetName = getTimeoutSetName(getRawName()); this.idleSetName = getIdleSetName(getRawName()); this.lastAccessTimeSetName = getLastAccessTimeSetName(getRawName()); this.optionsName = getOptionsName(getRawName()); if (evictionScheduler != null) { evictionScheduler.schedule(getRawName(), timeoutSetName, idleSetName, getExpiredChannelName(), lastAccessTimeSetName, options, commandExecutor.getConnectionManager().getSubscribeService().getPublishCommand()); } this.evictionScheduler = evictionScheduler; this.publishCommand = commandExecutor.getConnectionManager().getSubscribeService().getPublishCommand(); } @Override public boolean trySetMaxSize(int maxSize) { return get(trySetMaxSizeAsync(maxSize)); } public boolean trySetMaxSize(int maxSize, EvictionMode mode) { return get(trySetMaxSizeAsync(maxSize, mode)); } @Override public RFuture trySetMaxSizeAsync(int maxSize) { return trySetMaxSizeAsync(maxSize, EvictionMode.LRU); } public RFuture trySetMaxSizeAsync(int maxSize, EvictionMode mode) { if (maxSize < 0) { throw new IllegalArgumentException("maxSize should be greater than zero"); } return commandExecutor.evalWriteNoRetryAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, "redis.call('hsetnx', KEYS[1], 'max-size', ARGV[1]);" + "return redis.call('hsetnx', KEYS[1], 'mode', ARGV[2]);", Collections.singletonList(optionsName), maxSize, mode); } @Override public V computeIfAbsent(K key, Duration ttl, Function mappingFunction) { checkNotBatch(); checkKey(key); Objects.requireNonNull(mappingFunction); V value = get(key); if (value != null) { return value; } RLock lock = getLock(key); lock.lock(); try { value = get(key); if (value == null) { V newValue = mappingFunction.apply(key); if (newValue != null) { fastPut(key, newValue, ttl.toMillis(), TimeUnit.MILLISECONDS); return newValue; } return null; } return value; } finally { lock.unlock(); } } @Override public void setMaxSize(int maxSize) { get(setMaxSizeAsync(maxSize)); } public void setMaxSize(int maxSize, EvictionMode mode) { get(setMaxSizeAsync(maxSize, mode)); } @Override public RFuture setMaxSizeAsync(int maxSize) { return setMaxSizeAsync(maxSize, EvictionMode.LRU); } public RFuture setMaxSizeAsync(int maxSize, EvictionMode mode) { if (maxSize < 0) { throw new IllegalArgumentException("maxSize should be greater than zero"); } List params = new ArrayList<>(3); params.add(optionsName); params.add("max-size"); params.add(maxSize); params.add("mode"); params.add(mode); return commandExecutor.writeAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.HMSET, params.toArray()); } @Override protected RFuture containsKeyOperationAsync(String name, Object key) { return commandExecutor.evalWriteAsync(name, 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 " + " local mode = redis.call('hget', KEYS[5], 'mode'); " + " if mode == false or mode == 'LRU' then " + " redis.call('zadd', KEYS[4], tonumber(ARGV[1]), ARGV[2]); " + " else " + " redis.call('zincrby', KEYS[4], 1, ARGV[2]); " + " end; " + " 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(name, getTimeoutSetName(name), getIdleSetName(name), getLastAccessTimeSetName(name), getOptionsName(name)), System.currentTimeMillis(), encodeMapKey(key)); } @Override public RFuture containsValueAsync(Object value) { checkValue(value); return commandExecutor.evalWriteAsync(getRawName(), 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 " + " local mode = redis.call('hget', KEYS[5], 'mode'); " + " if mode == false or mode == 'LRU' then " + " redis.call('zadd', KEYS[4], tonumber(ARGV[1]), key); " + " else " + " redis.call('zincrby', KEYS[4], 1, key); " + " end; " + " end; " + " 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(getRawName(), timeoutSetName, idleSetName, lastAccessTimeSetName, optionsName), System.currentTimeMillis(), encodeMapValue(value)); } @Override public RFuture> getAllOperationAsync(Set keys) { List args = new ArrayList<>(keys.size() + 1); List plainKeys = new ArrayList<>(keys); args.add(System.currentTimeMillis()); encodeMapKeys(args, keys); return commandExecutor.evalWriteAsync(getRawName(), codec, new RedisCommand>("EVAL", new MapValueDecoder(new MapGetAllDecoder(plainKeys, 0))), "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 " + " local mode = redis.call('hget', KEYS[5], 'mode'); " + " if mode == false or mode == 'LRU' then " + " redis.call('zadd', KEYS[4], currentTime, key); " + " else " + " redis.call('zincrby', KEYS[4], 1, key); " + " end; " + " 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(getRawName(), timeoutSetName, idleSetName, lastAccessTimeSetName, optionsName), 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(K key, 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; } String name = getRawName(key); RFuture future = commandExecutor.evalWriteAsync(name, 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')); " + "local hasListeners = redis.call('hget', KEYS[7], 'has-listeners'); " + "if maxSize ~= nil and maxSize ~= 0 then " + " local currentTime = tonumber(ARGV[1]); " + " local lastAccessTimeSetName = KEYS[5]; " + "local mode = redis.call('hget', KEYS[7], 'mode'); " + "if mode == false or mode == 'LRU' then " + "redis.call('zadd', lastAccessTimeSetName, currentTime, ARGV[5]); " + "end; " + " 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 and lruItem ~= ARGV[5] 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); " + " if lruItemValue ~= false and hasListeners ~= false then " + " local removedChannelName = KEYS[6]; " + "local ttl, obj = struct.unpack('dLc0', lruItemValue);" + " local msg = struct.pack('Lc0Lc0', string.len(lruItem), lruItem, string.len(obj), obj);" + " redis.call(ARGV[7], removedChannelName, msg); " + "end; " + " end; " + " end; " + " end; " + "if mode == 'LFU' then " + "redis.call('zincrby', lastAccessTimeSetName, 1, ARGV[5]); " + "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); " + "if hasListeners ~= false then " + "local msg = struct.pack('Lc0Lc0', string.len(ARGV[5]), ARGV[5], string.len(ARGV[6]), ARGV[6]); " + "redis.call(ARGV[7], KEYS[4], msg); " + "end; " + "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(name, getTimeoutSetName(name), getIdleSetName(name), getCreatedChannelName(name), getLastAccessTimeSetName(name), getRemovedChannelName(name), getOptionsName(name)), System.currentTimeMillis(), ttlTimeout, maxIdleTimeout, maxIdleDelta, encodeMapKey(key), encodeMapValue(value), publishCommand); if (hasNoWriter()) { return future; } MapWriterTask.Add task = new MapWriterTask.Add(key, value); return mapWriterFuture(future, task, r -> r == null); } @Override protected RFuture removeOperationAsync(Object key, Object value) { String name = getRawName(key); return commandExecutor.evalWriteAsync(name, 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; " + "local hasListeners = redis.call('hget', KEYS[6], 'has-listeners'); " + "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]); " + "if hasListeners ~= false then " + "local msg = struct.pack('Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(val), val); " + "redis.call(ARGV[4], KEYS[4], msg); " + "end; " + "return 1; " + "else " + "return 0; " + "end", Arrays.asList(name, getTimeoutSetName(name), getIdleSetName(name), getRemovedChannelName(name), getLastAccessTimeSetName(name), getOptionsName(name)), System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value), publishCommand); } @Override public RFuture getOperationAsync(K key) { String name = getRawName(key); return commandExecutor.evalWriteAsync(name, 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 " + "local mode = redis.call('hget', KEYS[5], 'mode'); " + "if mode == false or mode == 'LRU' then " + "redis.call('zadd', KEYS[4], tonumber(ARGV[1]), ARGV[2]); " + "else " + "redis.call('zincrby', KEYS[4], 1, ARGV[2]); " + "end; " + "end; " + "return val; ", Arrays.asList(name, getTimeoutSetName(name), getIdleSetName(name), getLastAccessTimeSetName(name), getOptionsName(name)), 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) { String name = getRawName(key); return commandExecutor.evalWriteAsync(name, 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;" + "redis.call('zrem', KEYS[2], ARGV[2]); " + "redis.call('zrem', KEYS[3], ARGV[2]); " + "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'));" + "local mode = redis.call('hget', KEYS[8], 'mode'); " + "local hasListeners = redis.call('hget', KEYS[8], 'has-listeners'); " + "if exists == false then" + " if maxSize ~= nil and maxSize ~= 0 then " + "if mode == false or mode == 'LRU' then " + "redis.call('zadd', lastAccessTimeSetName, currentTime, ARGV[2]); " + "end; " + " 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 and lruItem ~= ARGV[2] 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);" + " if lruItemValue ~= false and hasListeners ~= false then " + " local removedChannelName = KEYS[7];" + "local ttl, obj = struct.unpack('dLc0', lruItemValue);" + " local msg = struct.pack('Lc0Lc0', string.len(lruItem), lruItem, string.len(obj), obj);" + " redis.call(ARGV[4], removedChannelName, msg);" + "end; " + " end;" + " end" + " end;" + "if mode == 'LFU' then " + "redis.call('zincrby', lastAccessTimeSetName, 1, ARGV[2]); " + "end; " + " end;" + "if hasListeners ~= false then " + "local msg = struct.pack('Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[3]), ARGV[3]);" + "redis.call(ARGV[4], KEYS[4], msg);" + "end; " + " return nil;" + "else" + " if maxSize ~= nil and maxSize ~= 0 then " + "if mode == false or mode == 'LRU' then " + "redis.call('zadd', lastAccessTimeSetName, currentTime, ARGV[2]); " + "else " + "redis.call('zincrby', lastAccessTimeSetName, 1, ARGV[2]); " + "end; " + " end;" + "end;" + "" + "local t, val = struct.unpack('dLc0', v);" + "if hasListeners ~= false then " + "local msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[3]), ARGV[3], string.len(val), val);" + "redis.call(ARGV[4], KEYS[5], msg);" + "end; " + "return val;", Arrays.asList(name, getTimeoutSetName(name), getIdleSetName(name), getCreatedChannelName(name), getUpdatedChannelName(name), getLastAccessTimeSetName(name), getRemovedChannelName(name), getOptionsName(name)), System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value), publishCommand); } @Override protected RFuture putIfExistsOperationAsync(K key, V value) { String name = getRawName(key); return commandExecutor.evalWriteAsync(name, codec, RedisCommands.EVAL_MAP_VALUE, "local value = redis.call('hget', KEYS[1], ARGV[2]); " + "if value == false then " + "return nil;" + "end; " + "local maxSize = tonumber(redis.call('hget', KEYS[7], 'max-size'));" + "local lastAccessTimeSetName = KEYS[5]; " + "local currentTime = tonumber(ARGV[1]); " + "local t, val;" + "if value ~= false then " + "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 " + "local mode = redis.call('hget', KEYS[7], 'mode'); " + "if mode == false or mode == 'LRU' then " + "redis.call('zadd', lastAccessTimeSetName, currentTime, ARGV[2]); " + "else " + "redis.call('zincrby', lastAccessTimeSetName, 1, ARGV[2]); " + "end; " + "end; " + "else " + "return nil; " + "end; " + "end; " + "local newValue = struct.pack('dLc0', 0, string.len(ARGV[3]), ARGV[3]); " + "redis.call('hset', KEYS[1], ARGV[2], newValue); " + "local hasListeners = redis.call('hget', KEYS[7], 'has-listeners'); " // last access time + "if maxSize ~= nil and maxSize ~= 0 then " + "local mode = redis.call('hget', KEYS[7], 'mode'); " + "if mode == false or mode == 'LRU' then " + "redis.call('zadd', lastAccessTimeSetName, currentTime, ARGV[2]); " + "end; " + " 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 and lruItem ~= ARGV[2] 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); " + " if lruItemValue ~= false and hasListeners ~= false then " + " local removedChannelName = KEYS[6]; " + "local ttl, obj = struct.unpack('dLc0', lruItemValue);" + " local msg = struct.pack('Lc0Lc0', string.len(lruItem), lruItem, string.len(obj), obj);" + " redis.call(ARGV[4], removedChannelName, msg); " + "end; " + " end; " + " end; " + " end; " + "if mode == 'LFU' then " + "redis.call('zincrby', lastAccessTimeSetName, 1, ARGV[2]); " + "end; " + "end; " + "if hasListeners ~= false then " + "local msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[3]), ARGV[3], string.len(val), val); " + "redis.call(ARGV[4], KEYS[4], msg); " + "end; " + "return val;", Arrays.asList(name, getTimeoutSetName(name), getIdleSetName(name), getUpdatedChannelName(name), getLastAccessTimeSetName(name), getRemovedChannelName(name), getOptionsName(name)), System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value), publishCommand); } @Override protected RFuture putIfAbsentOperationAsync(K key, V value) { String name = getRawName(key); return commandExecutor.evalWriteAsync(name, 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 " + "local mode = redis.call('hget', KEYS[7], 'mode'); " + "if mode == false or mode == 'LRU' then " + "redis.call('zadd', lastAccessTimeSetName, currentTime, ARGV[2]); " + "else " + "redis.call('zincrby', lastAccessTimeSetName, 1, ARGV[2]); " + "end; " + "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); " + "local hasListeners = redis.call('hget', KEYS[7], 'has-listeners'); " // last access time + "if maxSize ~= nil and maxSize ~= 0 then " + "local mode = redis.call('hget', KEYS[7], 'mode'); " + "if mode == false or mode == 'LRU' then " + "redis.call('zadd', lastAccessTimeSetName, currentTime, ARGV[2]); " + "end; " + " 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 and lruItem ~= ARGV[2] 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); " + " if lruItemValue ~= false and hasListeners ~= false then " + " local removedChannelName = KEYS[6]; " + "local ttl, obj = struct.unpack('dLc0', lruItemValue);" + " local msg = struct.pack('Lc0Lc0', string.len(lruItem), lruItem, string.len(obj), obj);" + " redis.call(ARGV[4], removedChannelName, msg); " + "end; " + " end; " + " end; " + " end; " + "if mode == 'LFU' then " + "redis.call('zincrby', lastAccessTimeSetName, 1, ARGV[2]); " + "end; " + "end; " + "if hasListeners ~= false then " + "local msg = struct.pack('Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[3]), ARGV[3]); " + "redis.call(ARGV[4], KEYS[4], msg); " + "end; " + "return nil;", Arrays.asList(name, getTimeoutSetName(name), getIdleSetName(name), getCreatedChannelName(name), getLastAccessTimeSetName(name), getRemovedChannelName(name), getOptionsName(name)), System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value), publishCommand); } @Override public void putAll(Map map, long ttl, TimeUnit ttlUnit) { get(putAllAsync(map, ttl, ttlUnit)); } @Override public RFuture putAllAsync(Map map, long ttl, TimeUnit ttlUnit) { if (map.isEmpty()) { return new CompletableFutureWrapper<>((Void) null); } RFuture future = putAllOperationAsync(map, ttl, ttlUnit); if (hasNoWriter()) { return future; } MapWriterTask listener = new MapWriterTask.Add(map); return mapWriterFuture(future, listener); } @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); String name = getRawName(key); return commandExecutor.evalWriteAsync(name, 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 hasListeners = redis.call('hget', KEYS[8], 'has-listeners'); " + "local newValue; " + "if value ~= false and expireDate > tonumber(ARGV[1]) then " + "redis.call('hset', KEYS[1], 'temp_val__redisson', val); " + "newValue = redis.call('hincrbyfloat', KEYS[1], 'temp_val__redisson', ARGV[3]); " + "redis.call('hdel', KEYS[1], 'temp_val__redisson'); " + "if hasListeners ~= false then " + "local msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(newValue), newValue, string.len(val), val); " + "redis.call(ARGV[4], KEYS[5], msg); " + "end;" + "else " + "newValue = ARGV[3]; " + "if hasListeners ~= false then " + "local msg = struct.pack('Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[3]), ARGV[3]); " + "redis.call(ARGV[4], KEYS[4], msg); " + "end;" + "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]; " + "local mode = redis.call('hget', KEYS[8], 'mode'); " + "if mode == false or mode == 'LRU' then " + "redis.call('zadd', lastAccessTimeSetName, currentTime, ARGV[2]); " + "end; " + " 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 and lruItem ~= ARGV[2] 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); " + " if lruItemValue ~= false and hasListeners ~= false then " + " local removedChannelName = KEYS[7]; " + "local ttl, obj = struct.unpack('dLc0', lruItemValue);" + " local msg = struct.pack('Lc0Lc0', string.len(lruItem), lruItem, string.len(obj), obj);" + " redis.call(ARGV[4], removedChannelName, msg); " + "end; " + " end; " + " end; " + " end; " + "if mode == 'LFU' then " + "redis.call('zincrby', lastAccessTimeSetName, 1, ARGV[2]); " + "end; " + "end; " + "return newValue;", Arrays.asList(name, getTimeoutSetName(name), getIdleSetName(name), getCreatedChannelName(name), getUpdatedChannelName(name), getLastAccessTimeSetName(name), getRemovedChannelName(name), getOptionsName(name)), System.currentTimeMillis(), keyState, new BigDecimal(value.toString()).toPlainString(), publishCommand); } @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(K key, 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; } return mapWriterFuture(future, new MapWriterTask.Add(key, value)); } 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; } String name = getRawName(key); RFuture future = commandExecutor.evalWriteAsync(name, 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 hasListeners = redis.call('hget', KEYS[8], 'has-listeners'); " + // last access time "local maxSize = tonumber(redis.call('hget', KEYS[8], 'max-size')); " + "local mode = redis.call('hget', KEYS[8], 'mode'); " + "if maxSize ~= nil and maxSize ~= 0 then " + " local currentTime = tonumber(ARGV[1]); " + " local lastAccessTimeSetName = KEYS[6]; " + "if mode == false or mode == 'LRU' then " + "redis.call('zadd', lastAccessTimeSetName, currentTime, ARGV[5]); " + "end; " + " 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 and lruItem ~= ARGV[5] 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); " + " if lruItemValue ~= false and hasListeners ~= false then " + " local removedChannelName = KEYS[7]; " + "local ttl, obj = struct.unpack('dLc0', lruItemValue);" + " local msg = struct.pack('Lc0Lc0', string.len(lruItem), lruItem, string.len(obj), obj);" + " redis.call(ARGV[7], removedChannelName, msg); " + "end; " + " end; " + " end; " + " end; " + "if mode == 'LFU' then " + "redis.call('zincrby', lastAccessTimeSetName, 1, ARGV[5]); " + "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 " + "if hasListeners ~= false then " + "local msg = struct.pack('Lc0Lc0', string.len(ARGV[5]), ARGV[5], string.len(ARGV[6]), ARGV[6]); " + "redis.call(ARGV[7], KEYS[4], msg); " + "end; " + "return 1;" + "else " + "if hasListeners ~= false then " + "local msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[5]), ARGV[5], string.len(ARGV[6]), ARGV[6], string.len(val), val); " + "redis.call(ARGV[7], KEYS[5], msg); " + "end; " + "return 0;" + "end;", Arrays.asList(name, getTimeoutSetName(name), getIdleSetName(name), getCreatedChannelName(name), getUpdatedChannelName(name), getLastAccessTimeSetName(name), getRemovedChannelName(name), getOptionsName(name)), System.currentTimeMillis(), ttlTimeout, maxIdleTimeout, maxIdleDelta, encodeMapKey(key), encodeMapValue(value), publishCommand); return future; } @Override public boolean updateEntryExpiration(K key, long ttl, TimeUnit ttlUnit, long maxIdleTime, TimeUnit maxIdleUnit) { return get(updateEntryExpirationAsync(key, ttl, ttlUnit, maxIdleTime, maxIdleUnit)); } @Override public RFuture updateEntryExpirationAsync(K key, long ttl, TimeUnit ttlUnit, long maxIdleTime, TimeUnit maxIdleUnit) { Duration ttld = null; if (ttl > 0) { ttld = Duration.ofMillis(ttlUnit.toMillis(ttl)); } Duration idled = null; if (maxIdleTime > 0) { idled = Duration.ofMillis(maxIdleUnit.toMillis(maxIdleTime)); } return expireEntryAsync(key, ttld, idled); } @Override public boolean expireEntry(K key, Duration ttl, Duration maxIdleTime) { return get(expireEntryAsync(key, ttl, maxIdleTime)); } @Override public RFuture expireEntryAsync(K key, Duration ttl, Duration maxIdleTime) { checkKey(key); long currentTime = System.currentTimeMillis(); long ttlTimeout = 0; if (ttl != null && !ttl.isZero()) { ttlTimeout = currentTime + ttl.toMillis(); } long maxIdleTimeout = 0; long maxIdleDelta = 0; if (maxIdleTime != null && !maxIdleTime.isZero()) { maxIdleDelta = maxIdleTime.toMillis(); maxIdleTimeout = currentTime + maxIdleDelta; } String name = getRawName(key); RFuture future = commandExecutor.evalWriteAsync(name, codec, RedisCommands.EVAL_BOOLEAN, "local value = redis.call('hget', KEYS[1], ARGV[5]); " + "local t, val;" + "if value == false then " + "return 0; " + "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 " + "return 0; " + "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[5], 'max-size')); " + "local mode = redis.call('hget', KEYS[5], 'mode'); " + "if maxSize ~= nil and maxSize ~= 0 then " + "local currentTime = tonumber(ARGV[1]); " + "if mode == false or mode == 'LRU' then " + "redis.call('zadd', KEYS[4], currentTime, ARGV[5]); " + "end; " + "if mode == 'LFU' then " + "redis.call('zincrby', KEYS[4], 1, ARGV[5]); " + "end; " + "end; " + "local value = struct.pack('dLc0', ARGV[4], string.len(val), val); " + "redis.call('hset', KEYS[1], ARGV[5], value); " + "return 1;", Arrays.asList(name, getTimeoutSetName(name), getIdleSetName(name), getLastAccessTimeSetName(name), getOptionsName(name)), System.currentTimeMillis(), ttlTimeout, maxIdleTimeout, maxIdleDelta, encodeMapKey(key)); return future; } public boolean expireEntryIfNotSet(K key, Duration ttl, Duration maxIdleTime) { return get(expireEntryIfNotSetAsync(key, ttl, maxIdleTime)); } public RFuture expireEntryIfNotSetAsync(K key, Duration ttl, Duration maxIdleTime) { checkKey(key); long currentTime = System.currentTimeMillis(); long ttlTimeout = 0; if (ttl != null && !ttl.isZero()) { ttlTimeout = currentTime + ttl.toMillis(); } long maxIdleTimeout = 0; long maxIdleDelta = 0; if (maxIdleTime != null && !maxIdleTime.isZero()) { maxIdleDelta = maxIdleTime.toMillis(); maxIdleTimeout = currentTime + maxIdleDelta; } String name = getRawName(key); RFuture future = commandExecutor.evalWriteAsync(name, codec, RedisCommands.EVAL_BOOLEAN, "local value = redis.call('hget', KEYS[1], ARGV[5]); " + "local t, val;" + "if value == false then " + "return 0; " + "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 ~= 92233720368547758 then " + "return 0; " + "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[5], 'max-size')); " + "local mode = redis.call('hget', KEYS[5], 'mode'); " + "if maxSize ~= nil and maxSize ~= 0 then " + "local currentTime = tonumber(ARGV[1]); " + "if mode == false or mode == 'LRU' then " + "redis.call('zadd', KEYS[4], currentTime, ARGV[5]); " + "end; " + "if mode == 'LFU' then " + "redis.call('zincrby', KEYS[4], 1, ARGV[5]); " + "end; " + "end; " + "local value = struct.pack('dLc0', ARGV[4], string.len(val), val); " + "redis.call('hset', KEYS[1], ARGV[5], value); " + "return 1;", Arrays.asList(name, getTimeoutSetName(name), getIdleSetName(name), getLastAccessTimeSetName(name), getOptionsName(name)), System.currentTimeMillis(), ttlTimeout, maxIdleTimeout, maxIdleDelta, encodeMapKey(key)); return future; } public int expireEntriesIfNotSet(Set keys, Duration ttl, Duration maxIdleTime) { return get(expireEntriesIfNotSetAsync(keys, ttl, maxIdleTime)); } public RFuture expireEntriesIfNotSetAsync(Set keys, Duration ttl, Duration maxIdleTime) { for (K key : keys) { checkKey(key); } long currentTime = System.currentTimeMillis(); long ttlTimeout = 0; if (ttl != null && !ttl.isZero()) { ttlTimeout = currentTime + ttl.toMillis(); } long maxIdleTimeout = 0; long maxIdleDelta = 0; if (maxIdleTime != null && !maxIdleTime.isZero()) { maxIdleDelta = maxIdleTime.toMillis(); maxIdleTimeout = currentTime + maxIdleDelta; } List args = new ArrayList<>(keys.size() + 3); args.add(System.currentTimeMillis()); args.add(ttlTimeout); args.add(maxIdleTimeout); args.add(maxIdleDelta); encodeMapKeys(args, keys); RFuture future = commandExecutor.evalWriteAsync(getRawName(), IntegerCodec.INSTANCE, RedisCommands.EVAL_INTEGER, "local counter = 0;" + "for i = 5, #ARGV, 1 do " + "local value = redis.call('hget', KEYS[1], ARGV[i]); " + "local t, val;" + "if value == false then " + "else " + "t, val = struct.unpack('dLc0', value); " + "local expireDate = 92233720368547758; " + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[i]); " + "if expireDateScore ~= false then " + "expireDate = tonumber(expireDateScore) " + "end; " + "if t ~= 0 then " + "local expireIdle = redis.call('zscore', KEYS[3], ARGV[i]); " + "if expireIdle ~= false then " + "expireDate = math.min(expireDate, tonumber(expireIdle)) " + "end; " + "end; " + "if expireDate ~= 92233720368547758 then " + "value = false; " + "end; " + "end; " + "if value ~= false then " + "if tonumber(ARGV[2]) > 0 then " + "redis.call('zadd', KEYS[2], ARGV[2], ARGV[i]); " + "else " + "redis.call('zrem', KEYS[2], ARGV[i]); " + "end; " + "if tonumber(ARGV[3]) > 0 then " + "redis.call('zadd', KEYS[3], ARGV[3], ARGV[i]); " + "else " + "redis.call('zrem', KEYS[3], ARGV[i]); " + "end; " + // last access time "local maxSize = tonumber(redis.call('hget', KEYS[5], 'max-size')); " + "local mode = redis.call('hget', KEYS[5], 'mode'); " + "if maxSize ~= nil and maxSize ~= 0 then " + "local currentTime = tonumber(ARGV[1]); " + "if mode == false or mode == 'LRU' then " + "redis.call('zadd', KEYS[4], currentTime, ARGV[i]); " + "end; " + "if mode == 'LFU' then " + "redis.call('zincrby', KEYS[4], 1, ARGV[i]); " + "end; " + "end; " + "counter = counter + 1; " + "local value = struct.pack('dLc0', ARGV[4], string.len(val), val); " + "redis.call('hset', KEYS[1], ARGV[i], value); " + "end;" + "end;" + "return counter;", Arrays.asList(getRawName(), timeoutSetName, idleSetName, lastAccessTimeSetName, optionsName), args.toArray()); return future; } @Override public int expireEntries(Set keys, Duration ttl, Duration maxIdleTime) { return get(expireEntriesAsync(keys, ttl, maxIdleTime)); } @Override public RFuture expireEntriesAsync(Set keys, Duration ttl, Duration maxIdleTime) { for (K key : keys) { checkKey(key); } long currentTime = System.currentTimeMillis(); long ttlTimeout = 0; if (ttl != null && !ttl.isZero()) { ttlTimeout = currentTime + ttl.toMillis(); } long maxIdleTimeout = 0; long maxIdleDelta = 0; if (maxIdleTime != null && !maxIdleTime.isZero()) { maxIdleDelta = maxIdleTime.toMillis(); maxIdleTimeout = currentTime + maxIdleDelta; } List args = new ArrayList<>(keys.size() + 3); args.add(System.currentTimeMillis()); args.add(ttlTimeout); args.add(maxIdleTimeout); args.add(maxIdleDelta); encodeMapKeys(args, keys); RFuture future = commandExecutor.evalWriteAsync(getRawName(), IntegerCodec.INSTANCE, RedisCommands.EVAL_INTEGER, "local counter = 0;" + "for i = 5, #ARGV, 1 do " + "local value = redis.call('hget', KEYS[1], ARGV[i]); " + "local t, val;" + "if value == false then " + "else " + "t, val = struct.unpack('dLc0', value); " + "local expireDate = 92233720368547758; " + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[i]); " + "if expireDateScore ~= false then " + "expireDate = tonumber(expireDateScore) " + "end; " + "if t ~= 0 then " + "local expireIdle = redis.call('zscore', KEYS[3], ARGV[i]); " + "if expireIdle ~= false then " + "expireDate = math.min(expireDate, tonumber(expireIdle)) " + "end; " + "end; " + "if expireDate <= tonumber(ARGV[1]) then " + "value = false; " + "end; " + "end; " + "if value ~= false then " + "if tonumber(ARGV[2]) > 0 then " + "redis.call('zadd', KEYS[2], ARGV[2], ARGV[i]); " + "else " + "redis.call('zrem', KEYS[2], ARGV[i]); " + "end; " + "if tonumber(ARGV[3]) > 0 then " + "redis.call('zadd', KEYS[3], ARGV[3], ARGV[i]); " + "else " + "redis.call('zrem', KEYS[3], ARGV[i]); " + "end; " + // last access time "local maxSize = tonumber(redis.call('hget', KEYS[5], 'max-size')); " + "local mode = redis.call('hget', KEYS[5], 'mode'); " + "if maxSize ~= nil and maxSize ~= 0 then " + "local currentTime = tonumber(ARGV[1]); " + "if mode == false or mode == 'LRU' then " + "redis.call('zadd', KEYS[4], currentTime, ARGV[i]); " + "end; " + "if mode == 'LFU' then " + "redis.call('zincrby', KEYS[4], 1, ARGV[i]); " + "end; " + "end; " + "counter = counter + 1; " + "local value = struct.pack('dLc0', ARGV[4], string.len(val), val); " + "redis.call('hset', KEYS[1], ARGV[i], value); " + "end;" + "end;" + "return counter;", Arrays.asList(getRawName(), timeoutSetName, idleSetName, lastAccessTimeSetName, optionsName), args.toArray()); 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(K key, 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; long ttlTimeoutDelta = 0; if (ttl > 0) { ttlTimeoutDelta = ttlUnit.toMillis(ttl); ttlTimeout = System.currentTimeMillis() + ttlTimeoutDelta; } 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, ttlTimeoutDelta); if (hasNoWriter()) { return future; } MapWriterTask.Add listener = new MapWriterTask.Add(key, value); return mapWriterFuture(future, listener); } protected RFuture putOperationAsync(K key, V value, long ttlTimeout, long maxIdleTimeout, long maxIdleDelta, long ttlTimeoutDelta) { String name = getRawName(key); RFuture future = commandExecutor.evalWriteAsync(name, 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 hasListeners = redis.call('hget', KEYS[8], 'has-listeners'); " // 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]; " + " local mode = redis.call('hget', KEYS[8], 'mode'); " + " if mode == false or mode == 'LRU' then " + " redis.call('zadd', lastAccessTimeSetName, currentTime, ARGV[5]); " + " end; " + " 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 and lruItem ~= ARGV[5] 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); " + "if lruItemValue ~= false and hasListeners ~= false then " + " local removedChannelName = KEYS[7]; " + "local ttl, obj = struct.unpack('dLc0', lruItemValue);" + " local msg = struct.pack('Lc0Lc0', string.len(lruItem), lruItem, string.len(obj), obj);" + " redis.call(ARGV[7], removedChannelName, msg); " + "end; " + " end; " + " end; " + " end; " + "if mode == 'LFU' then " + "redis.call('zincrby', lastAccessTimeSetName, 1, ARGV[5]); " + "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 " + "if hasListeners ~= false then " + "local msg = struct.pack('Lc0Lc0', string.len(ARGV[5]), ARGV[5], string.len(ARGV[6]), ARGV[6]); " + "redis.call(ARGV[7], KEYS[4], msg); " + "end;" + "return nil;" + "end; " + "local t, val = struct.unpack('dLc0', v); " + "if hasListeners ~= false then " + "local msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[5]), ARGV[5], string.len(ARGV[6]), ARGV[6], string.len(val), val); " + "redis.call(ARGV[7], KEYS[5], msg); " + "end;" + "return val", Arrays.asList(name, getTimeoutSetName(name), getIdleSetName(name), getCreatedChannelName(name), getUpdatedChannelName(name), getLastAccessTimeSetName(name), getRemovedChannelName(name), getOptionsName(name)), System.currentTimeMillis(), ttlTimeout, maxIdleTimeout, maxIdleDelta, encodeMapKey(key), encodeMapValue(value), publishCommand); return future; } @Override public V getWithTTLOnly(K key) { return get(getWithTTLOnlyAsync(key)); } private RFuture getWithTTLOnlyOperationAsync(K key) { String name = getRawName(key); return commandExecutor.evalReadAsync(name, 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 expireDate <= tonumber(ARGV[1]) then " + "return nil; " + "end; " + "return val; ", Arrays.asList(name, getTimeoutSetName(name)), System.currentTimeMillis(), encodeMapKey(key)); } @Override public RFuture getWithTTLOnlyAsync(K key) { checkKey(key); RFuture future = getWithTTLOnlyOperationAsync(key); if (hasNoLoader()) { return future; } CompletionStage f = future.thenCompose(res -> { if (res == null) { return loadValue(key, false); } return CompletableFuture.completedFuture(res); }); return new CompletableFutureWrapper<>(f); } @Override public Map getAllWithTTLOnly(Set keys) { return get(getAllWithTTLOnlyAsync(keys)); } @Override public RFuture> getAllWithTTLOnlyAsync(Set keys) { if (keys.isEmpty()) { return new CompletableFutureWrapper<>(Collections.emptyMap()); } RFuture> future = getAllWithTTLOnlyOperationAsync(keys); if (hasNoLoader()) { return future; } CompletionStage> f = future.thenCompose(res -> { if (!res.keySet().containsAll(keys)) { Set newKeys = new HashSet(keys); newKeys.removeAll(res.keySet()); CompletionStage> ff = loadAllMapAsync(newKeys.spliterator(), false, 1); return ff.thenApply(map -> { res.putAll(map); return res; }); } return CompletableFuture.completedFuture(res); }); return new CompletableFutureWrapper<>(f); } protected RFuture> getAllWithTTLOnlyOperationAsync(Set keys) { List args = new ArrayList<>(keys.size() + 1); List plainKeys = new ArrayList<>(keys); args.add(System.currentTimeMillis()); encodeMapKeys(args, keys); return commandExecutor.evalReadAsync(getRawName(), codec, new RedisCommand>("EVAL", new MapValueDecoder(new MapGetAllDecoder(plainKeys, 0))), "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 = {}; " + "local values = redis.call('hmget', KEYS[1], unpack(ARGV));" + "for i = 1, #values, 1 do " + " local value = values[i]; " + " map[i] = false;" + " 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; " + " end; " + "end; " + "return map;", Arrays.asList(getRawName(), timeoutSetName), args.toArray()); } @Override public long remainTimeToLive(K key) { return get(remainTimeToLiveAsync(key)); } @Override public RFuture remainTimeToLiveAsync(K key) { checkKey(key); String name = getRawName(key); return commandExecutor.evalWriteAsync(name, 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 expireDate - ARGV[1]; " + "else " + "return -2; " + "end; " + "return val; ", Arrays.asList(name, getTimeoutSetName(name), getIdleSetName(name)), System.currentTimeMillis(), encodeMapKey(key)); } String getTimeoutSetName(String name) { return prefixName("redisson__timeout__set", name); } String getLastAccessTimeSetName(String name) { return prefixName("redisson__map_cache__last_access__set", name); } String getIdleSetName(String name) { return prefixName("redisson__idle__set", name); } String getOptionsName(String name) { return suffixName(name, "redisson_options"); } String getCreatedChannelName(String name) { return prefixName("redisson_map_cache_created", name); } String getCreatedChannelName() { return prefixName("redisson_map_cache_created", getRawName()); } String getUpdatedChannelName() { return prefixName("redisson_map_cache_updated", getRawName()); } String getUpdatedChannelName(String name) { return prefixName("redisson_map_cache_updated", name); } String getExpiredChannelName(String name) { return prefixName("redisson_map_cache_expired", name); } String getExpiredChannelName() { return prefixName("redisson_map_cache_expired", getRawName()); } String getRemovedChannelName() { return prefixName("redisson_map_cache_removed", getRawName()); } String getRemovedChannelName(String name) { return prefixName("redisson_map_cache_removed", name); } @Override protected RFuture removeOperationAsync(K key) { String name = getRawName(key); return commandExecutor.evalWriteAsync(name, 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 hasListeners = redis.call('hget', KEYS[6], 'has-listeners'); " + "if hasListeners ~= false then " + "local msg = struct.pack('Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(val), val); " + "redis.call(ARGV[3], KEYS[4], msg); " + "end; " + "return val; ", Arrays.asList(name, getTimeoutSetName(name), getIdleSetName(name), getRemovedChannelName(name), getLastAccessTimeSetName(name), getOptionsName(name)), System.currentTimeMillis(), encodeMapKey(key), publishCommand); } @Override protected RFuture> fastRemoveOperationBatchAsync(K... keys) { List args = new ArrayList<>(keys.length); args.add(publishCommand); encodeMapKeys(args, Arrays.asList(keys)); RFuture> future = commandExecutor.evalWriteAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_LIST, "local publishCommand = table.remove(ARGV, 1); " + "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)); " + "local hasListeners = redis.call('hget', KEYS[6], 'has-listeners'); " + "if hasListeners ~= false then " + "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(publishCommand, 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(getRawName(), timeoutSetName, idleSetName, getRemovedChannelName(), lastAccessTimeSetName, optionsName), args.toArray()); return future; } @Override protected RFuture fastRemoveOperationAsync(K... keys) { List params = new ArrayList<>(keys.length); params.add(publishCommand); encodeMapKeys(params, Arrays.asList(keys)); return commandExecutor.evalWriteAsync(getRawName(), codec, RedisCommands.EVAL_LONG, "local publishCommand = table.remove(ARGV, 1); " + "local maxSize = tonumber(redis.call('hget', KEYS[6], 'max-size')); " + "for i=1, #ARGV, 5000 do " + "if maxSize ~= nil and maxSize ~= 0 then " + "redis.call('zrem', KEYS[5], unpack(ARGV, i, math.min(i+4999, table.getn(ARGV)))) " + "end; " + "redis.call('zrem', KEYS[3], unpack(ARGV, i, math.min(i+4999, table.getn(ARGV)))) " + "redis.call('zrem', KEYS[2], unpack(ARGV, i, math.min(i+4999, table.getn(ARGV)))) " + "end; " + "local hasListeners = redis.call('hget', KEYS[6], 'has-listeners'); " + "if hasListeners ~= false then " + "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(publishCommand, KEYS[4], msg); " + "end; " + "end; " + "end; " + "local n = 0;" + "for i=1, #ARGV, 5000 do " + "n = n + redis.call('hdel', KEYS[1], unpack(ARGV, i, math.min(i+4999, table.getn(ARGV)))) " + "end; " + "return n; ", Arrays.asList(getRawName(), timeoutSetName, idleSetName, getRemovedChannelName(), lastAccessTimeSetName, optionsName), params.toArray()); } @Override public RFuture> scanKeyIteratorAsync(String name, RedisClient client, String startPos, String pattern, int count) { List params = new ArrayList<>(); params.add(System.currentTimeMillis()); params.add(startPos); if (pattern != null) { params.add(pattern); } params.add(count); RedisCommand> evalScan = new RedisCommand>("EVAL", new ListMultiDecoder2(new MapCacheKeyScanResultDecoder(), new ObjectDecoder<>(codec.getMapKeyDecoder()))); RFuture> future = commandExecutor.evalReadAsync(client, name, codec, evalScan, "local result = {}; " + "local idleKeys = {}; " + "local res; " + "if (#ARGV == 4) then " + " res = redis.call('hscan', KEYS[1], ARGV[2], 'match', ARGV[3], 'count', ARGV[4]); " + "else " + " res = redis.call('hscan', KEYS[1], ARGV[2], 'count', ARGV[3]); " + "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); " + "end; " + "end; " + "end;" + "return {res[1], result, idleKeys};", Arrays.asList(name, getTimeoutSetName(name), getIdleSetName(name)), params.toArray()); CompletionStage> f = future.thenApply(res -> { if (res.getIdleKeys().isEmpty()) { return res; } List args = new ArrayList(res.getIdleKeys().size() + 1); args.add(System.currentTimeMillis()); encodeMapKeys(args, res.getIdleKeys()); commandExecutor.evalWriteAsync(name, codec, new RedisCommand>("EVAL", new MapValueDecoder(new MapGetAllDecoder(args, 1))), "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 res; }); return new CompletableFutureWrapper<>((CompletionStage>) (Object) f); } private static final RedisCommand> SCAN = new RedisCommand>("EVAL", new ListMultiDecoder2( new MapCacheScanResultReplayDecoder(), new ObjectMapDecoder(true))); @Override public RFuture>> scanIteratorAsync(String name, RedisClient client, String startPos, String pattern, int count) { List params = new ArrayList(); params.add(System.currentTimeMillis()); params.add(startPos); if (pattern != null) { params.add(pattern); } params.add(count); RFuture> future = commandExecutor.evalReadAsync(client, name, codec, SCAN, "local result = {}; " + "local idleKeys = {}; " + "local res; " + "if (#ARGV == 4) then " + " res = redis.call('hscan', KEYS[1], ARGV[2], 'match', ARGV[3], 'count', ARGV[4]); " + "else " + " res = redis.call('hscan', KEYS[1], ARGV[2], 'count', ARGV[3]); " + "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()); CompletionStage> f = future.thenApply(res -> { if (res.getIdleKeys().isEmpty()) { return res; } List args = new ArrayList(res.getIdleKeys().size() + 1); args.add(System.currentTimeMillis()); encodeMapKeys(args, res.getIdleKeys()); commandExecutor.evalWriteAsync(name, codec, new RedisCommand>("EVAL", new MapValueDecoder(new MapGetAllDecoder(args, 1))), "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 res; }); return new CompletableFutureWrapper<>((CompletionStage>>) (Object) f); } @Override protected RFuture fastPutOperationAsync(K key, V value) { String name = getRawName(key); return commandExecutor.evalWriteAsync(name, 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 " + "redis.call('zrem', KEYS[2], ARGV[2]); " + "redis.call('zrem', KEYS[3], ARGV[2]); " + "insertable = true; " + "end; " + "end; " + "local val = struct.pack('dLc0', 0, string.len(ARGV[3]), ARGV[3]); " + "redis.call('hset', KEYS[1], ARGV[2], val); " + "local hasListeners = redis.call('hget', KEYS[8], 'has-listeners'); " + // 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]; " + "local mode = redis.call('hget', KEYS[8], 'mode'); " + "if mode == false or mode == 'LRU' then " + "redis.call('zadd', lastAccessTimeSetName, currentTime, ARGV[2]); " + "end; " + " 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 and lruItem ~= ARGV[2] 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); " + " if lruItemValue ~= false and hasListeners ~= false then " + " local removedChannelName = KEYS[7]; " + "local ttl, obj = struct.unpack('dLc0', lruItemValue);" + " local msg = struct.pack('Lc0Lc0', string.len(lruItem), lruItem, string.len(obj), obj);" + " redis.call(ARGV[4], removedChannelName, msg); " + "end; " + " end; " + " end; " + " end; " + "if mode == 'LFU' then " + "redis.call('zincrby', lastAccessTimeSetName, 1, ARGV[2]); " + "end; " + "end; " + "if insertable == true then " + "if hasListeners ~= false then " + "local msg = struct.pack('Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[3]), ARGV[3]); " + "redis.call(ARGV[4], KEYS[4], msg); " + "end; " + "return 1;" + "else " + "if hasListeners ~= false then " + "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(ARGV[4], KEYS[5], msg); " + "end; " + "return 0;" + "end;", Arrays.asList(getRawName(key), getTimeoutSetName(name), getIdleSetName(name), getCreatedChannelName(name), getUpdatedChannelName(name), getLastAccessTimeSetName(name), getRemovedChannelName(name), getOptionsName(name)), System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value), publishCommand); } @Override protected RFuture fastPutIfExistsOperationAsync(K key, V value) { String name = getRawName(key); return commandExecutor.evalWriteAsync(name, 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 hasListeners = redis.call('hget', KEYS[7], 'has-listeners'); " + "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); " + "if hasListeners ~= false then " + "local msg = struct.pack('Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[3]), ARGV[3]); " + "redis.call(ARGV[4], KEYS[4], msg); " + "end; " + // last access time "if maxSize ~= nil and maxSize ~= 0 then " + "local mode = redis.call('hget', KEYS[7], 'mode'); " + "if mode == false or mode == 'LRU' then " + "redis.call('zadd', lastAccessTimeSetName, currentTime, ARGV[2]); " + "end; " + " 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 and lruItem ~= ARGV[2] 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); " + " if lruItemValue ~= false and hasListeners ~= false then " + " local removedChannelName = KEYS[6]; " + "local ttl, obj = struct.unpack('dLc0', lruItemValue);" + " local msg = struct.pack('Lc0Lc0', string.len(lruItem), lruItem, string.len(obj), obj);" + " redis.call(ARGV[4], removedChannelName, msg); " + "end; " + " end; " + " end; " + " end; " + "if mode == 'LFU' then " + "redis.call('zincrby', lastAccessTimeSetName, 1, ARGV[2]); " + "end; " + "end; " + "return 1; " + "end; " + "return 0; ", Arrays.asList(name, getTimeoutSetName(name), getIdleSetName(name), getUpdatedChannelName(name), getLastAccessTimeSetName(name), getRemovedChannelName(name), getOptionsName(name)), System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value), publishCommand); } @Override protected RFuture fastPutIfAbsentOperationAsync(K key, V value) { String name = getRawName(key); return commandExecutor.evalWriteAsync(name, 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 hasListeners = redis.call('hget', KEYS[7], 'has-listeners'); " + "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); " + "if hasListeners ~= false then " + "local msg = struct.pack('Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[3]), ARGV[3]); " + "redis.call(ARGV[4], KEYS[4], msg); " + "end; " + // last access time "if maxSize ~= nil and maxSize ~= 0 then " + "local mode = redis.call('hget', KEYS[7], 'mode'); " + "if mode == false or mode == 'LRU' then " + "redis.call('zadd', lastAccessTimeSetName, currentTime, ARGV[2]); " + "end; " + " 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 and lruItem ~= ARGV[2] 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); " + " if lruItemValue ~= false and hasListeners ~= false then " + " local removedChannelName = KEYS[6]; " + "local ttl, obj = struct.unpack('dLc0', lruItemValue);" + " local msg = struct.pack('Lc0Lc0', string.len(lruItem), lruItem, string.len(obj), obj);" + " redis.call(ARGV[4], removedChannelName, msg); " + "end; " + " end; " + " end; " + " end; " + "if mode == 'LFU' then " + "redis.call('zincrby', lastAccessTimeSetName, 1, ARGV[2]); " + "end; " + "end; " + "return 1; " + "end; " + "if maxSize ~= nil and maxSize ~= 0 then " + "local mode = redis.call('hget', KEYS[7], 'mode'); " + "if mode == false or mode == 'LRU' then " + "redis.call('zadd', lastAccessTimeSetName, currentTime, ARGV[2]); " + "else " + "redis.call('zincrby', lastAccessTimeSetName, 1, ARGV[2]); " + "end; " + "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); " + "if hasListeners ~= false then " + "local msg = struct.pack('Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[3]), ARGV[3]); " + "redis.call(ARGV[4], KEYS[4], msg); " + "end; " + "return 1; ", Arrays.asList(name, getTimeoutSetName(name), getIdleSetName(name), getCreatedChannelName(name), getLastAccessTimeSetName(name), getRemovedChannelName(name), getOptionsName(name)), System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value), publishCommand); } @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(K key, 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; } String name = getRawName(key); RFuture future = commandExecutor.evalWriteAsync(name, 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; " + "local hasListeners = redis.call('hget', KEYS[7], 'has-listeners'); " + // 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]; " + "local mode = redis.call('hget', KEYS[7], 'mode'); " + "if mode == false or mode == 'LRU' then " + "redis.call('zadd', lastAccessTimeSetName, currentTime, ARGV[5]); " + "end; " + " 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 and lruItem ~= ARGV[5] 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); " + " if lruItemValue ~= false and hasListeners ~= false then " + " local removedChannelName = KEYS[6]; " + "local ttl, obj = struct.unpack('dLc0', lruItemValue);" + " local msg = struct.pack('Lc0Lc0', string.len(lruItem), lruItem, string.len(obj), obj);" + " redis.call(ARGV[7], removedChannelName, msg); " + "end; " + " end; " + " end; " + " end; " + "if mode == 'LFU' then " + "redis.call('zincrby', lastAccessTimeSetName, 1, ARGV[5]); " + "end; " + " end; " + // value " local val = struct.pack('dLc0', ARGV[4], string.len(ARGV[6]), ARGV[6]); " + " redis.call('hset', KEYS[1], ARGV[5], val); " + "if hasListeners ~= false then " + "local msg = struct.pack('Lc0Lc0', string.len(ARGV[5]), ARGV[5], string.len(ARGV[6]), ARGV[6]); " + "redis.call(ARGV[7], KEYS[4], msg); " + "end; " + " return 1; " + "else " + " return 0; " + "end; ", Arrays.asList(name, getTimeoutSetName(name), getIdleSetName(name), getCreatedChannelName(name), getLastAccessTimeSetName(name), getRemovedChannelName(name), getOptionsName(name)), System.currentTimeMillis(), ttlTimeout, maxIdleTimeout, maxIdleDelta, encodeMapKey(key), encodeMapValue(value), publishCommand); if (hasNoWriter()) { return future; } MapWriterTask.Add listener = new MapWriterTask.Add(key, value); return mapWriterFuture(future, listener, Function.identity()); } @Override protected RFuture replaceOperationAsync(K key, V oldValue, V newValue) { String name = getRawName(key); return commandExecutor.evalWriteAsync(name, 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 tonumber(expireIdle) > tonumber(ARGV[1]) then " + " redis.call('zadd', KEYS[3], t + tonumber(ARGV[1]), ARGV[2]); " + " end ;" + " if expireIdle ~= false then " + " expireDate = math.min(expireDate, tonumber(expireIdle)) " + " end; " + "end; " + "if expireDate > tonumber(ARGV[1]) and val == ARGV[3] then " + "local hasListeners = redis.call('hget', KEYS[5], 'has-listeners'); " + "if hasListeners ~= false 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(ARGV[5], KEYS[4], msg); " + "end; " + " 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(name, getTimeoutSetName(name), getIdleSetName(name), getUpdatedChannelName(name), getOptionsName(name)), System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(oldValue), encodeMapValue(newValue), publishCommand); } @Override protected RFuture fastReplaceOperationAsync(K key, V value) { String name = getRawName(key); return commandExecutor.evalWriteAsync(name, 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 tonumber(expireIdle) > tonumber(ARGV[1]) then " + " redis.call('zadd', KEYS[3], t + tonumber(ARGV[1]), ARGV[2]); " + " end ;" + " if expireIdle ~= false then " + " expireDate = math.min(expireDate, tonumber(expireIdle)) " + " end; " + "end; " + "if expireDate <= tonumber(ARGV[1]) then " + " return 0; " + "end; " + "local value = struct.pack('dLc0', t, string.len(ARGV[3]), ARGV[3]); " + "redis.call('hset', KEYS[1], ARGV[2], value); " + "local hasListeners = redis.call('hget', KEYS[5], 'has-listeners'); " + "if hasListeners ~= false then " + "local msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[3]), ARGV[3], string.len(val), val); " + "redis.call(ARGV[4], KEYS[4], msg); " + "end; " + "return 1; ", Arrays.asList(name, getTimeoutSetName(name), getIdleSetName(name), getUpdatedChannelName(name), getOptionsName(name)), System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value), publishCommand); } @Override protected RFuture replaceOperationAsync(K key, V value) { String name = getRawName(key); return commandExecutor.evalWriteAsync(name, 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 tonumber(expireIdle) > tonumber(ARGV[1]) then " + " redis.call('zadd', KEYS[3], t + tonumber(ARGV[1]), ARGV[2]); " + " end ;" + " if expireIdle ~= false then " + " expireDate = math.min(expireDate, tonumber(expireIdle)) " + " end; " + "end; " + "if expireDate <= tonumber(ARGV[1]) then " + " return nil; " + "end; " + "local hasListeners = redis.call('hget', KEYS[5], 'has-listeners'); " + "local value = struct.pack('dLc0', t, string.len(ARGV[3]), ARGV[3]); " + "redis.call('hset', KEYS[1], ARGV[2], value); " + "if hasListeners ~= false then " + "local msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[3]), ARGV[3], string.len(val), val); " + "redis.call(ARGV[4], KEYS[4], msg); " + "end; " + "return val; ", Arrays.asList(name, getTimeoutSetName(name), getIdleSetName(name), getUpdatedChannelName(name), getOptionsName(name)), System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value), publishCommand); } @Override protected RFuture putAllOperationAsync(Map map) { List params = new ArrayList(map.size()*2 + 1); params.add(System.currentTimeMillis()); params.add(publishCommand); encodeMapKeys(params, map); return commandExecutor.evalWriteAsync(getRawName(), codec, RedisCommands.EVAL_VOID, "local currentTime = tonumber(table.remove(ARGV, 1)); " + // index is the first parameter "local publishCommand = tonumber(table.remove(ARGV, 1)); " + // index is the first parameter "local maxSize = tonumber(redis.call('hget', KEYS[8], 'max-size'));" + "local mode = redis.call('hget', KEYS[8], 'mode'); " + "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 hasListeners = redis.call('hget', KEYS[8], 'has-listeners'); " + "local lastAccessTimeSetName = KEYS[6];" + "if exists == false then" + " if maxSize ~= nil and maxSize ~= 0 then " + "if mode == false or mode == 'LRU' then " + "redis.call('zadd', lastAccessTimeSetName, currentTime, key); " + "end; " + " 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 and lruItem ~= key 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);" + " if lruItemValue ~= false and hasListeners ~= false then " + " local removedChannelName = KEYS[7];" + "local ttl, obj = struct.unpack('dLc0', lruItemValue);" + " local msg = struct.pack('Lc0Lc0', string.len(lruItem), lruItem, string.len(obj), obj);" + " redis.call(publishCommand, removedChannelName, msg);" + "end; " + " end;" + " end" + " end;" + "if mode == 'LFU' then " + "redis.call('zincrby', lastAccessTimeSetName, 1, key); " + "end; " + " end;" + "if hasListeners ~= false then " + " local msg = struct.pack('Lc0Lc0', string.len(key), key, string.len(value), value);" + " redis.call(publishCommand, KEYS[4], msg);" + "end;" + "else " + "if hasListeners ~= false then " + "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(publishCommand, KEYS[5], msg);" + "end; " + " if maxSize ~= nil and maxSize ~= 0 then " + "if mode == false or mode == 'LRU' then " + "redis.call('zadd', lastAccessTimeSetName, currentTime, key); " + "else " + "redis.call('zincrby', lastAccessTimeSetName, 1, key); " + "end; " + " end;" + "end;" + "end;" + "end;", Arrays.asList(getRawName(), timeoutSetName, idleSetName, getCreatedChannelName(), getUpdatedChannelName(), lastAccessTimeSetName, getRemovedChannelName(), optionsName), params.toArray()); } private RFuture putAllOperationAsync(Map map, long ttl, TimeUnit ttlUnit) { List params = new ArrayList(map.size()*2 + 2); params.add(System.currentTimeMillis()); long ttlTimeout = 0; if (ttl > 0) { ttlTimeout = System.currentTimeMillis() + ttlUnit.toMillis(ttl); } params.add(ttlTimeout); params.add(publishCommand); encodeMapKeys(params, map); return commandExecutor.evalWriteAsync(getRawName(), codec, RedisCommands.EVAL_VOID, "local currentTime = tonumber(table.remove(ARGV, 1)); " + // index is the first parameter "local ttl = table.remove(ARGV, 1); " + // ttl is the second parameter "local publishCommand = table.remove(ARGV, 1); " + "local ttlNumber = tonumber(ttl); " + "local maxSize = tonumber(redis.call('hget', KEYS[8], 'max-size'));" + "local mode = redis.call('hget', KEYS[8], 'mode'); " + "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;" + "" + "if ttlNumber > 0 then " + " redis.call('zadd', KEYS[2], ttl, key); " + "else " + " redis.call('zrem', KEYS[2], key); " + "end; " + "" + "local newvalue = struct.pack('dLc0', 0, string.len(value), value);" + "redis.call('hset', KEYS[1], key, newvalue);" + "local hasListeners = redis.call('hget', KEYS[8], 'has-listeners'); " + "local lastAccessTimeSetName = KEYS[6];" + "if exists == false then" + " if maxSize ~= nil and maxSize ~= 0 then " + "if mode == false or mode == 'LRU' then " + "redis.call('zadd', lastAccessTimeSetName, currentTime, key); " + "end; " + " 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 and lruItem ~= key 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);" + " if lruItemValue ~= false and hasListeners ~= false then " + " local removedChannelName = KEYS[7];" + "local ttl, obj = struct.unpack('dLc0', lruItemValue);" + " local msg = struct.pack('Lc0Lc0', string.len(lruItem), lruItem, string.len(obj), obj);" + " redis.call(publishCommand, removedChannelName, msg);" + "end; " + " end;" + " end" + " end;" + "if mode == 'LFU' then " + "redis.call('zincrby', lastAccessTimeSetName, 1, key); " + "end; " + " end;" + "if hasListeners ~= false then " + "local msg = struct.pack('Lc0Lc0', string.len(key), key, string.len(value), value);" + "redis.call(publishCommand, KEYS[4], msg);" + "end; " + "else " + "if hasListeners ~= false then " + "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(publishCommand, KEYS[5], msg);" + "end; " + " if maxSize ~= nil and maxSize ~= 0 then " + "if mode == false or mode == 'LRU' then " + "redis.call('zadd', lastAccessTimeSetName, currentTime, key); " + "else " + "redis.call('zincrby', lastAccessTimeSetName, 1, key); " + "end; " + " end;" + "end;" + "end;" + "end;", Arrays.asList(getRawName(), timeoutSetName, idleSetName, getCreatedChannelName(), getUpdatedChannelName(), lastAccessTimeSetName, getRemovedChannelName(), optionsName), params.toArray()); } private volatile MapCacheEventCodec.OSType osType; private volatile Codec topicCodec; @Override public int addListener(MapEntryListener listener) { return get(addListenerAsync(listener)); } protected RTopic getTopic(String name) { if (getSubscribeService().isShardingSupported()) { return RedissonShardedTopic.createRaw(topicCodec, commandExecutor, name); } return RedissonTopic.createRaw(topicCodec, commandExecutor, name); } @Override public RFuture addListenerAsync(MapEntryListener listener) { Objects.requireNonNull(listener); CompletionStage osTypeFuture = CompletableFuture.completedFuture(null); if (osType == null) { RFuture> serverFuture = commandExecutor.readAsync((String) null, StringCodec.INSTANCE, RedisCommands.INFO_SERVER); osTypeFuture = serverFuture.thenAccept(res -> { String os = res.get("os"); if (os == null || os.contains("Windows")) { osType = BaseEventCodec.OSType.WINDOWS; } else if (os.contains("NONSTOP")) { osType = BaseEventCodec.OSType.HPNONSTOP; } topicCodec = new MapCacheEventCodec(codec, osType); }).thenCompose(r -> { return commandExecutor.writeAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.HSET_VOID, optionsName, "has-listeners", 1); }); } CompletionStage f = osTypeFuture.thenCompose(osType -> { if (listener instanceof EntryRemovedListener) { RTopic topic = getTopic(getRemovedChannelName()); return topic.addListenerAsync(List.class, new MessageListener>() { @Override public void onMessage(CharSequence 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 = getTopic(getCreatedChannelName()); return topic.addListenerAsync(List.class, new MessageListener>() { @Override public void onMessage(CharSequence 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 = getTopic(getUpdatedChannelName()); return topic.addListenerAsync(List.class, new MessageListener>() { @Override public void onMessage(CharSequence 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 = getTopic(getExpiredChannelName()); return topic.addListenerAsync(List.class, new MessageListener>() { @Override public void onMessage(CharSequence 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); } }); } CompletableFuture res = new CompletableFuture<>(); res.completeExceptionally(new IllegalArgumentException("Wrong listener type " + listener.getClass())); return res; }); f = f.thenApply(id -> { if (listener instanceof EntryRemovedListener) { addListenerId(getRemovedChannelName(), id); } if (listener instanceof EntryUpdatedListener) { addListenerId(getUpdatedChannelName(), id); } if (listener instanceof EntryCreatedListener) { addListenerId(getCreatedChannelName(), id); } if (listener instanceof EntryExpiredListener) { addListenerId(getExpiredChannelName(), id); } return id; }); return new CompletableFutureWrapper<>(f); } @Override public void removeListener(int listenerId) { super.removeListener(listenerId); String topicName = getNameByListenerId(listenerId); if (topicName != null) { RTopic topic = getTopic(topicName); removeListenerId(topicName, listenerId); topic.removeListener(listenerId); } } @Override public RFuture removeListenerAsync(int listenerId) { CompletionStage r = super.removeListenerAsync(listenerId); r = r.thenCompose(v -> { String topicName = getNameByListenerId(listenerId); if (topicName != null) { RTopic topic = getTopic(topicName); removeListenerId(topicName, listenerId); return topic.removeListenerAsync(listenerId); } return CompletableFuture.completedFuture(null); }); return new CompletableFutureWrapper<>(r); } @Override public RFuture sizeInMemoryAsync() { List keys = Arrays.asList(getRawName(), timeoutSetName, idleSetName, lastAccessTimeSetName, optionsName); return super.sizeInMemoryAsync(keys); } @Override public RFuture copyAsync(List keys, int database, boolean replace) { String newName = (String) keys.get(1); List kks = Arrays.asList(getRawName(), timeoutSetName, idleSetName, lastAccessTimeSetName, optionsName, newName, getTimeoutSetName(newName), getIdleSetName(newName), getLastAccessTimeSetName(newName), getOptionsName(newName)); return super.copyAsync(kks, database, replace); } @Override public RFuture renameAsync(String nn) { String newName = mapName(nn); List kks = Arrays.asList(getRawName(), timeoutSetName, idleSetName, lastAccessTimeSetName, optionsName, newName, getTimeoutSetName(newName), getIdleSetName(newName), getLastAccessTimeSetName(newName), getOptionsName(newName)); return renameAsync(commandExecutor, kks, () -> { setName(nn); this.timeoutSetName = getTimeoutSetName(getRawName()); this.idleSetName = getIdleSetName(getRawName()); this.lastAccessTimeSetName = getLastAccessTimeSetName(getRawName()); this.optionsName = getOptionsName(getRawName()); }); } @Override public RFuture renamenxAsync(String nn) { String newName = mapName(nn); List kks = Arrays.asList(getRawName(), timeoutSetName, idleSetName, lastAccessTimeSetName, optionsName, newName, getTimeoutSetName(newName), getIdleSetName(newName), getLastAccessTimeSetName(newName), getOptionsName(newName)); return renamenxAsync(commandExecutor, kks, value -> { if (value) { setName(nn); this.timeoutSetName = getTimeoutSetName(getRawName()); this.idleSetName = getIdleSetName(getRawName()); this.lastAccessTimeSetName = getLastAccessTimeSetName(getRawName()); this.optionsName = getOptionsName(getRawName()); } }); } @Override public void clear() { get(clearAsync()); } @Override public RFuture clearAsync() { return deleteAsync(getRawName(), timeoutSetName, idleSetName, lastAccessTimeSetName); } @Override public RFuture deleteAsync() { return deleteAsync(getRawName(), timeoutSetName, idleSetName, lastAccessTimeSetName, optionsName); } @Override public RFuture expireAsync(long timeToLive, TimeUnit timeUnit, String param, String... keys) { return commandExecutor.evalWriteAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, "local maxSize = tonumber(redis.call('hget', KEYS[5], 'max-size')); " + "if maxSize ~= nil and maxSize ~= 0 then " + "redis.call('zadd', KEYS[4], 92233720368547758, 'redisson__expiretag'); " + "if ARGV[2] ~= '' then " + "redis.call('pexpire', KEYS[5], ARGV[1], ARGV[2]); " + "redis.call('pexpire', KEYS[4], ARGV[1], ARGV[2]); " + "else " + "redis.call('pexpire', KEYS[5], ARGV[1]); " + "redis.call('pexpire', KEYS[4], ARGV[1]); " + "end; " + "end; " + "redis.call('zadd', KEYS[2], 92233720368547758, 'redisson__expiretag'); " + "redis.call('zadd', KEYS[3], 92233720368547758, 'redisson__expiretag'); " + "if ARGV[2] ~= '' then " + "redis.call('pexpire', KEYS[2], ARGV[1], ARGV[2]); " + "redis.call('pexpire', KEYS[3], ARGV[1], ARGV[2]); " + "return redis.call('pexpireat', KEYS[1], ARGV[1], ARGV[2]); " + "end; " + "redis.call('pexpire', KEYS[2], ARGV[1]); " + "redis.call('pexpire', KEYS[3], ARGV[1]); " + "return redis.call('pexpire', KEYS[1], ARGV[1]); ", Arrays.asList(getRawName(), timeoutSetName, idleSetName, lastAccessTimeSetName, optionsName), timeUnit.toMillis(timeToLive), param); } @Override protected RFuture expireAtAsync(long timestamp, String param, String... keys) { return commandExecutor.evalWriteAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, "local maxSize = tonumber(redis.call('hget', KEYS[5], 'max-size')); " + "if maxSize ~= nil and maxSize ~= 0 then " + "redis.call('zadd', KEYS[4], 92233720368547758, 'redisson__expiretag'); " + "if ARGV[2] ~= '' then " + "redis.call('pexpireat', KEYS[5], ARGV[1], ARGV[2]); " + "redis.call('pexpireat', KEYS[4], ARGV[1], ARGV[2]); " + "else " + "redis.call('pexpireat', KEYS[5], ARGV[1]); " + "redis.call('pexpireat', KEYS[4], ARGV[1]); " + "end; " + "end; " + "redis.call('zadd', KEYS[2], 92233720368547758, 'redisson__expiretag'); " + "redis.call('zadd', KEYS[3], 92233720368547758, 'redisson__expiretag'); " + "if ARGV[2] ~= '' then " + "redis.call('pexpireat', KEYS[2], ARGV[1], ARGV[2]); " + "redis.call('pexpireat', KEYS[3], ARGV[1], ARGV[2]); " + "return redis.call('pexpireat', KEYS[1], ARGV[1], ARGV[2]); " + "end; " + "redis.call('pexpireat', KEYS[2], ARGV[1]); " + "redis.call('pexpireat', KEYS[3], ARGV[1]); " + "return redis.call('pexpireat', KEYS[1], ARGV[1]); ", Arrays.asList(getRawName(), timeoutSetName, idleSetName, lastAccessTimeSetName, optionsName), timestamp, param); } @Override public RFuture clearExpireAsync() { return commandExecutor.evalWriteAsync(getRawName(), 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(getRawName(), timeoutSetName, idleSetName, lastAccessTimeSetName, optionsName)); } @Override public RFuture> readAllKeySetAsync() { return commandExecutor.evalWriteAsync(getRawName(), 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 " + "local mode = redis.call('hget', KEYS[5], 'mode'); " + "if mode == false or mode == 'LRU' then " + "redis.call('zadd', KEYS[4], tonumber(ARGV[1]), key); " + "else " + "redis.call('zincrby', KEYS[4], 1, key); " + "end; " + "end; " + "end; " + "expireDate = math.min(expireDate, tonumber(expireIdle)) " + "end; " + "end; " + "if expireDate > tonumber(ARGV[1]) then " + "table.insert(result, key); " + "end; " + "end; " + "end;" + "return result;", Arrays.asList(getRawName(), timeoutSetName, idleSetName, lastAccessTimeSetName, optionsName), System.currentTimeMillis()); } @Override public RFuture> randomKeysAsync(int count) { return commandExecutor.evalWriteAsync(getRawName(), codec, RedisCommands.EVAL_MAP_KEY_SET, "local s = redis.call('hrandfield', KEYS[1], ARGV[2], 'withvalues'); " + "if s == false then " + "return {};" + "end; " + "local maxSize = tonumber(redis.call('hget', KEYS[5], 'max-size'));" + "local result = {}; " + "for i, v in ipairs(s) do " + "if i % 2 == 0 then " + "local t, val = struct.unpack('dLc0', v); " + "local key = s[i-1];" + "local expireDate = 92233720368547758; " + "local expireDateScore = redis.call('zscore', KEYS[2], key); " + "if expireDateScore ~= false then " + "expireDate = tonumber(expireDateScore) " + "end; " + "if t ~= 0 then " + "local expireIdle = redis.call('zscore', KEYS[3], key); " + "if expireIdle ~= false then " + "if tonumber(expireIdle) > tonumber(ARGV[1]) then " + "redis.call('zadd', KEYS[3], t + tonumber(ARGV[1]), key); " + "if maxSize ~= nil and maxSize ~= 0 then " + "local mode = redis.call('hget', KEYS[5], 'mode'); " + "if mode == false or mode == 'LRU' then " + "redis.call('zadd', KEYS[4], tonumber(ARGV[1]), key); " + "else " + "redis.call('zincrby', KEYS[4], 1, key); " + "end; " + "end; " + "end; " + "expireDate = math.min(expireDate, tonumber(expireIdle)) " + "end; " + "end; " + "if expireDate > tonumber(ARGV[1]) then " + "table.insert(result, key); " + "end; " + "end; " + "end;" + "return result;", Arrays.asList(getRawName(), timeoutSetName, idleSetName, lastAccessTimeSetName, optionsName), System.currentTimeMillis(), count); } @Override public RFuture> randomEntriesAsync(int count) { return commandExecutor.evalWriteAsync(getRawName(), codec, RedisCommands.EVAL_MAP, "local s = redis.call('hrandfield', KEYS[1], ARGV[2], 'withvalues'); " + "if s == false then " + "return {};" + "end; " + "local result = {}; " + "local maxSize = tonumber(redis.call('hget', KEYS[5], 'max-size'));" + "for i, v in ipairs(s) do " + "if i % 2 == 0 then " + "local t, val = struct.unpack('dLc0', v); " + "local key = s[i-1];" + "local expireDate = 92233720368547758; " + "local expireDateScore = redis.call('zscore', KEYS[2], key); " + "if expireDateScore ~= false then " + "expireDate = tonumber(expireDateScore) " + "end; " + "if t ~= 0 then " + "local expireIdle = redis.call('zscore', KEYS[3], key); " + "if expireIdle ~= false then " + "if tonumber(expireIdle) > tonumber(ARGV[1]) then " + "redis.call('zadd', KEYS[3], t + tonumber(ARGV[1]), key); " + "if maxSize ~= nil and maxSize ~= 0 then " + "local mode = redis.call('hget', KEYS[5], 'mode'); " + "if mode == false or mode == 'LRU' then " + "redis.call('zadd', KEYS[4], tonumber(ARGV[1]), key); " + "else " + "redis.call('zincrby', KEYS[4], 1, key); " + "end; " + "end; " + "end; " + "expireDate = math.min(expireDate, tonumber(expireIdle)) " + "end; " + "end; " + "if expireDate > tonumber(ARGV[1]) then " + "table.insert(result, key); " + "table.insert(result, val); " + "end; " + "end; " + "end;" + "return result;", Arrays.asList(getRawName(), timeoutSetName, idleSetName, lastAccessTimeSetName, optionsName), System.currentTimeMillis(), count); } @Override public RFuture>> readAllEntrySetAsync() { return readAll(RedisCommands.EVAL_MAP_ENTRY); } private RFuture readAll(RedisCommand evalCommandType) { return commandExecutor.evalWriteAsync(getRawName(), 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 " + "local mode = redis.call('hget', KEYS[5], 'mode'); " + "if mode == false or mode == 'LRU' then " + "redis.call('zadd', KEYS[4], tonumber(ARGV[1]), key); " + "else " + "redis.call('zincrby', KEYS[4], 1, key); " + "end; " + "end; " + "end; " + "expireDate = math.min(expireDate, tonumber(expireIdle)) " + "end; " + "end; " + "if expireDate > tonumber(ARGV[1]) then " + "table.insert(result, key); " + "table.insert(result, val); " + "end; " + "end; " + "end;" + "return result;", Arrays.asList(getRawName(), timeoutSetName, idleSetName, lastAccessTimeSetName, optionsName), System.currentTimeMillis()); } @Override public RFuture> readAllMapAsync() { return readAll(RedisCommands.EVAL_MAP); } @Override public RFuture> readAllValuesAsync() { return commandExecutor.evalWriteAsync(getRawName(), 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 " + "local mode = redis.call('hget', KEYS[5], 'mode'); " + "if mode == false or mode == 'LRU' then " + "redis.call('zadd', KEYS[4], tonumber(ARGV[1]), key); " + "else " + "redis.call('zincrby', KEYS[4], 1, key); " + "end; " + "end; " + "end; " + "expireDate = math.min(expireDate, tonumber(expireIdle)) " + "end; " + "end; " + "if expireDate > tonumber(ARGV[1]) then " + "table.insert(result, val); " + "end; " + "end; " + "end;" + "return result;", Arrays.asList(getRawName(), timeoutSetName, idleSetName, lastAccessTimeSetName, optionsName), System.currentTimeMillis()); } @Override public void destroy() { if (evictionScheduler != null) { evictionScheduler.remove(getRawName()); } super.destroy(); List channels = Arrays.asList(getCreatedChannelName(), getRemovedChannelName(), getUpdatedChannelName(), getExpiredChannelName()); for (String channel : channels) { Collection ids = getListenerIdsByName(channel); if (ids.isEmpty()) { continue; } RTopic topic = getTopic(channel); for (Integer listenerId : ids) { removeListenerId(channel, listenerId); topic.removeListener(listenerId); } } } }