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

org.redkalex.cache.redis.RedissonCacheSource Maven / Gradle / Ivy

There is a newer version: 2.7.7
Show newest version
/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package org.redkalex.cache.redis;

import java.io.Serializable;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Predicate;
import java.util.logging.*;
import java.util.stream.Collectors;
import org.redisson.Redisson;
import org.redisson.api.*;
import org.redisson.api.listener.MessageListener;
import org.redisson.client.codec.*;
import org.redisson.client.protocol.*;
import org.redisson.config.*;
import org.redkale.annotation.AutoLoad;
import org.redkale.annotation.ResourceChanged;
import org.redkale.annotation.ResourceType;
import org.redkale.convert.Convert;
import org.redkale.inject.ResourceEvent;
import org.redkale.service.Local;
import org.redkale.source.*;
import org.redkale.util.*;
import static org.redkale.util.Utility.*;

/**
 * //https://www.cnblogs.com/xiami2046/p/13934146.html
 *
 * @author zhangjx
 */
@Local
@AutoLoad(false)
@ResourceType(CacheSource.class)
@SourceType(RedisSource.class)
public class RedissonCacheSource extends RedisSource {

    private final Logger logger = Logger.getLogger(this.getClass().getSimpleName());

    private List nodeAddrs;

    private RedissonClient client;

    // >
    private final ConcurrentHashMap> pubsubListeners =
            new ConcurrentHashMap<>();

    private static final Codec SCAN_CODEC = new Codec() {
        @Override
        public Decoder getMapValueDecoder() {
            return ByteArrayCodec.INSTANCE.getMapValueDecoder();
        }

        @Override
        public Encoder getMapValueEncoder() {
            return ByteArrayCodec.INSTANCE.getMapValueEncoder();
        }

        @Override
        public Decoder getMapKeyDecoder() {
            return StringCodec.INSTANCE.getMapKeyDecoder();
        }

        @Override
        public Encoder getMapKeyEncoder() {
            return StringCodec.INSTANCE.getMapKeyEncoder();
        }

        @Override
        public Decoder getValueDecoder() {
            return ByteArrayCodec.INSTANCE.getMapValueDecoder();
        }

        @Override
        public Encoder getValueEncoder() {
            return StringCodec.INSTANCE.getValueEncoder();
        }

        @Override
        public ClassLoader getClassLoader() {
            return ByteArrayCodec.INSTANCE.getClassLoader();
        }
    };

    @Override
    public void init(AnyValue conf) {
        super.init(conf);
        if (conf == null) {
            conf = AnyValue.create();
        }
        initClient(conf);
    }

    private void initClient(AnyValue conf) {
        RedisConfig config = RedisConfig.create(conf);
        Config redisConfig = new Config();
        String cluster = conf.getOrDefault("cluster", "");
        BaseConfig baseConfig = null;
        SingleServerConfig singleConfig = null;
        ClusterServersConfig clusterConfig = null;
        ReplicatedServersConfig replicateConfig = null;
        SentinelServersConfig sentinelConfig = null;
        int max = config.getMaxconns(2);
        for (String addr : config.getAddresses()) {
            if (config.getAddresses().size() == 1) { // 单体
                if (singleConfig == null) {
                    singleConfig = redisConfig.useSingleServer();
                    singleConfig.setConnectionMinimumIdleSize(max / 2 + 1);
                    singleConfig.setConnectionPoolSize(max);
                    baseConfig = singleConfig;
                }
                singleConfig.setAddress(addr);
                singleConfig.setDatabase(this.db);
            } else if ("cluster".equalsIgnoreCase(cluster)) { // 集群
                if (clusterConfig == null) {
                    clusterConfig = redisConfig.useClusterServers();
                    clusterConfig.setMasterConnectionMinimumIdleSize(max / 2 + 1);
                    clusterConfig.setMasterConnectionPoolSize(max);
                    clusterConfig.setSlaveConnectionMinimumIdleSize(max / 2 + 1);
                    clusterConfig.setSlaveConnectionPoolSize(max);
                    baseConfig = clusterConfig;
                }
                clusterConfig.addNodeAddress(addr);
            } else if ("replicated".equalsIgnoreCase(cluster)) { // 主从
                if (replicateConfig == null) {
                    replicateConfig = redisConfig.useReplicatedServers();
                    replicateConfig.setMasterConnectionMinimumIdleSize(max / 2 + 1);
                    replicateConfig.setMasterConnectionPoolSize(max);
                    replicateConfig.setSlaveConnectionMinimumIdleSize(max / 2 + 1);
                    replicateConfig.setSlaveConnectionPoolSize(max);
                    baseConfig = replicateConfig;
                }
                replicateConfig.addNodeAddress(addr);
                replicateConfig.setDatabase(this.db);
            } else if ("sentinel".equalsIgnoreCase(cluster)) { // 哨兵
                if (sentinelConfig == null) {
                    sentinelConfig = redisConfig.useSentinelServers();
                    sentinelConfig.setMasterConnectionMinimumIdleSize(max / 2 + 1);
                    sentinelConfig.setMasterConnectionPoolSize(max);
                    sentinelConfig.setSlaveConnectionMinimumIdleSize(max / 2 + 1);
                    sentinelConfig.setSlaveConnectionPoolSize(max);
                    baseConfig = sentinelConfig;
                }
                sentinelConfig.addSentinelAddress(addr);
                sentinelConfig.setDatabase(this.db);
            }
            if (baseConfig != null) { // 单个进程的不同自定义密码
                if (config.getUsername() != null) {
                    baseConfig.setUsername(config.getUsername());
                }
                if (config.getPassword() != null) {
                    baseConfig.setPassword(config.getPassword());
                }
            }
        }
        if (baseConfig != null) { // 配置全局密码
            String username = conf.getValue(CACHE_SOURCE_USER, "").trim();
            String password = conf.getValue(CACHE_SOURCE_PASSWORD, "").trim();
            String retryAttempts = conf.getValue("retryAttempts", "").trim();
            String retryInterval = conf.getValue("retryInterval", "").trim();
            if (!username.isEmpty()) {
                baseConfig.setUsername(username);
            }
            if (!password.isEmpty()) {
                baseConfig.setPassword(password);
            }
            if (!retryAttempts.isEmpty()) {
                baseConfig.setRetryAttempts(Integer.parseInt(retryAttempts));
            }
            if (!retryInterval.isEmpty()) {
                baseConfig.setRetryInterval(Integer.parseInt(retryInterval));
            }
        }
        RedissonClient old = this.client;
        this.client = Redisson.create(redisConfig);
        this.nodeAddrs = config.getAddresses();
        if (old != null) {
            old.shutdown();
        }
        if (!pubsubListeners.isEmpty()) {
            reloadSubConn();
        }
        //        RTopic topic = client.getTopic("__keyevent@" + db + "__:expired", new StringCodec());
        //        topic.addListener(String.class, (CharSequence cs, String key) -> {
        //            if (logger.isLoggable(Level.FINE)) logger.log(Level.FINE,
        // RedissonCacheSource.class.getSimpleName() + "." + db + ": expired key=" + key + ", cs=" + cs);
        //        });
        // if (logger.isLoggable(Level.FINE)) logger.log(Level.FINE, RedissonCacheSource.class.getSimpleName() + ":
        // addrs=" + addresses + ", db=" + db);

    }

    @Override
    @ResourceChanged
    public void onResourceChange(ResourceEvent[] events) {
        if (Utility.isEmpty(events)) {
            return;
        }
        StringBuilder sb = new StringBuilder();
        for (ResourceEvent event : events) {
            sb.append("CacheSource(name=")
                    .append(resourceName())
                    .append(") change '")
                    .append(event.name())
                    .append("' to '")
                    .append(event.coverNewValue())
                    .append("'\r\n");
        }
        initClient(this.conf);
        if (sb.length() > 0) {
            logger.log(Level.INFO, sb.toString());
        }
    }

    @Override
    public final String getType() {
        return "redis";
    }

    @Override
    public String toString() {
        return getClass().getSimpleName() + "{addrs=" + this.nodeAddrs + ", db=" + this.db + "}";
    }

    @Local
    public org.redisson.api.RedissonClient getRedisClient() {
        return client;
    }

    protected  CompletableFuture toFuture(CompletionStage rf) {
        return rf.toCompletableFuture();
    }

    protected CompletableFuture toFuture(String key, RedisCryptor cryptor, CompletionStage rf) {
        return cryptor != null
                ? rf.toCompletableFuture().thenApply(v -> cryptor.decrypt(key, v))
                : rf.toCompletableFuture();
    }

    protected  CompletableFuture toFuture(
            String key, RedisCryptor cryptor, Type type, CompletionStage rf) {
        return rf.toCompletableFuture().thenApply(bs -> decryptValue(key, cryptor, type, bs));
    }

