org.redisson.RedissonMapCache.java Source code

Java tutorial

Introduction

Here is the source code for org.redisson.RedissonMapCache.java

Source

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

import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

import org.redisson.api.RFuture;
import org.redisson.api.RMapCache;
import org.redisson.client.codec.Codec;
import org.redisson.client.codec.LongCodec;
import org.redisson.client.codec.MapScanCodec;
import org.redisson.client.protocol.RedisCommand;
import org.redisson.client.protocol.RedisCommand.ValueType;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.client.protocol.convertor.BooleanReplayConvertor;
import org.redisson.client.protocol.convertor.VoidReplayConvertor;
import org.redisson.client.protocol.decoder.ListMultiDecoder;
import org.redisson.client.protocol.decoder.LongMultiDecoder;
import org.redisson.client.protocol.decoder.MapCacheScanResult;
import org.redisson.client.protocol.decoder.MapCacheScanResultReplayDecoder;
import org.redisson.client.protocol.decoder.MapScanResult;
import org.redisson.client.protocol.decoder.ObjectListDecoder;
import org.redisson.client.protocol.decoder.ObjectMapDecoder;
import org.redisson.client.protocol.decoder.ScanObjectEntry;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.connection.decoder.MapGetAllDecoder;
import org.redisson.eviction.EvictionScheduler;

import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;

/**
 * <p>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.</p>
 *
 * <p>Current redis implementation doesnt have map entry eviction functionality.
 * Thus entries are checked for TTL expiration during any key/value/entry read operation.
 * If key/value/entry expired then it doesn't returns and clean task runs asynchronous.
 * Clean task deletes removes 100 expired entries at once.
 * In addition there is {@link org.redisson.eviction.EvictionScheduler}. This scheduler
 * deletes expired entries in time interval between 5 seconds to 2 hours.</p>
 *
 * <p>If eviction is not required then it's better to use {@link org.redisson.RedissonMap} object.</p>
 *
 * @author Nikita Koksharov
 *
 * @param <K> key
 * @param <V> value
 */
public class RedissonMapCache<K, V> extends RedissonMap<K, V> implements RMapCache<K, V> {

    static final RedisCommand<Boolean> EVAL_PUT_IF_ABSENT = new RedisCommand<Boolean>("EVAL",
            new BooleanReplayConvertor(), 7, ValueType.MAP);
    static final RedisCommand<Boolean> EVAL_HSET = new RedisCommand<Boolean>("EVAL", new BooleanReplayConvertor(),
            4, ValueType.MAP);
    static final RedisCommand<Object> EVAL_REPLACE = new RedisCommand<Object>("EVAL", 6, ValueType.MAP,
            ValueType.MAP_VALUE);
    static final RedisCommand<Boolean> EVAL_REPLACE_VALUE = new RedisCommand<Boolean>("EVAL",
            new BooleanReplayConvertor(), 7,
            Arrays.asList(ValueType.MAP_KEY, ValueType.MAP_VALUE, ValueType.MAP_VALUE));
    static final RedisCommand<Void> EVAL_HMSET = new RedisCommand<Void>("EVAL", new VoidReplayConvertor(), 4,
            ValueType.MAP);
    private static final RedisCommand<Object> EVAL_REMOVE = new RedisCommand<Object>("EVAL", 4, ValueType.MAP_KEY,
            ValueType.MAP_VALUE);
    private static final RedisCommand<Boolean> EVAL_REMOVE_VALUE = new RedisCommand<Boolean>("EVAL",
            new BooleanReplayConvertor(), 5, ValueType.MAP);
    private static final RedisCommand<Object> EVAL_PUT_TTL = new RedisCommand<Object>("EVAL", 9, ValueType.MAP,
            ValueType.MAP_VALUE);
    private static final RedisCommand<Boolean> EVAL_FAST_PUT_TTL = new RedisCommand<Boolean>("EVAL",
            new BooleanReplayConvertor(), 9, ValueType.MAP, ValueType.MAP_VALUE);
    private static final RedisCommand<Object> EVAL_GET_TTL = new RedisCommand<Object>("EVAL", 7, ValueType.MAP_KEY,
            ValueType.MAP_VALUE);
    private static final RedisCommand<Boolean> EVAL_CONTAINS_KEY = new RedisCommand<Boolean>("EVAL",
            new BooleanReplayConvertor(), 7, ValueType.MAP_KEY);
    static final RedisCommand<Boolean> EVAL_CONTAINS_VALUE = new RedisCommand<Boolean>("EVAL",
            new BooleanReplayConvertor(), 7, ValueType.MAP_VALUE);
    static final RedisCommand<Long> EVAL_FAST_REMOVE = new RedisCommand<Long>("EVAL", 5, ValueType.MAP_KEY);

