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

com.github.lontime.shaded.org.redisson.RedissonMapCache Maven / Gradle / Ivy

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

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

import java.math.BigDecimal;
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 EvictionScheduler evictionScheduler; public RedissonMapCache(EvictionScheduler evictionScheduler, CommandAsyncExecutor commandExecutor, String name, RedissonClient redisson, MapOptions options, WriteBehindService writeBehindService) { super(commandExecutor, name, redisson, options, writeBehindService); if (evictionScheduler != null) { evictionScheduler.schedule(getRawName(), getTimeoutSetName(), getIdleSetName(), getExpiredChannelName(), getLastAccessTimeSetName()); } this.evictionScheduler = evictionScheduler; } public RedissonMapCache(Codec codec, EvictionScheduler evictionScheduler, CommandAsyncExecutor commandExecutor, String name, RedissonClient redisson, MapOptions options, WriteBehindService writeBehindService) { super(codec, commandExecutor, name, redisson, options, writeBehindService); if (evictionScheduler != null) { evictionScheduler.schedule(getRawName(), getTimeoutSetName(), getIdleSetName(), getExpiredChannelName(), getLastAccessTimeSetName()); } this.evictionScheduler = evictionScheduler; } @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(getOptionsName()), maxSize, mode); } @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(getOptionsName()); 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(), getTimeoutSetName(), getIdleSetName(), getLastAccessTimeSetName(), getOptionsName()), System.currentTimeMillis(), encodeMapValue(value)); } @Override public RFuture> getAllOperationAsync(Set keys) { List args = new ArrayList(keys.size() + 1); List plainKeys = new ArrayList(keys.size()); args.add(System.currentTimeMillis()); for (K key : keys) { plainKeys.add(key); args.add(encodeMapKey(key)); } return commandExecutor.evalWriteAsync(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(), getTimeoutSetName(), getIdleSetName(), getLastAccessTimeSetName(), getOptionsName()), args.toArray()); } @Override public V putIfAbsent(K key, V value, long ttl, TimeUnit ttlUnit) { return get(putIfAbsentAsync(key, value, ttl, ttlUnit)); } @Override public RFuture putIfAbsentAsync(K key, V value, long ttl, TimeUnit ttlUnit) { return putIfAbsentAsync(key, value, ttl, ttlUnit, 0, null); } @Override public V putIfAbsent(K key, V value, long ttl, TimeUnit ttlUnit, long maxIdleTime, TimeUnit maxIdleUnit) { return get(putIfAbsentAsync(key, value, ttl, ttlUnit, maxIdleTime, maxIdleUnit)); } @Override public RFuture putIfAbsentAsync(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')); " + "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 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('publish', 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); " + "local msg = struct.pack('Lc0Lc0', string.len(ARGV[5]), ARGV[5], string.len(ARGV[6]), ARGV[6]); " + "redis.call('publish', KEYS[4], msg); " + "return nil; " + "else " + "local t, val = struct.unpack('dLc0', value); " + "redis.call('zadd', KEYS[3], t + ARGV[1], ARGV[5]); " + "return val; " + "end; ", Arrays.asList(name, getTimeoutSetName(name), getIdleSetName(name), getCreatedChannelName(name), getLastAccessTimeSetName(name), getRemovedChannelName(name), getOptionsName(name)), System.currentTimeMillis(), ttlTimeout, maxIdleTimeout, maxIdleDelta, encodeMapKey(key), encodeMapValue(value)); 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; " + "if val == ARGV[3] then " + "redis.call('zrem', KEYS[2], ARGV[2]); " + "redis.call('zrem', KEYS[3], ARGV[2]); " + "local maxSize = tonumber(redis.call('hget', KEYS[6], 'max-size')); " + "if maxSize ~= nil and maxSize ~= 0 then " + " redis.call('zrem', KEYS[5], ARGV[2]); " + "end; " + "redis.call('hdel', KEYS[1], ARGV[2]); " + "local msg = struct.pack('Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(val), val); " + "redis.call('publish', KEYS[4], msg); " + "return 1; " + "else " + "return 0; " + "end", Arrays.asList(name, getTimeoutSetName(name), getIdleSetName(name), getRemovedChannelName(name), getLastAccessTimeSetName(name), getOptionsName(name)), System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value)); } @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'); " + "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 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('publish', removedChannelName, msg);" + "end; " + " end;" + " end" + " end;" + "if mode == 'LFU' then " + "redis.call('zincrby', lastAccessTimeSetName, 1, ARGV[2]); " + "end; " + " end;" + " local msg = struct.pack('Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[3]), ARGV[3]);" + " redis.call('publish', KEYS[4], msg);" + " return nil;" + "else" + " if maxSize ~= nil and maxSize ~= 0 then " + "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);" + "local msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[3]), ARGV[3], string.len(val), val);" + "redis.call('publish', KEYS[5], msg);" + "return val;", Arrays.asList(name, getTimeoutSetName(name), getIdleSetName(name), getCreatedChannelName(name), getUpdatedChannelName(name), getLastAccessTimeSetName(name), getRemovedChannelName(name), getOptionsName(name)), System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value)); } @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); " // 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 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('publish', removedChannelName, msg); " + "end; " + " end; " + " end; " + " end; " + "if mode == 'LFU' then " + "redis.call('zincrby', lastAccessTimeSetName, 1, ARGV[2]); " + "end; " + "end; " + "local msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[3]), ARGV[3], string.len(val), val); " + "redis.call('publish', KEYS[4], msg); " + "return val;", Arrays.asList(name, getTimeoutSetName(name), getIdleSetName(name), getUpdatedChannelName(name), getLastAccessTimeSetName(name), getRemovedChannelName(name), getOptionsName(name)), System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value)); } @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); " // 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 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('publish', removedChannelName, msg); " + "end; " + " end; " + " end; " + " end; " + "if mode == 'LFU' then " + "redis.call('zincrby', lastAccessTimeSetName, 1, ARGV[2]); " + "end; " + "end; " + "local msg = struct.pack('Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[3]), ARGV[3]); " + "redis.call('publish', KEYS[4], msg); " + "return nil;", Arrays.asList(name, getTimeoutSetName(name), getIdleSetName(name), getCreatedChannelName(name), getLastAccessTimeSetName(name), getRemovedChannelName(name), getOptionsName(name)), System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value)); } @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 newValue = tonumber(ARGV[3]); " + "if value ~= false and expireDate > tonumber(ARGV[1]) then " + "newValue = tonumber(val) + newValue; " + "local msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(newValue), newValue, string.len(val), val); " + "redis.call('publish', KEYS[5], msg); " + "else " + "local msg = struct.pack('Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[3]), ARGV[3]); " + "redis.call('publish', KEYS[4], msg); " + "end; " + "local newValuePack = struct.pack('dLc0', t, string.len(newValue), newValue); " + "redis.call('hset', KEYS[1], ARGV[2], newValuePack); " // last access time + "local maxSize = tonumber(redis.call('hget', KEYS[8], 'max-size')); " + "if maxSize ~= nil and maxSize ~= 0 then " + " local currentTime = tonumber(ARGV[1]); " + " local lastAccessTimeSetName = KEYS[6]; " + "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 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('publish', removedChannelName, msg); " + "end; " + " end; " + " end; " + " end; " + "if mode == 'LFU' then " + "redis.call('zincrby', lastAccessTimeSetName, 1, ARGV[2]); " + "end; " + "end; " + "return tostring(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()); } @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; " + // 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 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('publish', 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 " + "local msg = struct.pack('Lc0Lc0', string.len(ARGV[5]), ARGV[5], string.len(ARGV[6]), ARGV[6]); " + "redis.call('publish', KEYS[4], msg); " + "return 1;" + "else " + "local msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[5]), ARGV[5], string.len(ARGV[6]), ARGV[6], string.len(val), val); " + "redis.call('publish', KEYS[5], msg); " + "return 0;" + "end;", Arrays.asList(name, getTimeoutSetName(name), getIdleSetName(name), getCreatedChannelName(name), getUpdatedChannelName(name), getLastAccessTimeSetName(name), getRemovedChannelName(name), getOptionsName(name)), System.currentTimeMillis(), ttlTimeout, maxIdleTimeout, maxIdleDelta, encodeMapKey(key), encodeMapValue(value)); 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) { checkKey(key); long currentTime = System.currentTimeMillis(); long ttlTimeout = 0; if (ttl > 0) { ttlTimeout = currentTime + ttlUnit.toMillis(ttl); } long maxIdleTimeout = 0; if (maxIdleTime > 0) { long maxIdleDelta = maxIdleUnit.toMillis(maxIdleTime); maxIdleTimeout = currentTime + maxIdleDelta; } String name = getRawName(key); RFuture future = commandExecutor.evalWriteAsync(name, codec, RedisCommands.EVAL_BOOLEAN, "local value = redis.call('hget', KEYS[1], ARGV[4]); " + "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[4]); " + "if expireDateScore ~= false then " + "expireDate = tonumber(expireDateScore) " + "end; " + "if t ~= 0 then " + "local expireIdle = redis.call('zscore', KEYS[3], ARGV[4]); " + "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[4]); " + "else " + "redis.call('zrem', KEYS[2], ARGV[4]); " + "end; " + "if tonumber(ARGV[3]) > 0 then " + "redis.call('zadd', KEYS[3], ARGV[3], ARGV[4]); " + "else " + "redis.call('zrem', KEYS[3], ARGV[4]); " + "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[4]); " + "end; " + "if mode == 'LFU' then " + "redis.call('zincrby', KEYS[4], 1, ARGV[4]); " + "end; " + "end; " + "return 1;", Arrays.asList(name, getTimeoutSetName(name), getIdleSetName(name), getLastAccessTimeSetName(name), getOptionsName(name)), System.currentTimeMillis(), ttlTimeout, maxIdleTimeout, encodeMapKey(key)); 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; " // 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 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('publish', 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 " + "local msg = struct.pack('Lc0Lc0', string.len(ARGV[5]), ARGV[5], string.len(ARGV[6]), ARGV[6]); " + "redis.call('publish', KEYS[4], msg); " + "return nil;" + "end; " + "local t, val = struct.unpack('dLc0', v); " + "local msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[5]), ARGV[5], string.len(ARGV[6]), ARGV[6], string.len(val), val); " + "redis.call('publish', KEYS[5], msg); " + "return val", Arrays.asList(name, getTimeoutSetName(name), getIdleSetName(name), getCreatedChannelName(name), getUpdatedChannelName(name), getLastAccessTimeSetName(name), getRemovedChannelName(name), getOptionsName(name)), System.currentTimeMillis(), ttlTimeout, maxIdleTimeout, maxIdleDelta, encodeMapKey(key), encodeMapValue(value)); 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 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 getTimeoutSetName() { return prefixName("redisson__timeout__set", getRawName()); } String getLastAccessTimeSetName(String name) { return prefixName("redisson__map_cache__last_access__set", name); } String getLastAccessTimeSetName() { return prefixName("redisson__map_cache__last_access__set", getRawName()); } String getIdleSetName(String name) { return prefixName("redisson__idle__set", name); } String getIdleSetName() { return prefixName("redisson__idle__set", getRawName()); } String getOptionsName() { return suffixName(getRawName(), "redisson_options"); } 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 msg = struct.pack('Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(val), val); " + "redis.call('publish', KEYS[4], msg); " + "return val; ", Arrays.asList(name, getTimeoutSetName(name), getIdleSetName(name), getRemovedChannelName(name), getLastAccessTimeSetName(name)), System.currentTimeMillis(), encodeMapKey(key)); } @Override protected RFuture> fastRemoveOperationBatchAsync(K... keys) { List args = new ArrayList(keys.length); for (K key : keys) { args.add(encodeMapKey(key)); } RFuture> future = commandExecutor.evalWriteAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_LIST, "local maxSize = tonumber(redis.call('hget', KEYS[6], 'max-size')); " + "if maxSize ~= nil and maxSize ~= 0 then " + " redis.call('zrem', KEYS[5], unpack(ARGV)); " + "end; " + "redis.call('zrem', KEYS[3], unpack(ARGV)); " + "redis.call('zrem', KEYS[2], unpack(ARGV)); " + "for i, key in ipairs(ARGV) do " + "local v = redis.call('hget', KEYS[1], key); " + "if v ~= false then " + "local t, val = struct.unpack('dLc0', v); " + "local msg = struct.pack('Lc0Lc0', string.len(key), key, string.len(val), val); " + "redis.call('publish', KEYS[4], msg); " + "end; " + "end; " + "local result = {}; " + "for i = 1, #ARGV, 1 do " + "local val = redis.call('hdel', KEYS[1], ARGV[i]); " + "table.insert(result, val); " + "end;" + "return result;", Arrays.asList(getRawName(), getTimeoutSetName(), getIdleSetName(), getRemovedChannelName(), getLastAccessTimeSetName(), getOptionsName()), args.toArray()); return future; } @Override protected RFuture fastRemoveOperationAsync(K... keys) { List params = new ArrayList(keys.length); for (K key : keys) { params.add(encodeMapKey(key)); } return commandExecutor.evalWriteAsync(getRawName(), codec, RedisCommands.EVAL_LONG, "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; " + "for i, key in ipairs(ARGV) do " + "local v = redis.call('hget', KEYS[1], key); " + "if v ~= false then " + "local t, val = struct.unpack('dLc0', v); " + "local msg = struct.pack('Lc0Lc0', string.len(key), key, string.len(val), val); " + "redis.call('publish', KEYS[4], msg); " + "end; " + "end; " + "local 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(), getTimeoutSetName(), getIdleSetName(), getRemovedChannelName(), getLastAccessTimeSetName(), getOptionsName()), params.toArray()); } @Override public ScanResult> scanIterator(String name, RedisClient client, long startPos, String pattern, int count) { return get(scanIteratorAsync(name, client, startPos, pattern, count)); } private static final RedisCommand> SCAN = new RedisCommand>("EVAL", new ListMultiDecoder2( new MapCacheScanResultReplayDecoder(), new ObjectMapDecoder(true))); @Override public RFuture>> scanIteratorAsync(String name, RedisClient client, long 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 " + "insertable = true; " + "end; " + "end; " + "local val = struct.pack('dLc0', 0, string.len(ARGV[3]), ARGV[3]); " + "redis.call('hset', KEYS[1], ARGV[2], val); " + // last access time "local maxSize = tonumber(redis.call('hget', KEYS[8], 'max-size'));" + "if maxSize ~= nil and maxSize ~= 0 then " + " local currentTime = tonumber(ARGV[1]); " + " local lastAccessTimeSetName = KEYS[6]; " + "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 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('publish', removedChannelName, msg); " + "end; " + " end; " + " end; " + " end; " + "if mode == 'LFU' then " + "redis.call('zincrby', lastAccessTimeSetName, 1, ARGV[2]); " + "end; " + "end; " + "if insertable == true then " + "local msg = struct.pack('Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[3]), ARGV[3]); " + "redis.call('publish', KEYS[4], msg); " + "return 1;" + "else " + "local t, val = struct.unpack('dLc0', v); " + "local msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[3]), ARGV[3], string.len(val), val); " + "redis.call('publish', KEYS[5], msg); " + "return 0;" + "end;", Arrays.asList(getRawName(key), getTimeoutSetName(name), getIdleSetName(name), getCreatedChannelName(name), getUpdatedChannelName(name), getLastAccessTimeSetName(name), getRemovedChannelName(name), getOptionsName(name)), System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value)); } @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 currentTime = tonumber(ARGV[1]); " + "if value ~= false then " + "local val = struct.pack('dLc0', 0, string.len(ARGV[3]), ARGV[3]); " + "redis.call('hset', KEYS[1], ARGV[2], val); " + "local msg = struct.pack('Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[3]), ARGV[3]); " + "redis.call('publish', KEYS[4], msg); "+ // last access time "if maxSize ~= nil and maxSize ~= 0 then " + "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 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('publish', 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), getCreatedChannelName(name), getLastAccessTimeSetName(name), getRemovedChannelName(name), getOptionsName(name)), System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value)); } @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 currentTime = tonumber(ARGV[1]); " + "if value == false then " + "local val = struct.pack('dLc0', 0, string.len(ARGV[3]), ARGV[3]); " + "redis.call('hset', KEYS[1], ARGV[2], val); " + "local msg = struct.pack('Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[3]), ARGV[3]); " + "redis.call('publish', KEYS[4], msg); "+ // last access time "if maxSize ~= nil and maxSize ~= 0 then " + "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 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('publish', 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); " + "local msg = struct.pack('Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[3]), ARGV[3]); " + "redis.call('publish', KEYS[4], msg); " + "return 1; ", Arrays.asList(name, getTimeoutSetName(name), getIdleSetName(name), getCreatedChannelName(name), getLastAccessTimeSetName(name), getRemovedChannelName(name), getOptionsName(name)), System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value)); } @Override public boolean fastPutIfAbsent(K key, V value, long ttl, TimeUnit ttlUnit) { return fastPutIfAbsent(key, value, ttl, ttlUnit, 0, null); } @Override public boolean fastPutIfAbsent(K key, V value, long ttl, TimeUnit ttlUnit, long maxIdleTime, TimeUnit maxIdleUnit) { return get(fastPutIfAbsentAsync(key, value, ttl, ttlUnit, maxIdleTime, maxIdleUnit)); } @Override public RFuture fastPutIfAbsentAsync(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; " + // 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 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('publish', 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); " + " local msg = struct.pack('Lc0Lc0', string.len(ARGV[5]), ARGV[5], string.len(ARGV[6]), ARGV[6]); " + " redis.call('publish', KEYS[4], msg); " + " return 1; " + "else " + " return 0; " + "end; ", Arrays.asList(name, getTimeoutSetName(name), getIdleSetName(name), getCreatedChannelName(name), getLastAccessTimeSetName(name), getRemovedChannelName(name), getOptionsName(name)), System.currentTimeMillis(), ttlTimeout, maxIdleTimeout, maxIdleDelta, encodeMapKey(key), encodeMapValue(value)); 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 msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[4]), ARGV[4], string.len(ARGV[3]), ARGV[3]); " + " redis.call('publish', KEYS[4], msg); " + "" + " local value = struct.pack('dLc0', t, string.len(ARGV[4]), ARGV[4]); " + " redis.call('hset', KEYS[1], ARGV[2], value); " + " return 1; " + "end; " + "return 0; ", Arrays.asList(name, getTimeoutSetName(name), getIdleSetName(name), getUpdatedChannelName(name)), System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(oldValue), encodeMapValue(newValue)); } @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 msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[3]), ARGV[3], string.len(val), val); " + "redis.call('publish', KEYS[4], msg); " + "return 1; ", Arrays.asList(name, getTimeoutSetName(name), getIdleSetName(name), getUpdatedChannelName(name)), System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value)); } @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 value = struct.pack('dLc0', t, string.len(ARGV[3]), ARGV[3]); " + "redis.call('hset', KEYS[1], ARGV[2], value); " + "local msg = struct.pack('Lc0Lc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[3]), ARGV[3], string.len(val), val); " + "redis.call('publish', KEYS[4], msg); " + "return val; ", Arrays.asList(name, getTimeoutSetName(name), getIdleSetName(name), getUpdatedChannelName(name)), System.currentTimeMillis(), encodeMapKey(key), encodeMapValue(value)); } @Override protected RFuture putAllOperationAsync(Map map) { List params = new ArrayList(map.size()*2 + 1); params.add(System.currentTimeMillis()); for (java.util.Map.Entry t : map.entrySet()) { if (t.getKey() == null) { throw new NullPointerException("map key can't be null"); } if (t.getValue() == null) { throw new NullPointerException("map value can't be null"); } params.add(encodeMapKey(t.getKey())); params.add(encodeMapValue(t.getValue())); } return commandExecutor.evalWriteAsync(getRawName(), codec, RedisCommands.EVAL_VOID, "local currentTime = tonumber(table.remove(ARGV, 1)); " + // index is the first parameter "local maxSize = tonumber(redis.call('hget', KEYS[8], 'max-size'));" + "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 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 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('publish', removedChannelName, msg);" + "end; " + " end;" + " end" + " end;" + "if mode == 'LFU' then " + "redis.call('zincrby', lastAccessTimeSetName, 1, key); " + "end; " + " end;" + " local msg = struct.pack('Lc0Lc0', string.len(key), key, string.len(value), value);" + " redis.call('publish', KEYS[4], msg);" + "else " + "local t, val = struct.unpack('dLc0', v);" + "local msg = struct.pack('Lc0Lc0Lc0', string.len(key), key, string.len(value), value, string.len(val), val);" + "redis.call('publish', KEYS[5], msg);" + " if maxSize ~= nil and maxSize ~= 0 then " + "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(), getTimeoutSetName(), getIdleSetName(), getCreatedChannelName(), getUpdatedChannelName(), getLastAccessTimeSetName(), getRemovedChannelName(), getOptionsName()), 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); for (java.util.Map.Entry t : map.entrySet()) { if (t.getKey() == null) { throw new NullPointerException("map key can't be null"); } if (t.getValue() == null) { throw new NullPointerException("map value can't be null"); } params.add(encodeMapKey(t.getKey())); params.add(encodeMapValue(t.getValue())); } return commandExecutor.evalWriteAsync(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 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 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 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('publish', removedChannelName, msg);" + "end; " + " end;" + " end" + " end;" + "if mode == 'LFU' then " + "redis.call('zincrby', lastAccessTimeSetName, 1, key); " + "end; " + " end;" + " local msg = struct.pack('Lc0Lc0', string.len(key), key, string.len(value), value);" + " redis.call('publish', KEYS[4], msg);" + "else " + "local t, val = struct.unpack('dLc0', v);" + "local msg = struct.pack('Lc0Lc0Lc0', string.len(key), key, string.len(value), value, string.len(val), val);" + "redis.call('publish', KEYS[5], msg);" + " if maxSize ~= nil and maxSize ~= 0 then " + "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(), getTimeoutSetName(), getIdleSetName(), getCreatedChannelName(), getUpdatedChannelName(), getLastAccessTimeSetName(), getRemovedChannelName(), getOptionsName()), params.toArray()); } private MapCacheEventCodec.OSType osType; @Override public int addListener(MapEntryListener listener) { if (listener == null) { throw new NullPointerException(); } if (osType == null) { RFuture> serverFuture = commandExecutor.readAsync((String) null, StringCodec.INSTANCE, RedisCommands.INFO_SERVER); String os = serverFuture.toCompletableFuture().join().get("os"); if (os == null || os.contains("Windows")) { osType = BaseEventCodec.OSType.WINDOWS; } else if (os.contains("NONSTOP")) { osType = BaseEventCodec.OSType.HPNONSTOP; } } if (listener instanceof EntryRemovedListener) { RTopic topic = redisson.getTopic(getRemovedChannelName(), new MapCacheEventCodec(codec, osType)); return topic.addListener(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 = redisson.getTopic(getCreatedChannelName(), new MapCacheEventCodec(codec, osType)); return topic.addListener(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 = redisson.getTopic(getUpdatedChannelName(), new MapCacheEventCodec(codec, osType)); return topic.addListener(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 = redisson.getTopic(getExpiredChannelName(), new MapCacheEventCodec(codec, osType)); return topic.addListener(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); } }); } throw new IllegalArgumentException("Wrong listener type " + listener.getClass()); } @Override public void removeListener(int listenerId) { super.removeListener(listenerId); RTopic removedTopic = redisson.getTopic(getRemovedChannelName()); removedTopic.removeListener(listenerId); RTopic createdTopic = redisson.getTopic(getCreatedChannelName()); createdTopic.removeListener(listenerId); RTopic updatedTopic = redisson.getTopic(getUpdatedChannelName()); updatedTopic.removeListener(listenerId); RTopic expiredTopic = redisson.getTopic(getExpiredChannelName()); expiredTopic.removeListener(listenerId); } @Override public RFuture sizeInMemoryAsync() { List keys = Arrays.asList(getRawName(), getTimeoutSetName(), getIdleSetName(), getLastAccessTimeSetName(), getOptionsName()); return super.sizeInMemoryAsync(keys); } @Override public void clear() { RFuture future = deleteAsync(getRawName(), getTimeoutSetName(), getIdleSetName(), getLastAccessTimeSetName()); get(future); } @Override public RFuture deleteAsync() { return deleteAsync(getRawName(), getTimeoutSetName(), getIdleSetName(), getLastAccessTimeSetName(), getOptionsName()); } @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(), getTimeoutSetName(), getIdleSetName(), getLastAccessTimeSetName(), getOptionsName()), 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(), getTimeoutSetName(), getIdleSetName(), getLastAccessTimeSetName(), getOptionsName()), 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(), getTimeoutSetName(), getIdleSetName(), getLastAccessTimeSetName(), getOptionsName())); } @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(), getTimeoutSetName(), getIdleSetName(), getLastAccessTimeSetName(), getOptionsName()), 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(), getTimeoutSetName(), getIdleSetName(), getLastAccessTimeSetName(), getOptionsName()), 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(), getTimeoutSetName(), getIdleSetName(), getLastAccessTimeSetName(), getOptionsName()), 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(), getTimeoutSetName(), getIdleSetName(), getLastAccessTimeSetName(), getOptionsName()), 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(), getTimeoutSetName(), getIdleSetName(), getLastAccessTimeSetName(), getOptionsName()), System.currentTimeMillis()); } @Override public void destroy() { if (evictionScheduler != null) { evictionScheduler.remove(getRawName()); } if (writeBehindService != null) { writeBehindService.stop(getRawName()); } } }