    protected  CompletableFuture toFuture(
            String key, RedisCryptor cryptor, Convert c, Type type, CompletionStage rf) {
        return rf.toCompletableFuture().thenApply(bs -> decryptValue(key, cryptor, c, type, bs));
    }

    protected  Collection getCollectionValue(
            String key, Collection bytesResult, boolean set, Type componentType) {
        final Collection rs = set ? new LinkedHashSet<>() : new ArrayList<>();
        Collection kvs = bytesResult;
        for (byte[] bs : kvs) {
            if (bs == null) {
                rs.add(null);
            } else if (componentType == String.class) {
                rs.add((T) decryptValue(key, cryptor, new String(bs, StandardCharsets.UTF_8)));
            } else {
                rs.add((T) decryptValue(key, cryptor, componentType, bs));
            }
        }
        return rs;
    }

    protected List getSortedListValue(String key, Collection bytesResult) {
        final List rs = new ArrayList<>();
        Collection kvs = bytesResult;
        for (org.redisson.client.protocol.ScoredEntry bs : kvs) {
            rs.add(bs.getValue().toString());
        }
        return rs;
    }

    @Override
    public void destroy(AnyValue conf) {
        super.destroy(conf);
        if (client != null) {
            client.shutdown();
        }
    }

    protected void reloadSubConn() {
        // 重连时重新订阅
        if (!pubsubListeners.isEmpty()) {
            final Map, HashSet> listeners = new HashMap<>();
            pubsubListeners.forEach((l, s) -> {
                listeners.computeIfAbsent(l, x -> new HashSet<>()).addAll(s.keySet());
            });
            listeners.forEach((listener, topics) -> {
                subscribeAsync(listener, topics.toArray(Creator.funcStringArray()));
            });
        }
    }

    @Override
    public CompletableFuture isOpenAsync() {
        return CompletableFuture.completedFuture(client != null && !client.isShutdown());
    }

    // ------------------------ 订阅发布 SUB/PUB ------------------------
    @Override
    public CompletableFuture> pubsubChannelsAsync(String pattern) {
        throw new UnsupportedOperationException("Not supported yet."); // redission没有实现
    }

    @Override
    public CompletableFuture subscribeAsync(CacheEventListener listener, String... topics) {
        Objects.requireNonNull(listener);
        final MessageListener msgListener = new MessageListener() {
            @Override
            public void onMessage(CharSequence channel, byte[] msg) {
                pubSubExecutor().execute(() -> {
                    try {
                        listener.onMessage(channel.toString(), msg);
                    } catch (Throwable t) {
                        logger.log(
                                Level.SEVERE, "CacheSource subscribe message error, topic: " + channel.toString(), t);
                    }
                });
            }
        };
        CompletableFuture[] futures = new CompletableFuture[topics.length];
        for (int i = 0; i < topics.length; i++) {
            String topic = topics[i];
            futures[i] = toFuture(client.getTopic(topic, ByteArrayCodec.INSTANCE)
                    .addListenerAsync(byte[].class, msgListener)
                    .thenApply(v -> {
                        pubsubListeners
                                .computeIfAbsent(listener, t -> new ConcurrentHashMap<>())
                                .put(topic, v);
                        return null;
                    }));
        }
        return futures.length == 1 ? futures[0] : CompletableFuture.allOf(futures);
    }

    @Override
    public CompletableFuture publishAsync(String topic, byte[] message) {
        Objects.requireNonNull(topic);
        Objects.requireNonNull(message);
        return toFuture(client.getTopic(topic, ByteArrayCodec.INSTANCE)
                .publishAsync(message)
                .thenApply(v -> v.intValue()));
    }

    @Override
    public CompletableFuture unsubscribeAsync(CacheEventListener listener, String... topics) {
        if (listener == null) { // 清掉指定topic的所有订阅者
            Set delTopics = new HashSet<>();
            if (Utility.isEmpty(topics)) {
                pubsubListeners.values().forEach(m -> delTopics.addAll(m.keySet()));
            } else {
                delTopics.addAll(Arrays.asList(topics));
            }
            List> futures = new ArrayList<>();
            delTopics.forEach(topic -> {
                futures.add(
                        toFuture(client.getTopic(topic, ByteArrayCodec.INSTANCE).removeAllListenersAsync()));
            });
            return returnFutureSize(futures);
        } else { // 清掉指定topic的指定订阅者
            ConcurrentHashMap topicToIds = pubsubListeners.get(listener);
            if (topicToIds == null || topicToIds.isEmpty()) { // 没有找到指定订阅者
                return CompletableFuture.completedFuture(0);
            } else { // 清掉指定订阅者的指定topic
                if (Utility.isEmpty(topics)) {
                    return CompletableFuture.failedFuture(new RedkaleException("topics is empty"));
                }
                Predicate filter = t -> Utility.contains(topics, t);
                List> futures = new ArrayList<>();
                topicToIds.forEach((topic, id) -> {
                    if (filter.test(topic)) {
                        futures.add(toFuture(
                                client.getTopic(topic, ByteArrayCodec.INSTANCE).removeListenerAsync(id)));
                    }
                });
                return returnFutureSize(futures);
            }
        }
    }

    // --------------------- exists ------------------------------
    @Override
    public CompletableFuture existsAsync(String key) {
        final RBucket bucket = client.getBucket(key, ByteArrayCodec.INSTANCE);
        return toFuture(bucket.isExistsAsync());
    }

    // --------------------- get ------------------------------
    @Override
    public  CompletableFuture getAsync(String key, Type type) {
        final RBucket bucket = client.getBucket(key, ByteArrayCodec.INSTANCE);
        return toFuture(key, cryptor, type, bucket.getAsync());
    }

    // --------------------- getex ------------------------------
    @Override
    public  CompletableFuture getexAsync(String key, int expireSeconds, final Type type) {
        final RBucket bucket = client.getBucket(key, ByteArrayCodec.INSTANCE);
        return toFuture(bucket.getAndExpireAsync(Duration.ofSeconds(expireSeconds))
                .thenApply(bs -> decryptValue(key, cryptor, type, bs)));
    }

    // --------------------- setex ------------------------------
    @Override
    public  CompletableFuture setAsync(String key, Convert convert0, final Type type, T value) {
        final RBucket bucket = client.getBucket(key, ByteArrayCodec.INSTANCE);
        return toFuture(bucket.setAsync(
                type == String.class
                        ? encryptValue(key, cryptor, String.valueOf(value)).getBytes(StandardCharsets.UTF_8)
                        : encryptValue(key, cryptor, type, convert0, value)));
    }

    @Override
    public  CompletableFuture setnxAsync(String key, Convert convert0, final Type type, T value) {
        final RBucket bucket = client.getBucket(key, ByteArrayCodec.INSTANCE);
        return toFuture(bucket.setIfAbsentAsync(
                type == String.class
                        ? encryptValue(key, cryptor, String.valueOf(value)).getBytes(StandardCharsets.UTF_8)
                        : encryptValue(key, cryptor, type, convert0, value)));
    }

    @Override
    public  CompletableFuture getSetAsync(String key, Convert convert0, final Type type, T value) {
        final RBucket bucket = client.getBucket(key, ByteArrayCodec.INSTANCE);
        Convert c = convert0 == null ? this.convert : convert0;
        return toFuture(bucket.getAndSetAsync(
                        type == String.class
                                ? encryptValue(key, cryptor, String.valueOf(value))
                                        .getBytes(StandardCharsets.UTF_8)
                                : encryptValue(key, cryptor, type, c, value))
                .thenApply(old -> old == null ? null : (T) c.convertFrom(type, old)));
    }

    @Override
    public  CompletableFuture getDelAsync(String key, final Type type) {
        final RBucket bucket = client.getBucket(key, ByteArrayCodec.INSTANCE);
        return toFuture(key, cryptor, type, bucket.getAndDeleteAsync());
    }

    @Override
    public CompletableFuture msetAsync(Serializable... keyVals) {
        Map map = new LinkedHashMap<>();
        for (int i = 0; i < keyVals.length; i += 2) {
            String key = keyVals[i].toString();
            Object val = keyVals[i + 1];
            map.put(
                    key,
                    val instanceof String
                            ? encryptValue(key, cryptor, val.toString()).getBytes(StandardCharsets.UTF_8)
                            : encryptValue(key, cryptor, this.convert, val));
        }
        final RBuckets bucket = client.getBuckets(ByteArrayCodec.INSTANCE);
        return toFuture(bucket.setAsync(map).thenApply(v -> null));
    }