    RedissonMapCache(UUID id, CommandAsyncExecutor commandExecutor, String name) {
        super(id, commandExecutor, name);
    }

    RedissonMapCache(UUID id, Codec codec, CommandAsyncExecutor commandExecutor, String name) {
        super(id, codec, commandExecutor, name);
    }

    public RedissonMapCache(UUID id, EvictionScheduler evictionScheduler, CommandAsyncExecutor commandExecutor,
            String name) {
        super(id, commandExecutor, name);
        evictionScheduler.schedule(getName(), getTimeoutSetName(), getIdleSetName());
    }

    public RedissonMapCache(UUID id, Codec codec, EvictionScheduler evictionScheduler,
            CommandAsyncExecutor commandExecutor, String name) {
        super(id, codec, commandExecutor, name);
        evictionScheduler.schedule(getName(), getTimeoutSetName(), getIdleSetName());
    }

    @Override
    public RFuture<Boolean> containsKeyAsync(Object key) {
        return commandExecutor.evalWriteAsync(getName(key), codec, EVAL_CONTAINS_KEY,
                "local value = redis.call('hget', KEYS[1], ARGV[2]); " + "local expireDate = 92233720368547758; "
                        + "if value ~= false then "
                        + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); "
                        + "if expireDateScore ~= false then " + "expireDate = tonumber(expireDateScore) " + "end; "
                        + "local t, val = struct.unpack('dLc0', value); " + "if t ~= 0 then "
                        + "local expireIdle = redis.call('zscore', KEYS[3], ARGV[2]); "
                        + "if expireIdle ~= false then " + "if tonumber(expireIdle) > tonumber(ARGV[1]) then "
                        + "local value = struct.pack('dLc0', t, string.len(val), val); "
                        + "redis.call('hset', KEYS[1], ARGV[2], value); "
                        + "redis.call('zadd', KEYS[3], t + tonumber(ARGV[1]), ARGV[2]); " + "end; "
                        + "expireDate = math.min(expireDate, tonumber(expireIdle)) " + "end; " + "end; "
                        + "if expireDate <= tonumber(ARGV[1]) then " + "return 0;" + "end; " + "return 1;" + "end;"
                        + "return 0; ",
                Arrays.<Object>asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key)),
                System.currentTimeMillis(), key);
    }

    @Override
    public RFuture<Boolean> containsValueAsync(Object value) {
        return commandExecutor.evalWriteAsync(getName(), codec, EVAL_CONTAINS_VALUE,
                "local s = redis.call('hgetall', KEYS[1]); " + "for i, v in ipairs(s) do " + "if i % 2 == 0 then "
                        + "local t, val = struct.unpack('dLc0', v); " + "if ARGV[2] == val then "
                        + "local key = s[i-1];" + "local expireDate = 92233720368547758; "
                        + "local expireDateScore = redis.call('zscore', KEYS[2], key); "
                        + "if expireDateScore ~= false then " + "expireDate = tonumber(expireDateScore) " + "end; "
                        + "if t ~= 0 then " + "local expireIdle = redis.call('zscore', KEYS[3], key); "
                        + "if expireIdle ~= false then " + "if tonumber(expireIdle) > tonumber(ARGV[1]) then "
                        + "local value = struct.pack('dLc0', t, string.len(val), val); "
                        + "redis.call('hset', KEYS[1], key, value); "
                        + "redis.call('zadd', KEYS[3], t + tonumber(ARGV[1]), key); " + "end; "
                        + "expireDate = math.min(expireDate, tonumber(expireIdle)) " + "end; " + "end; "
                        + "if expireDate <= tonumber(ARGV[1]) then " + "return 0;" + "end; " + "return 1; "
                        + "end; " + "end; " + "end;" + "return 0;",
                Arrays.<Object>asList(getName(), getTimeoutSetName(), getIdleSetName()), System.currentTimeMillis(),
                value);
    }

    @Override
    public RFuture<Map<K, V>> getAllAsync(Set<K> keys) {
        if (keys.isEmpty()) {
            return newSucceededFuture(Collections.<K, V>emptyMap());
        }

        List<Object> args = new ArrayList<Object>(keys.size() + 1);
        args.add(System.currentTimeMillis());
        args.addAll(keys);

        return commandExecutor.evalWriteAsync(getName(), codec,
                new RedisCommand<Map<Object, Object>>("EVAL", new MapGetAllDecoder(args, 1), 7, ValueType.MAP_KEY,
                        ValueType.MAP_VALUE),
                "local expireHead = redis.call('zrange', KEYS[2], 0, 0, 'withscores');"
                        + "local currentTime = tonumber(table.remove(ARGV, 1)); " // index is the first parameter
                        + "local hasExpire = #expireHead == 2 and tonumber(expireHead[2]) <= currentTime; "
                        + "local map = redis.call('hmget', KEYS[1], unpack(ARGV)); " + "for i = #map, 1, -1 do "
                        + "local value = map[i]; " + "if value ~= false then " + "local key = ARGV[i]; "
                        + "local t, val = struct.unpack('dLc0', value); " + "map[i] = val; "

                        + "if hasExpire then " + "local expireDate = redis.call('zscore', KEYS[2], key); "
                        + "if expireDate ~= false and tonumber(expireDate) <= currentTime then "
                        + "map[i] = false; " + "end; " + "end; "

                        + "if t ~= 0 then " + "local expireIdle = redis.call('zscore', KEYS[3], key); "
                        + "if expireIdle ~= false then " + "if tonumber(expireIdle) > currentTime then "
                        + "local value = struct.pack('dLc0', t, string.len(val), val); "
                        + "redis.call('hset', KEYS[1], key, value); "
                        + "redis.call('zadd', KEYS[3], t + currentTime, key); " + "else " + "map[i] = false; "
                        + "end; " + "end; " + "end; "

                        + "end; " + "end; " + "return map;",
                Arrays.<Object>asList(getName(), getTimeoutSetName(), getIdleSetName()), args.toArray());

    }

    @Override
    public V putIfAbsent(K key, V value, long ttl, TimeUnit ttlUnit) {
        return get(putIfAbsentAsync(key, value, ttl, ttlUnit));
    }

    @Override
    public RFuture<V> 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<V> putIfAbsentAsync(K key, V value, long ttl, TimeUnit ttlUnit, long maxIdleTime,
            TimeUnit maxIdleUnit) {
        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;
        }

        return commandExecutor.evalWriteAsync(getName(key), codec, EVAL_PUT_TTL,
                "if redis.call('hexists', KEYS[1], ARGV[4]) == 0 then " + "if tonumber(ARGV[1]) > 0 then "
                        + "redis.call('zadd', KEYS[2], ARGV[1], ARGV[4]); " + "end; "
                        + "if tonumber(ARGV[2]) > 0 then " + "redis.call('zadd', KEYS[3], ARGV[2], ARGV[4]); "
                        + "end; " + "local value = struct.pack('dLc0', ARGV[3], string.len(ARGV[5]), ARGV[5]); "
                        + "redis.call('hset', KEYS[1], ARGV[4], value); " + "return nil; " + "else "
                        + "local value = redis.call('hget', KEYS[1], ARGV[4]); " + "if value == false then "
                        + "return nil; " + "end;" + "local t, val = struct.unpack('dLc0', value); " + "return val; "
                        + "end",
                Arrays.<Object>asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key)),
                ttlTimeout, maxIdleTimeout, maxIdleDelta, key, value);
    }

    @Override
    public RFuture<Boolean> removeAsync(Object key, Object value) {
        return commandExecutor.evalWriteAsync(getName(key), codec, EVAL_REMOVE_VALUE,
                "local value = redis.call('hget', KEYS[1], ARGV[1]); " + "if value == false then " + "return 0; "
                        + "end;" + "local t, val = struct.unpack('dLc0', value); " + "if val == ARGV[2] then "
                        + "redis.call('zrem', KEYS[2], ARGV[1]); " + "redis.call('zrem', KEYS[3], ARGV[1]); "
                        + "return redis.call('hdel', KEYS[1], ARGV[1]); " + "else " + "return 0 " + "end",
                Arrays.<Object>asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key)), key,
                value);
    }

    @Override
    public RFuture<V> getAsync(K key) {
        return commandExecutor.evalWriteAsync(getName(key), codec, EVAL_GET_TTL,
                "local value = redis.call('hget', KEYS[1], ARGV[2]); " + "if value == false then " + "return nil; "
                        + "end; " + "local t, val = struct.unpack('dLc0', value); "
                        + "local expireDate = 92233720368547758; "
                        + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); "
                        + "if expireDateScore ~= false then " + "expireDate = tonumber(expireDateScore) " + "end; "
                        + "if t ~= 0 then " + "local expireIdle = redis.call('zscore', KEYS[3], ARGV[2]); "
                        + "if expireIdle ~= false then " + "if tonumber(expireIdle) > tonumber(ARGV[1]) then "
                        + "local value = struct.pack('dLc0', t, string.len(val), val); "
                        + "redis.call('hset', KEYS[1], ARGV[2], value); "
                        + "redis.call('zadd', KEYS[3], t + tonumber(ARGV[1]), ARGV[2]); " + "end; "
                        + "expireDate = math.min(expireDate, tonumber(expireIdle)) " + "end; " + "end; "
                        + "if expireDate <= tonumber(ARGV[1]) then " + "return nil; " + "end; " + "return val; ",
                Arrays.<Object>asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key)),
                System.currentTimeMillis(), key);
    }

    @Override
    public V put(K key, V value, long ttl, TimeUnit unit) {
        return get(putAsync(key, value, ttl, unit));
    }

    @Override
    public RFuture<V> putAsync(K key, V value) {
        return commandExecutor.evalWriteAsync(getName(key), codec, EVAL_PUT,
                "local v = redis.call('hget', KEYS[1], ARGV[1]); "
                        + "local value = struct.pack('dLc0', 0, string.len(ARGV[2]), ARGV[2]); "
                        + "redis.call('hset', KEYS[1], ARGV[1], value); " + "if v == false then " + "return nil; "
                        + "end; " + "local t, val = struct.unpack('dLc0', v); " + "return val; ",
                Collections.<Object>singletonList(getName(key)), key, value);
    }

    @Override
    public RFuture<V> putIfAbsentAsync(K key, V value) {
        return commandExecutor.evalWriteAsync(getName(key), codec, EVAL_PUT,
                "local value = struct.pack('dLc0', 0, string.len(ARGV[2]), ARGV[2]); "
                        + "if redis.call('hsetnx', KEYS[1], ARGV[1], value) == 1 then " + "return nil " + "else "
                        + "local v = redis.call('hget', KEYS[1], ARGV[1]); " + "if v == false then "
                        + "return nil; " + "end; " + "local t, val = struct.unpack('dLc0', v); " + "return val; "
                        + "end",
                Collections.<Object>singletonList(getName(key)), key, value);
    }

    @Override
    public boolean fastPut(K key, V value, long ttl, TimeUnit ttlUnit) {
        return get(fastPutAsync(key, value, ttl, ttlUnit));
    }

    @Override
    public RFuture<Boolean> 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<Boolean> fastPutAsync(K key, V value, long ttl, TimeUnit ttlUnit, long maxIdleTime,
            TimeUnit maxIdleUnit) {
        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");
        }

        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;
        }

        return commandExecutor.evalWriteAsync(getName(key), codec, EVAL_FAST_PUT_TTL,
                "if tonumber(ARGV[1]) > 0 then " + "redis.call('zadd', KEYS[2], ARGV[1], ARGV[4]); " + "else "
                        + "redis.call('zrem', KEYS[2], ARGV[4]); " + "end; " + "if tonumber(ARGV[2]) > 0 then "
                        + "redis.call('zadd', KEYS[3], ARGV[2], ARGV[4]); " + "else "
                        + "redis.call('zrem', KEYS[3], ARGV[4]); " + "end; "
                        + "local value = struct.pack('dLc0', ARGV[3], string.len(ARGV[5]), ARGV[5]); "
                        + "return redis.call('hset', KEYS[1], ARGV[4], value); ",
                Arrays.<Object>asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key)),
                ttlTimeout, maxIdleTimeout, maxIdleDelta, key, value);
    }

    @Override
    public RFuture<V> 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<V> putAsync(K key, V value, long ttl, TimeUnit ttlUnit, long maxIdleTime, TimeUnit maxIdleUnit) {
        if (ttl < 0) {
            throw new IllegalArgumentException("ttl can't be negative");
        }
        if (maxIdleTime < 0) {
            throw new IllegalArgumentException("maxIdleTime can't be negative");
        }
        if (ttl == 0 && maxIdleTime == 0) {
            return putAsync(key, value);
        }

        if (ttl > 0 && ttlUnit == null) {
            throw new NullPointerException("ttlUnit param can't be null");
        }

        if (maxIdleTime > 0 && maxIdleUnit == null) {
            throw new NullPointerException("maxIdleUnit param can't be null");
        }

        long ttlTimeout = 0;
        if (ttl > 0) {
            ttlTimeout = System.currentTimeMillis() + ttlUnit.toMillis(ttl);
        }

        long maxIdleTimeout = 0;
        long maxIdleDelta = 0;
        if (maxIdleTime > 0) {
            maxIdleDelta = maxIdleUnit.toMillis(maxIdleTime);
            maxIdleTimeout = System.currentTimeMillis() + maxIdleDelta;
        }

        return commandExecutor.evalWriteAsync(getName(key), codec, EVAL_PUT_TTL,
                "local v = redis.call('hget', KEYS[1], ARGV[4]); " + "if tonumber(ARGV[1]) > 0 then "
                        + "redis.call('zadd', KEYS[2], ARGV[1], ARGV[4]); " + "else "
                        + "redis.call('zrem', KEYS[2], ARGV[4]); " + "end; " + "if tonumber(ARGV[2]) > 0 then "
                        + "redis.call('zadd', KEYS[3], ARGV[2], ARGV[4]); " + "else "
                        + "redis.call('zrem', KEYS[3], ARGV[4]); " + "end; "
                        + "local value = struct.pack('dLc0', ARGV[3], string.len(ARGV[5]), ARGV[5]); "
                        + "redis.call('hset', KEYS[1], ARGV[4], value); " + "if v == false then " + "return nil;"
                        + "end; " + "local t, val = struct.unpack('dLc0', v); " + "return val",
                Arrays.<Object>asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key)),
                ttlTimeout, maxIdleTimeout, maxIdleDelta, key, value);
    }

    String getTimeoutSetNameByKey(Object key) {
        return prefixName("redisson__timeout__set", getName(key));
    }

    String getTimeoutSetName(String name) {
        return prefixName("redisson__timeout__set", name);
    }

    String getTimeoutSetName() {
        return prefixName("redisson__timeout__set", getName());
    }

    String getIdleSetNameByKey(Object key) {
        return prefixName("redisson__idle__set", getName(key));
    }

    String getIdleSetName(String name) {
        return prefixName("redisson__idle__set", name);
    }

    String getIdleSetName() {
        return prefixName("redisson__idle__set", getName());
    }

    @Override
    public RFuture<V> removeAsync(K key) {
        return commandExecutor.evalWriteAsync(getName(key), codec, EVAL_REMOVE,
                "local v = redis.call('hget', KEYS[1], ARGV[1]); " + "redis.call('zrem', KEYS[2], ARGV[1]); "
                        + "redis.call('zrem', KEYS[3], ARGV[1]); " + "redis.call('hdel', KEYS[1], ARGV[1]); "
                        + "if v ~= false then " + "local t, val = struct.unpack('dLc0', v); " + "return val; "
                        + "end; " + "return v",
                Arrays.<Object>asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key)), key);
    }

    @Override
    public RFuture<Long> fastRemoveAsync(K... keys) {
        if (keys == null || keys.length == 0) {
            return newSucceededFuture(0L);
        }

        return commandExecutor.evalWriteAsync(getName(), codec, EVAL_FAST_REMOVE,
                "redis.call('zrem', KEYS[3], unpack(ARGV)); " + "redis.call('zrem', KEYS[2], unpack(ARGV)); "
                        + "return redis.call('hdel', KEYS[1], unpack(ARGV)); ",
                Arrays.<Object>asList(getName(), getTimeoutSetName(), getIdleSetName()), keys);
    }

    @Override
    MapScanResult<ScanObjectEntry, ScanObjectEntry> scanIterator(String name, InetSocketAddress client,
            long startPos) {
        return get(scanIteratorAsync(name, client, startPos));
    }

    public RFuture<MapScanResult<ScanObjectEntry, ScanObjectEntry>> scanIteratorAsync(final String name,
            InetSocketAddress client, long startPos) {
        RedisCommand<MapCacheScanResult<Object, Object>> EVAL_HSCAN = new RedisCommand<MapCacheScanResult<Object, Object>>(
                "EVAL", new ListMultiDecoder(new LongMultiDecoder(), new ObjectMapDecoder(new MapScanCodec(codec)),
                        new ObjectListDecoder(codec), new MapCacheScanResultReplayDecoder()),
                ValueType.MAP);
        RFuture<MapCacheScanResult<ScanObjectEntry, ScanObjectEntry>> f = commandExecutor.evalReadAsync(client,
                name, codec, EVAL_HSCAN,
                "local result = {}; " + "local idleKeys = {}; "
                        + "local res = redis.call('hscan', KEYS[1], ARGV[2]); "
                        + "local currentTime = tonumber(ARGV[1]); " + "for i, value in ipairs(res[2]) do "
                        + "if i % 2 == 0 then " + "local key = res[2][i-1]; "
                        + "local expireDate = 92233720368547758; "
                        + "local expireDateScore = redis.call('zscore', KEYS[2], key); "
                        + "if expireDateScore ~= false then " + "expireDate = tonumber(expireDateScore) " + "end; "

                        + "local 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.<Object>asList(name, getTimeoutSetName(name), getIdleSetName(name)),
                System.currentTimeMillis(), startPos);

        f.addListener(new FutureListener<MapCacheScanResult<ScanObjectEntry, ScanObjectEntry>>() {
            @Override
            public void operationComplete(Future<MapCacheScanResult<ScanObjectEntry, ScanObjectEntry>> future)
                    throws Exception {
                if (future.isSuccess()) {
                    MapCacheScanResult<ScanObjectEntry, ScanObjectEntry> res = future.getNow();
                    if (res.getIdleKeys().isEmpty()) {
                        return;
                    }

                    List<Object> args = new ArrayList<Object>(res.getIdleKeys().size() + 1);
                    args.add(System.currentTimeMillis());
                    args.addAll(res.getIdleKeys());

                    commandExecutor.evalWriteAsync(name, codec,
                            new RedisCommand<Map<Object, Object>>("EVAL", new MapGetAllDecoder(args, 1), 7,
                                    ValueType.MAP_KEY, ValueType.MAP_VALUE),
                            "local currentTime = tonumber(table.remove(ARGV, 1)); " // index is the first parameter
                                    + "local map = redis.call('hmget', KEYS[1], unpack(ARGV)); "
                                    + "for i = #map, 1, -1 do " + "local value = map[i]; "
                                    + "if value ~= false then " + "local key = ARGV[i]; "
                                    + "local t, val = struct.unpack('dLc0', value); "

                                    + "if t ~= 0 then " + "local expireIdle = redis.call('zscore', KEYS[2], key); "
                                    + "if expireIdle ~= false then " + "if tonumber(expireIdle) > currentTime then "
                                    + "local value = struct.pack('dLc0', t, string.len(val), val); "
                                    + "redis.call('hset', KEYS[1], key, value); "
                                    + "redis.call('zadd', KEYS[2], t + currentTime, key); " + "end; " + "end; "
                                    + "end; " + "end; " + "end; ",
                            Arrays.<Object>asList(name, getIdleSetName(name)), args.toArray());

                }
            }
        });

        return (RFuture<MapScanResult<ScanObjectEntry, ScanObjectEntry>>) (Object) f;
    }

    @Override
    public RFuture<Boolean> fastPutAsync(K key, V value) {
        return commandExecutor.evalWriteAsync(getName(key), codec, EVAL_HSET,
                "local val = struct.pack('dLc0', 0, string.len(ARGV[2]), ARGV[2]); "
                        + "return redis.call('hset', KEYS[1], ARGV[1], val); ",
                Collections.<Object>singletonList(getName(key)), key, value);
    }

    @Override
    public RFuture<Boolean> fastPutIfAbsentAsync(K key, V value) {
        return commandExecutor.evalWriteAsync(getName(key), codec, EVAL_PUT_IF_ABSENT,
                "local value = redis.call('hget', KEYS[1], ARGV[2]); " + "if value == false then "
                        + "local val = struct.pack('dLc0', 0, string.len(ARGV[3]), ARGV[3]); "
                        + "redis.call('hset', KEYS[1], ARGV[2], val); " + "return 1; " + "end; "
                        + "local t, val = struct.unpack('dLc0', value); " + "local expireDate = 92233720368547758; "
                        + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); "
                        + "if expireDateScore ~= false then " + "expireDate = tonumber(expireDateScore) " + "end; "
                        + "if t ~= 0 then " + "local expireIdle = redis.call('zscore', KEYS[3], ARGV[2]); "
                        + "if expireIdle ~= false then " + "if tonumber(expireIdle) > tonumber(ARGV[1]) then "
                        + "local value = struct.pack('dLc0', t, string.len(val), val); "
                        + "redis.call('hset', KEYS[1], ARGV[2], value); "
                        + "redis.call('zadd', KEYS[3], t + tonumber(ARGV[1]), ARGV[2]); " + "end; "
                        + "expireDate = math.min(expireDate, tonumber(expireIdle)) " + "end; " + "end; "
                        + "if expireDate > tonumber(ARGV[1]) then " + "return 0; " + "end; "

                        + "redis.call('zrem', KEYS[2], ARGV[2]); " + "redis.call('zrem', KEYS[3], ARGV[2]); "
                        + "local val = struct.pack('dLc0', 0, string.len(ARGV[3]), ARGV[3]); "
                        + "redis.call('hset', KEYS[1], ARGV[2], val); " + "return 1; ",
                Arrays.<Object>asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key)),
                System.currentTimeMillis(), key, value);
    }

    @Override
    public RFuture<Boolean> replaceAsync(K key, V oldValue, V newValue) {
        return commandExecutor.evalWriteAsync(getName(key), codec, EVAL_REPLACE_VALUE,
                "local v = redis.call('hget', KEYS[1], ARGV[2]); " + "if v == false then " + "return 0;" + "end;"
                        + "local expireDate = 92233720368547758; "
                        + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); "
                        + "if expireDateScore ~= false then " + "expireDate = tonumber(expireDateScore) " + "end; "

                        + "local t, val = struct.unpack('dLc0', v); " + "if t ~= 0 then "
                        + "local expireIdle = redis.call('zscore', KEYS[3], ARGV[2]); "
                        + "if expireIdle ~= false then " + "if tonumber(expireIdle) > tonumber(ARGV[1]) then "
                        + "local value = struct.pack('dLc0', t, string.len(val), val); "
                        + "redis.call('hset', KEYS[1], ARGV[2], value); "
                        + "redis.call('zadd', KEYS[3], t + tonumber(ARGV[1]), ARGV[2]); " + "end; "
                        + "expireDate = math.min(expireDate, tonumber(expireIdle)) " + "end; " + "end; "
                        + "if expireDate > tonumber(ARGV[1]) and val == ARGV[3] then "
                        + "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.<Object>asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key)),
                System.currentTimeMillis(), key, oldValue, newValue);
    }

    @Override
    public RFuture<V> replaceAsync(K key, V value) {
        return commandExecutor.evalWriteAsync(getName(key), codec, EVAL_REPLACE,
                "local v = redis.call('hget', KEYS[1], ARGV[2]); " + "if v ~= false then "
                        + "local t, val = struct.unpack('dLc0', v); " + "if t ~= 0 then "
                        + "t = t + tonumber(ARGV[1]); " + "end; "
                        + "local value = struct.pack('dLc0', t, string.len(ARGV[3]), ARGV[3]); "
                        + "redis.call('hset', KEYS[1], ARGV[2], value); " + "return val; " + "else "
                        + "return nil; " + "end",
                Arrays.<Object>asList(getName(key), getTimeoutSetNameByKey(key)), System.currentTimeMillis(), key,
                value);
    }

    @Override
    public RFuture<Void> putAllAsync(Map<? extends K, ? extends V> map) {
        if (map.isEmpty()) {
            return newSucceededFuture(null);
        }

        List<Object> params = new ArrayList<Object>(map.size() * 2);
        for (java.util.Map.Entry<? extends K, ? extends V> t : map.entrySet()) {
            params.add(t.getKey());
            params.add(t.getValue());
        }

        return commandExecutor.evalWriteAsync(getName(), codec, EVAL_HMSET,
                "for i, value in ipairs(ARGV) do " + "if i % 2 == 0 then "
                        + "local val = struct.pack('dLc0', 0, string.len(value), value); " + "ARGV[i] = val; "
                        + "end;" + "end;" + "return redis.call('hmset', KEYS[1], unpack(ARGV)); ",
                Collections.<Object>singletonList(getName()), params.toArray());
    }

    @Override
    public RFuture<Boolean> deleteAsync() {
        return commandExecutor.writeAsync(getName(), RedisCommands.DEL_OBJECTS, getName(), getTimeoutSetName(),
                getIdleSetName());
    }

    @Override
    public RFuture<Boolean> expireAsync(long timeToLive, TimeUnit timeUnit) {
        return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
                "redis.call('zadd', KEYS[2], 92233720368547758, 'redisson__expiretag');"
                        + "redis.call('pexpire', KEYS[2], ARGV[1]); "
                        + "redis.call('zadd', KEYS[3], 92233720368547758, 'redisson__expiretag');"
                        + "redis.call('pexpire', KEYS[3], ARGV[1]); "
                        + "return redis.call('pexpire', KEYS[1], ARGV[1]); ",
                Arrays.<Object>asList(getName(), getTimeoutSetName(), getIdleSetName()),
                timeUnit.toMillis(timeToLive));
    }

    @Override
    public RFuture<Boolean> expireAtAsync(long timestamp) {
        return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
                "redis.call('zadd', KEYS[2], 92233720368547758, 'redisson__expiretag');"
                        + "redis.call('pexpireat', KEYS[2], ARGV[1]); "
                        + "redis.call('zadd', KEYS[3], 92233720368547758, 'redisson__expiretag');"
                        + "redis.call('pexpire', KEYS[3], ARGV[1]); "
                        + "return redis.call('pexpireat', KEYS[1], ARGV[1]); ",
                Arrays.<Object>asList(getName(), getTimeoutSetName(), getIdleSetName()), timestamp);
    }

    @Override
    public RFuture<Boolean> clearExpireAsync() {
        return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
                "redis.call('zrem', KEYS[2], 'redisson__expiretag'); " + "redis.call('persist', KEYS[2]); "
                        + "redis.call('zrem', KEYS[3], 'redisson__expiretag'); "
                        + "redis.call('persist', KEYS[3]); " + "return redis.call('persist', KEYS[1]); ",
                Arrays.<Object>asList(getName(), getTimeoutSetName(), getIdleSetName()));
    }

    @Override
    public RFuture<Set<K>> readAllKeySetAsync() {
        return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_MAP_KEY_SET,
                "local s = redis.call('hgetall', KEYS[1]); " + "local result = {}; " + "for i, v in ipairs(s) do "
                        + "if i % 2 == 0 then " + "local t, val = struct.unpack('dLc0', v); "
                        + "local key = s[i-1];" + "local expireDate = 92233720368547758; "
                        + "local expireDateScore = redis.call('zscore', KEYS[2], key); "
                        + "if expireDateScore ~= false then " + "expireDate = tonumber(expireDateScore) " + "end; "
                        + "if t ~= 0 then " + "local expireIdle = redis.call('zscore', KEYS[3], key); "
                        + "if expireIdle ~= false then " + "if tonumber(expireIdle) > tonumber(ARGV[1]) then "
                        + "local value = struct.pack('dLc0', t, string.len(val), val); "
                        + "redis.call('hset', KEYS[1], key, value); "
                        + "redis.call('zadd', KEYS[3], t + tonumber(ARGV[1]), key); " + "end; "
                        + "expireDate = math.min(expireDate, tonumber(expireIdle)) " + "end; " + "end; "
                        + "if expireDate > tonumber(ARGV[1]) then " + "table.insert(result, key); " + "end; "
                        + "end; " + "end;" + "return result;",
                Arrays.<Object>asList(getName(), getTimeoutSetName(), getIdleSetName()),
                System.currentTimeMillis());
    }

    @Override
    public RFuture<Set<java.util.Map.Entry<K, V>>> readAllEntrySetAsync() {
        return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_MAP_ENTRY,
                "local s = redis.call('hgetall', KEYS[1]); " + "local result = {}; " + "for i, v in ipairs(s) do "
                        + "if i % 2 == 0 then " + "local t, val = struct.unpack('dLc0', v); "
                        + "local key = s[i-1];" + "local expireDate = 92233720368547758; "
                        + "local expireDateScore = redis.call('zscore', KEYS[2], key); "
                        + "if expireDateScore ~= false then " + "expireDate = tonumber(expireDateScore) " + "end; "
                        + "if t ~= 0 then " + "local expireIdle = redis.call('zscore', KEYS[3], key); "
                        + "if expireIdle ~= false then " + "if tonumber(expireIdle) > tonumber(ARGV[1]) then "
                        + "local value = struct.pack('dLc0', t, string.len(val), val); "
                        + "redis.call('hset', KEYS[1], key, value); "
                        + "redis.call('zadd', KEYS[3], t + tonumber(ARGV[1]), key); " + "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.<Object>asList(getName(), getTimeoutSetName(), getIdleSetName()),
                System.currentTimeMillis());
    }

    @Override
    public RFuture<Collection<V>> readAllValuesAsync() {
        return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_MAP_VALUE_LIST,
                "local s = redis.call('hgetall', KEYS[1]); " + "local result = {}; " + "for i, v in ipairs(s) do "
                        + "if i % 2 == 0 then " + "local t, val = struct.unpack('dLc0', v); "
                        + "local key = s[i-1];" + "local expireDate = 92233720368547758; "
                        + "local expireDateScore = redis.call('zscore', KEYS[2], key); "
                        + "if expireDateScore ~= false then " + "expireDate = tonumber(expireDateScore) " + "end; "
                        + "if t ~= 0 then " + "local expireIdle = redis.call('zscore', KEYS[3], key); "
                        + "if expireIdle ~= false then " + "if tonumber(expireIdle) > tonumber(ARGV[1]) then "
                        + "local value = struct.pack('dLc0', t, string.len(val), val); "
                        + "redis.call('hset', KEYS[1], key, value); "
                        + "redis.call('zadd', KEYS[3], t + tonumber(ARGV[1]), key); " + "end; "
                        + "expireDate = math.min(expireDate, tonumber(expireIdle)) " + "end; " + "end; "
                        + "if expireDate > tonumber(ARGV[1]) then " + "table.insert(result, val); " + "end; "
                        + "end; " + "end;" + "return result;",
                Arrays.<Object>asList(getName(), getTimeoutSetName(), getIdleSetName()),
                System.currentTimeMillis());
    }

}