    @Override
    public CompletableFuture msetAsync(Map map) {
        Map bs = new LinkedHashMap<>();
        map.forEach((key, val) -> {
            bs.put(
                    key.toString(),
                    val instanceof String
                            ? encryptValue(key.toString(), cryptor, val.toString())
                                    .getBytes(StandardCharsets.UTF_8)
                            : encryptValue(key.toString(), cryptor, this.convert, val));
        });
        final RBuckets bucket = client.getBuckets(ByteArrayCodec.INSTANCE);
        return toFuture(bucket.setAsync(bs).thenApply(v -> null));
    }

    @Override
    public CompletableFuture msetnxAsync(Serializable... keyVals) {
        Map map = new LinkedHashMap<>();
        for (int i = 0; i < keyVals.length; i += 2) {
            String key = keyVals[i].toString();
            Object val = keyVals[i + 1];
            map.put(
                    key,
                    val instanceof String
                            ? encryptValue(key, cryptor, val.toString()).getBytes(StandardCharsets.UTF_8)
                            : encryptValue(key, cryptor, this.convert, val));
        }
        final RBuckets bucket = client.getBuckets(ByteArrayCodec.INSTANCE);
        return toFuture(bucket.trySetAsync(map).thenApply(v -> v.booleanValue()));
    }

    @Override
    public CompletableFuture msetnxAsync(Map map) {
        Map bs = new LinkedHashMap<>();
        map.forEach((key, val) -> {
            bs.put(
                    key.toString(),
                    val instanceof String
                            ? encryptValue(key.toString(), cryptor, val.toString())
                                    .getBytes(StandardCharsets.UTF_8)
                            : encryptValue(key.toString(), cryptor, this.convert, val));
        });
        final RBuckets bucket = client.getBuckets(ByteArrayCodec.INSTANCE);
        return toFuture(bucket.trySetAsync(bs).thenApply(v -> v.booleanValue()));
    }

    // --------------------- setex ------------------------------
    @Override
    public  CompletableFuture setexAsync(
            String key, int expireSeconds, Convert convert, final Type type, T value) {
        final RBucket bucket = client.getBucket(key, ByteArrayCodec.INSTANCE);
        return toFuture(
                bucket.setAsync(encryptValue(key, cryptor, type, convert, value), expireSeconds, TimeUnit.SECONDS)
                        .thenApply(r -> null));
    }

    @Override
    public  CompletableFuture psetexAsync(
            String key, long milliSeconds, Convert convert, final Type type, T value) {
        final RBucket bucket = client.getBucket(key, ByteArrayCodec.INSTANCE);
        return toFuture(
                bucket.setAsync(encryptValue(key, cryptor, type, convert, value), milliSeconds, TimeUnit.MILLISECONDS)
                        .thenApply(r -> null));
    }

    // --------------------- setex ------------------------------
    @Override
    public  CompletableFuture setnxexAsync(
            String key, int expireSeconds, Convert convert0, final Type type, T value) {
        final RBucket bucket = client.getBucket(key, ByteArrayCodec.INSTANCE);
        return toFuture(bucket.setIfAbsentAsync(
                encryptValue(key, cryptor, type, convert0, value), Duration.ofSeconds(expireSeconds)));
    }

    @Override
    public  CompletableFuture setnxpxAsync(
            String key, long milliSeconds, Convert convert0, final Type type, T value) {
        final RBucket bucket = client.getBucket(key, ByteArrayCodec.INSTANCE);
        return toFuture(bucket.setIfAbsentAsync(
                encryptValue(key, cryptor, type, convert0, value), Duration.ofMillis(milliSeconds)));
    }

    // --------------------- expire ------------------------------
    @Override
    public CompletableFuture expireAsync(String key, int expireSeconds) {
        return toFuture(client.getBucket(key)
                .expireAsync(Duration.ofSeconds(expireSeconds))
                .thenApply(r -> null));
    }

    @Override
    public CompletableFuture pexpireAsync(String key, long milliSeconds) {
        return toFuture(client.getBucket(key)
                .expireAsync(Duration.ofMillis(milliSeconds))
                .thenApply(r -> null));
    }

    @Override
    public CompletableFuture expireAtAsync(String key, long secondsTime) {
        return toFuture(client.getBucket(key)
                .expireAsync(Instant.ofEpochSecond(secondsTime))
                .thenApply(r -> null));
    }

    @Override
    public CompletableFuture pexpireAtAsync(String key, long milliTime) {
        return toFuture(client.getBucket(key)
                .expireAsync(Instant.ofEpochMilli(milliTime))
                .thenApply(r -> null));
    }

    // --------------------- ttl ------------------------------
    @Override
    public CompletableFuture ttlAsync(String key) {
        return toFuture(client.getBucket(key).remainTimeToLiveAsync().thenApply(r -> r > 0 ? r / 1000 : r));
    }

    @Override
    public CompletableFuture pttlAsync(String key) {
        return toFuture(client.getBucket(key).remainTimeToLiveAsync());
    }

    @Override
    public CompletableFuture expireTimeAsync(String key) {
        return toFuture(client.getBucket(key).getExpireTimeAsync().thenApply(r -> r > 0 ? r / 1000 : r));
    }

    @Override
    public CompletableFuture pexpireTimeAsync(String key) {
        return toFuture(client.getBucket(key).getExpireTimeAsync());
    }

    // --------------------- persist ------------------------------
    @Override
    public CompletableFuture persistAsync(String key) {
        return toFuture(client.getBucket(key).clearExpireAsync());
    }

    // --------------------- rename ------------------------------
    @Override
    public CompletableFuture renameAsync(String oldKey, String newKey) {
        return toFuture(client.getBucket(oldKey).renameAsync(newKey).handle((v, t) -> t == null));
    }

    @Override
    public CompletableFuture renamenxAsync(String oldKey, String newKey) {
        return toFuture(client.getBucket(oldKey).renamenxAsync(newKey));
    }

    @Override
    public  CompletableFuture evalAsync(Type type, String script, List keys, String... args) {
        String key = keys == null || keys.isEmpty() ? null : keys.get(0);
        Object[] vals = args;
        RScript.ReturnType rt = RScript.ReturnType.VALUE;
        final Class t = TypeToken.typeToClass(type);
        if (Collection.class.isAssignableFrom(t)) {
            rt = RScript.ReturnType.MULTI;
        } else if (Map.class.isAssignableFrom(t)) {
            rt = RScript.ReturnType.MAPVALUE;
        }
        RScript.ReturnType rrt = rt;
        return toFuture(client.getScript(StringCodec.INSTANCE)
                .evalAsync(RScript.Mode.READ_WRITE, script, rrt, (List) keys, vals)
                .thenApply(bs -> {
                    if (bs == null) {
                        return null;
                    }
                    if (TypeToken.primitiveToWrapper(t).isAssignableFrom(bs.getClass())) {
                        return (T) bs;
                    }
                    if (bs instanceof String) {
                        String val = (String) bs;
                        if (type == String.class) {
                            return val;
                        } else {
                            return (T) this.convert.convertFrom(type, val.getBytes(StandardCharsets.UTF_8));
                        }
                    }
                    return decryptValue(key, cryptor, type, (byte[]) bs);
                }));
    }

    // --------------------- del ------------------------------
    @Override
    public CompletableFuture delAsync(String... keys) {
        return toFuture(client.getKeys().deleteAsync(keys));
    }

    @Override
    public CompletableFuture delexAsync(String key, String expectedValue) {
        if (key == null) {
            return CompletableFuture.completedFuture(0L);
        }
        return toFuture(client.getScript(StringCodec.INSTANCE)
                .evalAsync(
                        RScript.Mode.READ_WRITE,
                        SCRIPT_DELEX,
                        RScript.ReturnType.INTEGER,
                        List.of(key),
                        expectedValue));
    }

    // --------------------- incrby ------------------------------
    @Override
    public CompletableFuture incrAsync(final String key) {
        return toFuture(client.getAtomicLong(key).incrementAndGetAsync());
    }

    @Override
    public CompletableFuture incrbyAsync(final String key, long num) {
        return toFuture(client.getAtomicLong(key).addAndGetAsync(num));
    }

    @Override
    public CompletableFuture incrbyFloatAsync(final String key, double num) {
        return toFuture(client.getAtomicDouble(key).addAndGetAsync(num));
    }

    // --------------------- decrby ------------------------------
    @Override
    public CompletableFuture decrAsync(final String key) {
        return toFuture(client.getAtomicLong(key).decrementAndGetAsync());
    }

    @Override
    public CompletableFuture decrbyAsync(final String key, long num) {
        return toFuture(client.getAtomicLong(key).addAndGetAsync(-num));
    }

    @Override
    public CompletableFuture hdelAsync(final String key, String... fields) {
        RMap map = client.getMap(key, MapByteArrayCodec.instance);
        return toFuture(map.fastRemoveAsync(fields));
    }

    @Override
    public CompletableFuture hlenAsync(final String key) {
        RMap map = client.getMap(key, MapByteArrayCodec.instance);
        return toFuture(map.sizeAsync().thenApply(v -> v.longValue()));
    }

    @Override
    public CompletableFuture> hkeysAsync(final String key) {
        RMap map = client.getMap(key, MapByteArrayCodec.instance);
        return toFuture(map.readAllKeySetAsync().thenApply(set -> set == null ? null : new ArrayList(set)));
    }

    @Override
    public CompletableFuture hincrAsync(final String key, String field) {
        RMap map = client.getMap(key, MapLongCodec.instance);
        return toFuture(map.addAndGetAsync(field, 1L));
    }

    @Override
    public CompletableFuture hincrbyAsync(final String key, String field, long num) {
        RMap map = client.getMap(key, MapLongCodec.instance);
        return toFuture(map.addAndGetAsync(field, num));
    }

    @Override
    public CompletableFuture hincrbyFloatAsync(final String key, String field, double num) {
        RMap map = client.getMap(key, MapDoubleCodec.instance);
        return toFuture(map.addAndGetAsync(field, num));
    }

    @Override
    public CompletableFuture hdecrAsync(final String key, String field) {
        RMap map = client.getMap(key, MapLongCodec.instance);
        return toFuture(map.addAndGetAsync(field, -1L));
    }

    @Override
    public CompletableFuture hdecrbyAsync(final String key, String field, long num) {
        RMap map = client.getMap(key, MapLongCodec.instance);
        return toFuture(map.addAndGetAsync(field, -num));
    }

    @Override
    public CompletableFuture hexistsAsync(final String key, String field) {
        RMap map = client.getMap(key, MapLongCodec.instance);
        return toFuture(map.containsKeyAsync(field));
    }

    @Override
    public  CompletableFuture hsetAsync(
            final String key, final String field, final Convert convert0, final Type type, final T value) {
        if (value == null) {
            return CompletableFuture.completedFuture(null);
        }
        RMap map = client.getMap(key, MapByteArrayCodec.instance);
        return toFuture(map.fastPutAsync(field, encryptValue(key, cryptor, type, convert0, value))
                .thenApply(r -> null));
    }

    @Override
    public  CompletableFuture hsetnxAsync(
            final String key, final String field, final Type type, final T value) {
        RMap map = client.getMap(key, MapByteArrayCodec.instance);
        return toFuture(map.fastPutIfAbsentAsync(field, encryptValue(key, cryptor, type, convert, value)));
    }

    @Override
    public  CompletableFuture hsetnxAsync(
            final String key, final String field, final Convert convert0, final Type type, final T value) {
        RMap map = client.getMap(key, MapByteArrayCodec.instance);
        return toFuture(map.fastPutIfAbsentAsync(field, encryptValue(key, cryptor, type, convert0, value)));
    }

    @Override
    public CompletableFuture hmsetAsync(final String key, final Serializable... values) {
        Map vals = new LinkedHashMap<>();
        for (int i = 0; i < values.length; i += 2) {
            vals.put(String.valueOf(values[i]), encryptValue(key, cryptor, convert, values[i + 1]));
        }
        RMap rm = client.getMap(key, MapByteArrayCodec.instance);
        return toFuture(rm.putAllAsync(vals));
    }

    @Override
    public CompletableFuture hmsetAsync(final String key, final Map map) {
        Map vals = new LinkedHashMap<>();
        map.forEach((k, v) -> {
            vals.put(k.toString(), encryptValue(key, cryptor, convert, v));
        });
        RMap rm = client.getMap(key, MapByteArrayCodec.instance);
        return toFuture(rm.putAllAsync(vals));
    }

    @Override
    public CompletableFuture> hmgetAsync(final String key, final Type type, final String... fields) {
        RMap map = client.getMap(key, MapByteArrayCodec.instance);
        return toFuture(map.getAllAsync(Utility.ofSet(fields)).thenApply(rs -> {
            List list = new ArrayList<>(fields.length);
            for (String field : fields) {
                byte[] bs = rs.get(field);
                if (bs == null) {
                    list.add(null);
                } else {
                    list.add(decryptValue(key, cryptor, type, bs));
                }
            }
            return list;
        }));
    }

    @Override
    public  CompletableFuture> hscanAsync(
            final String key, final Type type, AtomicLong cursor, int limit, String pattern) {
        RFuture future;
        RScript script = client.getScript(SCAN_CODEC);
        if (isEmpty(pattern)) {
            if (limit > 0) {
                String lua = "return redis.call('hscan', KEYS[1], ARGV[1], 'count', ARGV[2]);";
                future = script.evalAsync(
                        RScript.Mode.READ_ONLY,
                        lua,
                        RScript.ReturnType.MULTI,
                        List.of(key),
                        cursor.toString(),
                        String.valueOf(limit));
            } else {
                String lua = "return redis.call('hscan', KEYS[1], ARGV[1]);";
                future = script.evalAsync(
                        RScript.Mode.READ_ONLY, lua, RScript.ReturnType.MULTI, List.of(key), cursor.toString());
            }
        } else {
            if (limit > 0) {
                String lua = "return redis.call('hscan', KEYS[1], ARGV[1], 'match', ARGV[2], 'count', ARGV[3]);";
                future = script.evalAsync(
                        RScript.Mode.READ_ONLY,
                        lua,
                        RScript.ReturnType.MULTI,
                        List.of(key),
                        cursor.toString(),
                        pattern,
                        String.valueOf(limit));
            } else {
                String lua = "return redis.call('hscan', KEYS[1], ARGV[1], 'match', ARGV[2]);";
                future = script.evalAsync(
                        RScript.Mode.READ_ONLY,
                        lua,
                        RScript.ReturnType.MULTI,
                        List.of(key),
                        cursor.toString(),
                        pattern);
            }
        }
        return toFuture(future.thenApply(result -> {
            final Map rs = new LinkedHashMap<>();
            List kvs = (List) result.get(1);
            for (int i = 0; i < kvs.size(); i += 2) {
                String field = new String(kvs.get(i), StandardCharsets.UTF_8);
                byte[] bs = kvs.get(i + 1);
                if (bs != null) {
                    rs.put(field, decryptValue(key, cryptor, type, bs));
                }
            }
            cursor.set(Long.parseLong(new String((byte[]) result.get(0))));
            return rs;
        }));
    }

    @Override
    public CompletableFuture> scanAsync(AtomicLong cursor, int limit, String pattern) {
        RFuture future;
        RScript script = client.getScript(SCAN_CODEC);
        if (isEmpty(pattern)) {
            if (limit > 0) {
                String lua = "return redis.call('scan', ARGV[1], 'count', ARGV[2]);";
                future = script.evalAsync(
                        RScript.Mode.READ_ONLY,
                        lua,
                        RScript.ReturnType.MULTI,
                        List.of(),
                        cursor.toString(),
                        String.valueOf(limit));
            } else {
                String lua = "return redis.call('scan', ARGV[1]);";
                future = script.evalAsync(
                        RScript.Mode.READ_ONLY, lua, RScript.ReturnType.MULTI, List.of(), cursor.toString());
            }
        } else {
            if (limit > 0) {
                String lua = "return redis.call('scan', ARGV[1], 'match', ARGV[2], 'count', ARGV[3]);";
                future = script.evalAsync(
                        RScript.Mode.READ_ONLY,
                        lua,
                        RScript.ReturnType.MULTI,
                        List.of(),
                        cursor.toString(),
                        pattern,
                        String.valueOf(limit));
            } else {
                String lua = "return redis.call('scan', ARGV[1], 'match', ARGV[2]);";
                future = script.evalAsync(
                        RScript.Mode.READ_ONLY, lua, RScript.ReturnType.MULTI, List.of(), cursor.toString(), pattern);
            }
        }
        return toFuture(future.thenApply(result -> {
            final List rs = (List) getCollectionValue(null, (Collection) result.get(1), false, String.class);
            cursor.set(Long.parseLong(new String((byte[]) result.get(0))));
            return rs;
        }));
    }

    @Override
    public  CompletableFuture> sscanAsync(
            final String key, final Type componentType, AtomicLong cursor, int limit, String pattern) {
        RFuture future;
        RScript script = client.getScript(SCAN_CODEC);
        if (isEmpty(pattern)) {
            if (limit > 0) {
                String lua = "return redis.call('sscan', KEYS[1], ARGV[1], 'count', ARGV[2]);";
                future = script.evalAsync(
                        RScript.Mode.READ_ONLY,
                        lua,
                        RScript.ReturnType.MULTI,
                        List.of(key),
                        cursor.toString(),
                        String.valueOf(limit));
            } else {
                String lua = "return redis.call('sscan', KEYS[1], ARGV[1]);";
                future = script.evalAsync(
                        RScript.Mode.READ_ONLY, lua, RScript.ReturnType.MULTI, List.of(key), cursor.toString());
            }
        } else {
            if (limit > 0) {
                String lua = "return redis.call('sscan', KEYS[1], ARGV[1], 'match', ARGV[2], 'count', ARGV[3]);";
                future = script.evalAsync(
                        RScript.Mode.READ_ONLY,
                        lua,
                        RScript.ReturnType.MULTI,
                        List.of(key),
                        cursor.toString(),
                        pattern,
                        String.valueOf(limit));
            } else {
                String lua = "return redis.call('sscan', KEYS[1], ARGV[1], 'match', ARGV[2]);";
                future = script.evalAsync(
                        RScript.Mode.READ_ONLY,
                        lua,
                        RScript.ReturnType.MULTI,
                        List.of(key),
                        cursor.toString(),
                        pattern);
            }
        }
        return toFuture(future.thenApply(result -> {
            final Set rs = (Set) getCollectionValue(key, (Collection) result.get(1), true, componentType);
            cursor.set(Long.parseLong(new String((byte[]) result.get(0))));
            return rs;
        }));
    }

    @Override
    public  CompletableFuture hgetAsync(final String key, final String field, final Type type) {
        RMap map = client.getMap(key, MapByteArrayCodec.instance);
        return toFuture(map.getAsync(field).thenApply(r -> decryptValue(key, cryptor, type, r)));
    }

    @Override
    public CompletableFuture hstrlenAsync(final String key, final String field) {
        RMap map = client.getMap(key, MapByteArrayCodec.instance);
        return toFuture(map.valueSizeAsync(field).thenApply(r -> r.longValue()));
    }

    // --------------------- collection ------------------------------
    @Override
    public CompletableFuture scardAsync(String key) {
        return toFuture(client.getSet(key).sizeAsync().thenApply(v -> v.longValue()));
    }

    @Override
    public  CompletableFuture> sdiffAsync(final String key, final Type componentType, final String... key2s) {
        return toFuture(
                client.getSet(key, ByteArrayCodec.INSTANCE).readDiffAsync(key2s).thenApply(result ->
                        (Set) getCollectionValue(key, result, true, componentType)));
    }

    @Override
    public  CompletableFuture smoveAsync(String key, String key2, Type componentType, T member) {
        return toFuture(client.getSet(key, ByteArrayCodec.INSTANCE)
                .moveAsync(key2, encryptValue(key, cryptor, componentType, convert, member)));
    }

    @Override
    public  CompletableFuture> srandmemberAsync(String key, Type componentType, int count) {
        return toFuture(
                client.getSet(key, ByteArrayCodec.INSTANCE).randomAsync(count).thenApply(result ->
                        (List) getCollectionValue(key, result, false, componentType)));
    }

    @Override
    public  CompletableFuture> smismembersAsync(final String key, final String... members) {
        List bs = new ArrayList<>();
        for (String m : members) {
            bs.add(m.getBytes(StandardCharsets.UTF_8));
        }
        return toFuture(client.getSet(key, ByteArrayCodec.INSTANCE)
                .containsEachAsync(bs)
                .thenApply(result -> {
                    Set keys = new HashSet<>();
                    for (Object item : (List) result) {
                        keys.add(new String((byte[]) item, StandardCharsets.UTF_8));
                    }
                    List rs = new ArrayList<>();
                    for (String m : members) {
                        rs.add(keys.contains(m));
                    }
                    return rs;
                }));
    }

    @Override
    public CompletableFuture sdiffstoreAsync(final String key, final String srcKey, final String... srcKey2s) {
        return toFuture(
                client.getSet(key).diffAsync(Utility.append(srcKey, srcKey2s)).thenApply(v -> v.longValue()));
    }

    @Override
    public  CompletableFuture> sinterAsync(
            final String key, final Type componentType, final String... key2s) {
        return toFuture(client.getSet(key, ByteArrayCodec.INSTANCE)
                .readIntersectionAsync(key2s)
                .thenApply(result -> (Set) getCollectionValue(key, result, true, componentType)));
    }

    @Override
    public CompletableFuture sinterstoreAsync(final String key, final String srcKey, final String... srcKey2s) {
        return toFuture(client.getSet(key)
                .intersectionAsync(Utility.append(srcKey, srcKey2s))
                .thenApply(v -> v.longValue()));
    }

    @Override
    public  CompletableFuture> sunionAsync(
            final String key, final Type componentType, final String... key2s) {
        return toFuture(client.getSet(key, ByteArrayCodec.INSTANCE)
                .readUnionAsync(key2s)
                .thenApply(result -> (Set) getCollectionValue(key, result, true, componentType)));
    }

    @Override
    public CompletableFuture sunionstoreAsync(final String key, final String srcKey, final String... srcKey2s) {
        return toFuture(
                client.getSet(key).unionAsync(Utility.append(srcKey, srcKey2s)).thenApply(v -> v.longValue()));
    }

    @Override
    public  CompletableFuture> smembersAsync(String key, final Type componentType) {
        return toFuture((CompletionStage)
                client.getSet(key, ByteArrayCodec.INSTANCE).readAllAsync().thenApply(result ->
                        (Set) getCollectionValue(key, result, true, componentType)));
    }

    @Override
    public  CompletableFuture> lrangeAsync(String key, final Type componentType, int start, int stop) {
        return toFuture((CompletionStage) client.getList(key, ByteArrayCodec.INSTANCE)
                .rangeAsync(start, stop)
                .thenApply(result -> (List) getCollectionValue(key, result, false, componentType)));
    }

    @Override
    public  CompletableFuture> mgetAsync(final Type componentType, String... keys) {
        return toFuture(
                client.getBuckets(ByteArrayCodec.INSTANCE).getAsync(keys).thenApply(result -> {
                    List vs = new ArrayList();
                    for (String key : keys) {
                        vs.add(result.get(key));
                    }
                    return (List) getCollectionValue(keys[0], vs, false, componentType);
                }));
    }

    @Override
    public  CompletableFuture> hgetallAsync(final String key, final Type type) {
        return toFuture(
                client.getMap(key, MapByteArrayCodec.instance).readAllMapAsync().thenApply(map -> {
                    Map rs = new LinkedHashMap();
                    map.forEach((k, v) -> rs.put(k.toString(), decryptValue(k.toString(), cryptor, type, (byte[]) v)));
                    return rs;
                }));
    }

    @Override
    public  CompletableFuture> hvalsAsync(final String key, final Type type) {
        return toFuture(client.getMap(key, MapByteArrayCodec.instance)
                .readAllValuesAsync()
                .thenApply(list -> {
                    List rs = new ArrayList<>();
                    for (Object v : list) {
                        rs.add(decryptValue(key, cryptor, type, (byte[]) v));
                    }
                    return rs;
                }));
    }

    @Override
    public  CompletableFuture>> lrangesAsync(final Type componentType, final String... keys) {
        final RBatch batch = client.createBatch();
        for (String key : keys) {
            batch.getList(key, ByteArrayCodec.INSTANCE).readAllAsync();
        }
        return toFuture(batch.executeAsync().thenApply(resps -> {
            final Map> map = new LinkedHashMap<>();
            List list = (List) resps.getResponses();
            for (int i = 0; i < keys.length; i++) {
                String key = keys[i];
                List rs = new ArrayList<>();
                for (Object item : list.get(i)) {
                    byte[] bs = (byte[]) item;
                    if (bs == null) {
                        rs.add(null);
                    } else {
                        rs.add(decryptValue(key, cryptor, componentType, bs));
                    }
                }
                map.put(key, rs);
            }
            return map;
        }));
    }

    @Override
    public  CompletableFuture>> smembersAsync(final Type componentType, final String... keys) {
        final RBatch batch = client.createBatch();
        for (String key : keys) {
            batch.getSet(key, ByteArrayCodec.INSTANCE).readAllAsync();
        }
        return toFuture(batch.executeAsync().thenApply(resps -> {
            final Map> map = new LinkedHashMap<>();
            List list = (List) resps.getResponses();
            for (int i = 0; i < keys.length; i++) {
                String key = keys[i];
                Set rs = new LinkedHashSet<>();
                for (Object item : list.get(i)) {
                    byte[] bs = (byte[]) item;
                    if (bs == null) {
                        rs.add(null);
                    } else {
                        rs.add(decryptValue(key, cryptor, componentType, bs));
                    }
                }
                map.put(key, rs);
            }
            return map;
        }));
    }

    // --------------------- existsItem ------------------------------
    @Override
    public  CompletableFuture sismemberAsync(String key, final Type componentType, T value) {
        final RSet bucket = client.getSet(key, ByteArrayCodec.INSTANCE);
        return toFuture(bucket.containsAsync(encryptValue(key, cryptor, componentType, convert, value)));
    }

    // --------------------- push ------------------------------
    @Override
    public  CompletableFuture rpushAsync(String key, final Type componentType, T... values) {
        List list = new ArrayList<>();
        for (T value : values) {
            list.add(encryptValue(key, cryptor, componentType, convert, value));
        }
        final RDeque bucket = client.getDeque(key, ByteArrayCodec.INSTANCE);
        return toFuture(
                bucket.addLastAsync(list.toArray(new byte[list.size()][])).thenApply(r -> null));
    }

    @Override
    public  CompletableFuture rpushxAsync(String key, final Type componentType, T... values) {
        List list = new ArrayList<>();
        for (T value : values) {
            list.add(encryptValue(key, cryptor, componentType, convert, value));
        }
        final RDeque bucket = client.getDeque(key, ByteArrayCodec.INSTANCE);
        return toFuture(bucket.isExistsAsync()
                .thenCompose(b -> b
                        ? bucket.addLastAsync(list.toArray(new byte[list.size()][]))
                        : CompletableFuture.completedFuture(null))
                .thenApply(r -> null));
    }

    @Override
    public  CompletableFuture rpopAsync(String key, final Type componentType) {
        final RDeque bucket = client.getDeque(key, ByteArrayCodec.INSTANCE);
        return toFuture(bucket.pollLastAsync()).thenApply(v -> decryptValue(key, cryptor, componentType, v));
    }

    @Override
    public  CompletableFuture rpoplpushAsync(final String key, final String key2, final Type componentType) {
        final RDeque bucket = client.getDeque(key, ByteArrayCodec.INSTANCE);
        return toFuture(bucket.pollLastAndOfferFirstToAsync(key2))
                .thenApply(v -> decryptValue(key, cryptor, componentType, v));
    }

    @Override
    public  CompletableFuture lpushAsync(String key, final Type componentType, T... values) {
        List list = new ArrayList<>();
        for (T value : values) {
            list.add(encryptValue(key, cryptor, componentType, convert, value));
        }
        final RDeque bucket = client.getDeque(key, ByteArrayCodec.INSTANCE);
        return toFuture(
                bucket.addFirstAsync(list.toArray(new byte[list.size()][])).thenApply(r -> null));
    }

    @Override
    public  CompletableFuture lpushxAsync(String key, final Type componentType, T... values) {
        List list = new ArrayList<>();
        for (T value : values) {
            list.add(encryptValue(key, cryptor, componentType, convert, value));
        }
        final RDeque bucket = client.getDeque(key, ByteArrayCodec.INSTANCE);
        return toFuture(bucket.isExistsAsync()
                .thenCompose(b -> b
                        ? bucket.addFirstAsync(list.toArray(new byte[list.size()][]))
                        : CompletableFuture.completedFuture(null))
                .thenApply(r -> null));
    }

    @Override
    public  CompletableFuture lpopAsync(String key, final Type componentType) {
        final RDeque bucket = client.getDeque(key, ByteArrayCodec.INSTANCE);
        return toFuture(bucket.pollFirstAsync()).thenApply(v -> decryptValue(key, cryptor, componentType, v));
    }

    @Override
    public  CompletableFuture lindexAsync(String key, Type componentType, int index) {
        final RList bucket = client.getList(key, ByteArrayCodec.INSTANCE);
        return toFuture(bucket.getAsync(index)).thenApply(v -> decryptValue(key, cryptor, componentType, v));
    }

    @Override
    public  CompletableFuture linsertBeforeAsync(String key, Type componentType, T pivot, T value) {
        final RList bucket = client.getList(key, ByteArrayCodec.INSTANCE);
        return toFuture(bucket.addBeforeAsync(
                        encryptValue(key, cryptor, componentType, convert, pivot),
                        encryptValue(key, cryptor, componentType, convert, value))
                .thenApply(v -> v.longValue()));
    }

    @Override
    public  CompletableFuture linsertAfterAsync(String key, Type componentType, T pivot, T value) {
        final RList bucket = client.getList(key, ByteArrayCodec.INSTANCE);
        return toFuture(bucket.addAfterAsync(
                        encryptValue(key, cryptor, componentType, convert, pivot),
                        encryptValue(key, cryptor, componentType, convert, value))
                .thenApply(v -> v.longValue()));
    }

    @Override
    public CompletableFuture llenAsync(String key) {
        return toFuture(client.getList(key).sizeAsync().thenApply(v -> v.longValue()));
    }

    @Override
    public CompletableFuture ltrimAsync(final String key, int start, int stop) {
        final RList bucket = client.getList(key, ByteArrayCodec.INSTANCE);
        return toFuture(bucket.trimAsync(start, stop));
    }

    // --------------------- lrem ------------------------------
    @Override
    public  CompletableFuture lremAsync(String key, final Type componentType, T value) {
        return toFuture(client.getList(key, ByteArrayCodec.INSTANCE)
                .removeAsync(encryptValue(key, cryptor, componentType, convert, value))
                .thenApply(r -> r ? 1L : 0L));
    }

    // --------------------- sadd ------------------------------
    @Override
    public  CompletableFuture saddAsync(String key, Type componentType, T... values) {
        List list = new ArrayList<>();
        for (T value : values) {
            list.add(encryptValue(key, cryptor, componentType, convert, value));
        }
        final RSet bucket = client.getSet(key, ByteArrayCodec.INSTANCE);
        return toFuture(bucket.addAllAsync(list).thenApply(r -> null));
    }

    @Override
    public  CompletableFuture spopAsync(String key, Type componentType) {
        final RSet bucket = client.getSet(key, ByteArrayCodec.INSTANCE);
        return toFuture(bucket.removeRandomAsync()
                .thenApply(bs -> bs == null ? null : decryptValue(key, cryptor, componentType, bs)));
    }

    @Override
    public  CompletableFuture> spopAsync(String key, int count, Type componentType) {
        final RSet bucket = client.getSet(key, ByteArrayCodec.INSTANCE);
        return toFuture(bucket.removeRandomAsync(count).thenApply((Set bslist) -> {
            if (isEmpty(bslist)) {
                return new LinkedHashSet();
            }
            Set rs = new LinkedHashSet<>();
            for (byte[] bs : bslist) {
                rs.add(decryptValue(key, cryptor, componentType, bs));
            }
            return rs;
        }));
    }

    @Override
    public  CompletableFuture sremAsync(String key, final Type componentType, T... values) {
        List list = new ArrayList<>();
        for (T value : values) {
            list.add(encryptValue(key, cryptor, componentType, convert, value));
        }
        return toFuture(
                client.getSet(key, ByteArrayCodec.INSTANCE).removeAllAsync(list).thenApply(r -> r ? 1L : 0L));
    }

    // --------------------- sorted set ------------------------------
    @Override
    public CompletableFuture zaddAsync(String key, CacheScoredValue... values) {
        Map map = new HashMap<>();
        for (CacheScoredValue value : values) {
            map.put(value.getValue(), value.getScore().doubleValue());
        }
        final RScoredSortedSet bucket = client.getScoredSortedSet(key, StringCodec.INSTANCE);
        return toFuture(bucket.addAllAsync(map).thenApply(r -> null));
    }

    @Override
    public  CompletableFuture zincrbyAsync(String key, CacheScoredValue value) {
        final RScoredSortedSet bucket = client.getScoredSortedSet(key, StringCodec.INSTANCE);
        return toFuture(bucket.addScoreAsync(value.getValue(), value.getScore())
                .thenApply(v -> (T) decryptScore(value.getScore().getClass(), v)));
    }

    @Override
    public CompletableFuture zremAsync(String key, String... members) {
        final RScoredSortedSet bucket = client.getScoredSortedSet(key, StringCodec.INSTANCE);
        return toFuture(bucket.removeAllAsync(List.of(members)).thenApply(r -> r ? 1L : 0L));
    }

    @Override
    public  CompletableFuture> zmscoreAsync(
            String key, Class scoreType, String... members) {
        final RScoredSortedSet bucket = client.getScoredSortedSet(key, StringCodec.INSTANCE);
        return toFuture(bucket.getScoreAsync(List.of(members))
                .thenApply(list ->
                        list.stream().map(v -> decryptScore(scoreType, v)).collect(Collectors.toList())));
    }

    @Override
    public  CompletableFuture zscoreAsync(String key, Class scoreType, String member) {
        final RScoredSortedSet bucket = client.getScoredSortedSet(key, StringCodec.INSTANCE);
        return toFuture(bucket.getScoreAsync(member).thenApply(r -> decryptScore(scoreType, r)));
    }

    @Override
    public CompletableFuture zcardAsync(String key) {
        final RScoredSortedSet bucket = client.getScoredSortedSet(key, StringCodec.INSTANCE);
        return toFuture(bucket.sizeAsync().thenApply(r -> r.longValue()));
    }

    @Override
    public CompletableFuture zrankAsync(String key, String member) {
        final RScoredSortedSet bucket = client.getScoredSortedSet(key, StringCodec.INSTANCE);
        return toFuture(bucket.rankAsync(member).thenApply(r -> r == null ? null : r.longValue()));
    }

    @Override
    public CompletableFuture zrevrankAsync(String key, String member) {
        final RScoredSortedSet bucket = client.getScoredSortedSet(key, StringCodec.INSTANCE);
        return toFuture(bucket.revRankAsync(member).thenApply(r -> r == null ? null : r.longValue()));
    }

    @Override
    public CompletableFuture> zrangeAsync(String key, int start, int stop) {
        final RScoredSortedSet bucket = client.getScoredSortedSet(key, StringCodec.INSTANCE);
        return toFuture(bucket.entryRangeAsync(start, stop).thenApply(result -> getSortedListValue(key, result)));
    }

    @Override
    public CompletableFuture> zscanAsync(
            String key, Type scoreType, AtomicLong cursor, int limit, String pattern) {
        RFuture future;
        RScript script = client.getScript(SCAN_CODEC);
        if (isEmpty(pattern)) {
            if (limit > 0) {
                String lua = "return redis.call('zscan', KEYS[1], ARGV[1], 'count', ARGV[2]);";
                future = script.evalAsync(
                        RScript.Mode.READ_ONLY,
                        lua,
                        RScript.ReturnType.MULTI,
                        List.of(key),
                        cursor.toString(),
                        String.valueOf(limit));
            } else {
                String lua = "return redis.call('zscan', KEYS[1], ARGV[1]);";
                future = script.evalAsync(
                        RScript.Mode.READ_ONLY, lua, RScript.ReturnType.MULTI, List.of(key), cursor.toString());
            }
        } else {
            if (limit > 0) {
                String lua = "return redis.call('zscan', KEYS[1], ARGV[1], 'match', ARGV[2], 'count', ARGV[3]);";
                future = script.evalAsync(
                        RScript.Mode.READ_ONLY,
                        lua,
                        RScript.ReturnType.MULTI,
                        List.of(key),
                        cursor.toString(),
                        pattern,
                        String.valueOf(limit));
            } else {
                String lua = "return redis.call('zscan', KEYS[1], ARGV[1], 'match', ARGV[2]);";
                future = script.evalAsync(
                        RScript.Mode.READ_ONLY,
                        lua,
                        RScript.ReturnType.MULTI,
                        List.of(key),
                        cursor.toString(),
                        pattern);
            }
        }
        return toFuture(future.thenApply(result -> {
            final List rs = new ArrayList<>();
            List kvs = (List) result.get(1);
            for (int i = 0; i < kvs.size(); i += 2) {
                String field = new String(kvs.get(i), StandardCharsets.UTF_8);
                byte[] bs = kvs.get(i + 1);
                if (bs != null) {
                    rs.add(CacheScoredValue.create(decryptValue(key, cryptor, scoreType, bs), field));
                }
            }
            cursor.set(Long.parseLong(new String((byte[]) result.get(0))));
            return rs;
        }));
    }

    // --------------------- keys ------------------------------
    @Override
    public CompletableFuture> keysAsync(String pattern) {
        if (isEmpty(pattern)) {
            return client.reactive().getKeys().getKeys().collectList().toFuture();
        } else {
            return client.reactive()
                    .getKeys()
                    .getKeysByPattern(pattern)
                    .collectList()
                    .toFuture();
        }
    }

    // --------------------- dbsize ------------------------------
    @Override
    public CompletableFuture dbsizeAsync() {
        return toFuture(client.getKeys().countAsync());
    }

    @Override
    public CompletableFuture flushdbAsync() {
        return toFuture(client.getKeys().flushdbAsync());
    }

    @Override
    public CompletableFuture flushallAsync() {
        return toFuture(client.getKeys().flushallAsync());
    }

    protected static class MapByteArrayCodec extends ByteArrayCodec {

        public static final MapByteArrayCodec instance = new MapByteArrayCodec();

        @Override
        public org.redisson.client.protocol.Decoder getMapKeyDecoder() {
            return StringCodec.INSTANCE.getValueDecoder();
        }

        @Override
        public org.redisson.client.protocol.Encoder getMapKeyEncoder() {
            return StringCodec.INSTANCE.getValueEncoder();
        }
    }

    protected static class MapStringCodec extends StringCodec {

        public static final MapStringCodec instance = new MapStringCodec();

        @Override
        public org.redisson.client.protocol.Decoder getMapKeyDecoder() {
            return StringCodec.INSTANCE.getValueDecoder();
        }

        @Override
        public org.redisson.client.protocol.Encoder getMapKeyEncoder() {
            return StringCodec.INSTANCE.getValueEncoder();
        }
    }

    protected static class MapLongCodec extends org.redisson.client.codec.LongCodec {

        public static final MapLongCodec instance = new MapLongCodec();

        @Override
        public org.redisson.client.protocol.Decoder getMapKeyDecoder() {
            return StringCodec.INSTANCE.getValueDecoder();
        }

        @Override
        public org.redisson.client.protocol.Encoder getMapKeyEncoder() {
            return StringCodec.INSTANCE.getValueEncoder();
        }
    }

    protected static class MapDoubleCodec extends org.redisson.client.codec.DoubleCodec {

        public static final MapLongCodec instance = new MapLongCodec();

        @Override
        public org.redisson.client.protocol.Decoder getMapKeyDecoder() {
            return StringCodec.INSTANCE.getValueDecoder();
        }

        @Override
        public org.redisson.client.protocol.Encoder getMapKeyEncoder() {
            return StringCodec.INSTANCE.getValueEncoder();
        }
    }

    // -------------------------- 过期方法 ----------------------------------
    @Override
    @Deprecated(since = "2.8.0")
    public  CompletableFuture>> getCollectionMapAsync(
            final boolean set, final Type componentType, final String... keys) {
        final CompletableFuture>> rsFuture = new CompletableFuture<>();
        final Map> map = new LinkedHashMap<>();
        final ReentrantLock mapLock = new ReentrantLock();
        final CompletableFuture[] futures = new CompletableFuture[keys.length];
        if (!set) { // list
            for (int i = 0; i < keys.length; i++) {
                final String key = keys[i];
                futures[i] = toFuture(client.getList(key, ByteArrayCodec.INSTANCE)
                        .readAllAsync()
                        .thenApply(list -> {
                            if (isEmpty(list)) {
                                return list;
                            }
                            List rs = new ArrayList<>();
                            for (Object item : list) {
                                byte[] bs = (byte[]) item;
                                if (bs == null) {
                                    rs.add(null);
                                } else {
                                    rs.add(decryptValue(key, cryptor, componentType, bs));
                                }
                            }
                            mapLock.lock();
                            try {
                                map.put(key, rs);
                            } finally {
                                mapLock.unlock();
                            }
                            return rs;
                        }));
            }
        } else {
            for (int i = 0; i < keys.length; i++) {
                final String key = keys[i];
                futures[i] = toFuture(client.getSet(key, ByteArrayCodec.INSTANCE)
                        .readAllAsync()
                        .thenApply(list -> {
                            if (isEmpty(list)) {
                                return list;
                            }
                            List rs = new ArrayList<>();
                            for (Object item : list) {
                                byte[] bs = (byte[]) item;
                                if (bs == null) {
                                    rs.add(null);
                                } else {
                                    rs.add(decryptValue(key, cryptor, componentType, bs));
                                }
                            }
                            mapLock.lock();
                            try {
                                map.put(key, rs);
                            } finally {
                                mapLock.unlock();
                            }
                            return rs;
                        }));
            }
        }
        CompletableFuture.allOf(futures).whenComplete((w, e) -> {
            if (e != null) {
                rsFuture.completeExceptionally(e);
            } else {
                rsFuture.complete(map);
            }
        });
        return rsFuture;
    }

    @Override
    @Deprecated(since = "2.8.0")
    public CompletableFuture getCollectionSizeAsync(String key) {
        return toFuture(client.getScript()
                .evalAsync(RScript.Mode.READ_ONLY, "return redis.call('TYPE', '" + key + "')", RScript.ReturnType.VALUE)
                .thenCompose(type -> {
                    if (String.valueOf(type).contains("list")) {
                        return client.getList(key).sizeAsync();
                    } else {
                        return client.getSet(key).sizeAsync();
                    }
                }));
    }

    @Override
    @Deprecated(since = "2.8.0")
    public  CompletableFuture> getCollectionAsync(String key, final Type componentType) {
        return toFuture(client.getScript()
                .evalAsync(RScript.Mode.READ_ONLY, "return redis.call('TYPE', '" + key + "')", RScript.ReturnType.VALUE)
                .thenCompose(type -> {
                    if (String.valueOf(type).contains("list")) {
                        return (CompletionStage) client.getList(key, ByteArrayCodec.INSTANCE)
                                .readAllAsync()
                                .thenApply(result -> (List) getCollectionValue(key, result, false, componentType));
                    } else {
                        return (CompletionStage) client.getSet(key, ByteArrayCodec.INSTANCE)
                                .readAllAsync()
                                .thenApply(result -> (Set) getCollectionValue(key, result, true, componentType));
                    }
                }));
    }

    @Override
    @Deprecated(since = "2.8.0")
    public CompletableFuture getLongArrayAsync(String... keys) {
        return toFuture(client.getBuckets(org.redisson.client.codec.LongCodec.INSTANCE)
                .getAsync(keys)
                .thenApply(map -> {
                    Long[] rs = new Long[keys.length];
                    for (int i = 0; i < rs.length; i++) {
                        rs[i] = (Long) map.get(keys[i]);
                    }
                    return rs;
                }));
    }

    @Override
    @Deprecated(since = "2.8.0")
    public CompletableFuture getStringArrayAsync(String... keys) {
        return toFuture(client.getBuckets(StringCodec.INSTANCE).getAsync(keys).thenApply(map -> {
            String[] rs = new String[keys.length];
            for (int i = 0; i < rs.length; i++) {
                rs[i] = decryptValue(keys[i], cryptor, (String) map.get(keys[i]));
            }
            return rs;
        }));
    }

    @Override
    @Deprecated(since = "2.8.0")
    public CompletableFuture> getStringCollectionAsync(String key) {
        return toFuture(client.getScript()
                .evalAsync(RScript.Mode.READ_ONLY, "return redis.call('TYPE', '" + key + "')", RScript.ReturnType.VALUE)
                .thenCompose(type -> {
                    if (String.valueOf(type).contains("list")) {
                        return (CompletionStage) client.getList(key, StringCodec.INSTANCE)
                                .readAllAsync()
                                .thenApply(list -> {
                                    if (isEmpty(list) || cryptor == null) {
                                        return list;
                                    }
                                    List rs = new ArrayList<>();
                                    for (Object item : list) {
                                        rs.add(item == null ? null : decryptValue(key, cryptor, item.toString()));
                                    }
                                    return rs;
                                });
                    } else {
                        return (CompletionStage) client.getSet(key, StringCodec.INSTANCE)
                                .readAllAsync()
                                .thenApply(set -> {
                                    if (set == null) {
                                        return set;
                                    }
                                    if (set.isEmpty() || cryptor == null) {
                                        return new ArrayList<>(set);
                                    }
                                    List rs = new ArrayList<>(); // 不用set
                                    for (Object item : set) {
                                        rs.add(item == null ? null : decryptValue(key, cryptor, item.toString()));
                                    }
                                    return rs;
                                });
                    }
                }));
    }

    @Override
    @Deprecated(since = "2.8.0")
    public CompletableFuture>> getStringCollectionMapAsync(
            final boolean set, String... keys) {
        final CompletableFuture>> rsFuture = new CompletableFuture<>();
        final Map> map = new LinkedHashMap<>();
        final ReentrantLock mapLock = new ReentrantLock();
        final CompletableFuture[] futures = new CompletableFuture[keys.length];
        if (!set) { // list
            for (int i = 0; i < keys.length; i++) {
                final String key = keys[i];
                futures[i] = toFuture(
                        client.getList(key, StringCodec.INSTANCE).readAllAsync().thenApply((Collection r) -> {
                            if (r != null) {
                                if (cryptor != null && !r.isEmpty()) {
                                    List rs = new ArrayList<>();
                                    for (Object item : r) {
                                        rs.add(item == null ? null : decryptValue(key, cryptor, item.toString()));
                                    }
                                    r = rs;
                                }
                                mapLock.lock();
                                try {
                                    map.put(key, r);
                                } finally {
                                    mapLock.unlock();
                                }
                            }
                            return null;
                        }));
            }
        } else {
            for (int i = 0; i < keys.length; i++) {
                final String key = keys[i];
                futures[i] = toFuture(
                        client.getSet(key, StringCodec.INSTANCE).readAllAsync().thenApply((Collection r) -> {
                            if (r != null) {
                                boolean changed = false;
                                if (cryptor != null && !r.isEmpty()) {
                                    List rs = new ArrayList<>();
                                    for (Object item : r) {
                                        rs.add(item == null ? null : decryptValue(key, cryptor, item.toString()));
                                    }
                                    r = rs;
                                    changed = true;
                                }
                                mapLock.lock();
                                try {
                                    map.put(key, changed ? r : new ArrayList(r));
                                } finally {
                                    mapLock.unlock();
                                }
                            }
                            return null;
                        }));
            }
        }
        CompletableFuture.allOf(futures).whenComplete((w, e) -> {
            if (e != null) {
                rsFuture.completeExceptionally(e);
            } else {
                rsFuture.complete(map);
            }
        });
        return rsFuture;
    }

    @Override
    @Deprecated(since = "2.8.0")
    public CompletableFuture> getLongCollectionAsync(String key) {
        return toFuture(client.getScript()
                .evalAsync(RScript.Mode.READ_ONLY, "return redis.call('TYPE', '" + key + "')", RScript.ReturnType.VALUE)
                .thenCompose(type -> {
                    if (String.valueOf(type).contains("list")) {
                        return (CompletionStage) client.getList(key, org.redisson.client.codec.LongCodec.INSTANCE)
                                .readAllAsync();
                    } else {
                        return (CompletionStage) client.getSet(key, org.redisson.client.codec.LongCodec.INSTANCE)
                                .readAllAsync()
                                .thenApply(s -> s == null ? null : new ArrayList(s));
                    }
                }));
    }

    @Override
    @Deprecated(since = "2.8.0")
    public CompletableFuture>> getLongCollectionMapAsync(
            final boolean set, String... keys) {
        final CompletableFuture>> rsFuture = new CompletableFuture<>();
        final Map> map = new LinkedHashMap<>();
        final ReentrantLock mapLock = new ReentrantLock();
        final CompletableFuture[] futures = new CompletableFuture[keys.length];
        if (!set) { // list
            for (int i = 0; i < keys.length; i++) {
                final String key = keys[i];
                futures[i] = toFuture(client.getList(key, org.redisson.client.codec.LongCodec.INSTANCE)
                        .readAllAsync()
                        .thenApply(r -> {
                            if (r != null) {
                                mapLock.lock();
                                try {
                                    map.put(key, (Collection) r);
                                } finally {
                                    mapLock.unlock();
                                }
                            }
                            return null;
                        }));
            }
        } else {
            for (int i = 0; i < keys.length; i++) {
                final String key = keys[i];
                futures[i] = toFuture(client.getSet(key, org.redisson.client.codec.LongCodec.INSTANCE)
                        .readAllAsync()
                        .thenApply(r -> {
                            if (r != null) {
                                mapLock.lock();
                                try {
                                    map.put(key, new ArrayList(r));
                                } finally {
                                    mapLock.unlock();
                                }
                            }
                            return null;
                        }));
            }
        }
        CompletableFuture.allOf(futures).whenComplete((w, e) -> {
            if (e != null) {
                rsFuture.completeExceptionally(e);
            } else {
                rsFuture.complete(map);
            }
        });
        return rsFuture;
    }

    // --------------------- getexCollection ------------------------------
    @Override
    @Deprecated(since = "2.8.0")
    public  CompletableFuture> getexCollectionAsync(
            String key, int expireSeconds, final Type componentType) {
        return (CompletableFuture)
                expireAsync(key, expireSeconds).thenCompose(v -> getCollectionAsync(key, componentType));
    }

    @Override
    @Deprecated(since = "2.8.0")
    public CompletableFuture> getexStringCollectionAsync(String key, int expireSeconds) {
        return (CompletableFuture) expireAsync(key, expireSeconds).thenCompose(v -> getStringCollectionAsync(key));
    }

    @Override
    @Deprecated(since = "2.8.0")
    public CompletableFuture> getexLongCollectionAsync(String key, int expireSeconds) {
        return (CompletableFuture) expireAsync(key, expireSeconds).thenCompose(v -> getLongCollectionAsync(key));
    }

    @Override
    @Deprecated(since = "2.8.0")
    public Long[] getLongArray(final String... keys) {
        Map map =
                client.getBuckets(org.redisson.client.codec.LongCodec.INSTANCE).get(keys);
        Long[] rs = new Long[keys.length];
        for (int i = 0; i < rs.length; i++) {
            rs[i] = map.get(keys[i]);
        }
        return rs;
    }

    @Override
    @Deprecated(since = "2.8.0")
    public String[] getStringArray(final String... keys) {
        Map map = client.getBuckets(StringCodec.INSTANCE).get(keys);
        String[] rs = new String[keys.length];
        for (int i = 0; i < rs.length; i++) {
            rs[i] = decryptValue(keys[i], cryptor, map.get(keys[i]));
        }
        return rs;
    }
}