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

com.alicp.jetcache.redis.lettuce.RedisLettuceCache Maven / Gradle / Ivy

The newest version!
package com.alicp.jetcache.redis.lettuce;

import com.alicp.jetcache.CacheConfig;
import com.alicp.jetcache.CacheConfigException;
import com.alicp.jetcache.CacheGetResult;
import com.alicp.jetcache.CacheResult;
import com.alicp.jetcache.CacheResultCode;
import com.alicp.jetcache.CacheValueHolder;
import com.alicp.jetcache.MultiGetResult;
import com.alicp.jetcache.ResultData;
import com.alicp.jetcache.external.AbstractExternalCache;
import com.alicp.jetcache.support.JetCacheExecutor;
import io.lettuce.core.AbstractRedisClient;
import io.lettuce.core.KeyValue;
import io.lettuce.core.RedisFuture;
import io.lettuce.core.SetArgs;
import io.lettuce.core.api.async.RedisKeyAsyncCommands;
import io.lettuce.core.api.async.RedisStringAsyncCommands;
import io.lettuce.core.api.sync.RedisStringCommands;
import io.lettuce.core.cluster.api.async.RedisClusterAsyncCommands;
import io.lettuce.core.cluster.api.reactive.RedisClusterReactiveCommands;
import io.lettuce.core.cluster.api.sync.RedisClusterCommands;

import java.time.Duration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

/**
 * Created on 2017/4/28.
 *
 * @author huangli
 */
public class RedisLettuceCache extends AbstractExternalCache {

    private final RedisLettuceCacheConfig config;

    private final Function valueEncoder;
    private final Function valueDecoder;

    private final AbstractRedisClient client;
    private final LettuceConnectionManager lettuceConnectionManager;
    private final RedisStringCommands stringCommands;
    private final RedisStringAsyncCommands stringAsyncCommands;
    private final RedisKeyAsyncCommands keyAsyncCommands;

    public RedisLettuceCache(RedisLettuceCacheConfig config) {
        super(config);
        this.config = config;
        this.valueEncoder = config.getValueEncoder();
        this.valueDecoder = config.getValueDecoder();
        if (config.getRedisClient() == null) {
            throw new CacheConfigException("RedisClient is required");
        }
        if (config.isExpireAfterAccess()) {
            throw new CacheConfigException("expireAfterAccess is not supported");
        }

        client = config.getRedisClient();

        lettuceConnectionManager = config.getConnectionManager();
        lettuceConnectionManager.init(client, config.getConnection());
        stringCommands = (RedisStringCommands) lettuceConnectionManager.commands(client);
        stringAsyncCommands = (RedisStringAsyncCommands) lettuceConnectionManager.asyncCommands(client);
        keyAsyncCommands = (RedisKeyAsyncCommands) stringAsyncCommands;
    }

    @Override
    public  T unwrap(Class clazz) {
        Objects.requireNonNull(clazz);
        if (AbstractRedisClient.class.isAssignableFrom(clazz)) {
            return (T) client;
        } else if (RedisClusterCommands.class.isAssignableFrom(clazz)) {
            // RedisCommands extends RedisClusterCommands
            return (T) stringCommands;
        } else if (RedisClusterAsyncCommands.class.isAssignableFrom(clazz)) {
            // RedisAsyncCommands extends RedisClusterAsyncCommands
            return (T) stringAsyncCommands;
        } else if (RedisClusterReactiveCommands.class.isAssignableFrom(clazz)) {
            // RedisReactiveCommands extends RedisClusterReactiveCommands
            return (T) lettuceConnectionManager.reactiveCommands(client);
        }
        throw new IllegalArgumentException(clazz.getName());
    }

    @Override
    public CacheConfig config() {
        return config;
    }

    private void setTimeout(CacheResult cr) {
        Duration d = Duration.ofMillis(config.getAsyncResultTimeoutInMillis());
        cr.setTimeout(d);
    }

    @Override
    protected CacheResult do_PUT(K key, V value, long expireAfterWrite, TimeUnit timeUnit) {
        try {
            CacheValueHolder holder = new CacheValueHolder(value, timeUnit.toMillis(expireAfterWrite));
            byte[] newKey = buildKey(key);
            RedisFuture future = stringAsyncCommands.psetex(newKey, timeUnit.toMillis(expireAfterWrite), valueEncoder.apply(holder));
            CacheResult result = new CacheResult(future.handle((rt, ex) -> {
                if (ex != null) {
                    JetCacheExecutor.defaultExecutor().execute(() -> logError("PUT", key, ex));
                    return new ResultData(ex);
                } else {
                    if ("OK".equals(rt)) {
                        return new ResultData(CacheResultCode.SUCCESS, null, null);
                    } else {
                        return new ResultData(CacheResultCode.FAIL, rt, null);
                    }
                }
            }));
            setTimeout(result);
            return result;
        } catch (Exception ex) {
            logError("PUT", key, ex);
            return new CacheResult(ex);
        }
    }

    @Override
    protected CacheResult do_PUT_ALL(Map map, long expireAfterWrite, TimeUnit timeUnit) {
        try {
            CompletionStage future = CompletableFuture.completedFuture(0);
            for (Map.Entry en : map.entrySet()) {
                CacheValueHolder holder = new CacheValueHolder(en.getValue(), timeUnit.toMillis(expireAfterWrite));
                RedisFuture resp = stringAsyncCommands.psetex(buildKey(en.getKey()), timeUnit.toMillis(expireAfterWrite), valueEncoder.apply(holder));
                future = future.thenCombine(resp, (failCount, respStr) -> "OK".equals(respStr) ? failCount : failCount + 1);
            }
            CacheResult result = new CacheResult(future.handle((failCount, ex) -> {
                if (ex != null) {
                    JetCacheExecutor.defaultExecutor().execute(() -> logError("PUT_ALL", "map(" + map.size() + ")", ex));
                    return new ResultData(ex);
                } else {
                    if (failCount == 0) {
                        return new ResultData(CacheResultCode.SUCCESS, null, null);
                    } else if (failCount == map.size()) {
                        return new ResultData(CacheResultCode.FAIL, null, null);
                    } else {
                        return new ResultData(CacheResultCode.PART_SUCCESS, null, null);
                    }
                }
            }));
            setTimeout(result);
            return result;
        } catch (Exception ex) {
            logError("PUT_ALL", "map(" + map.size() + ")", ex);
            return new CacheResult(ex);
        }
    }

    @Override
    protected CacheGetResult do_GET(K key) {
        try {
            byte[] newKey = buildKey(key);
            RedisFuture future = stringAsyncCommands.get(newKey);
            CacheGetResult result = new CacheGetResult<>(future.handleAsync((valueBytes, ex) -> {
                if (ex != null) {
                    logError("GET", key, ex);
                    return new ResultData(ex);
                } else {
                    try {
                        if (valueBytes != null) {
                            CacheValueHolder holder = (CacheValueHolder) valueDecoder.apply(valueBytes);
                            if (System.currentTimeMillis() >= holder.getExpireTime()) {
                                return new ResultData(CacheResultCode.EXPIRED, null, null);
                            } else {
                                return new ResultData(CacheResultCode.SUCCESS, null, holder);
                            }
                        } else {
                            return new ResultData(CacheResultCode.NOT_EXISTS, null, null);
                        }
                    } catch (Exception exception) {
                        logError("GET", key, exception);
                        return new ResultData(exception);
                    }
                }
            }, JetCacheExecutor.defaultExecutor()));
            setTimeout(result);
            return result;
        } catch (Exception ex) {
            logError("GET", key, ex);
            return new CacheGetResult(ex);
        }
    }

    @Override
    protected MultiGetResult do_GET_ALL(Set keys) {
        try {
            ArrayList keyList = new ArrayList(keys);
            byte[][] newKeys = keyList.stream().map((k) -> buildKey(k)).toArray(byte[][]::new);

            Map> resultMap = new HashMap<>();
            if (newKeys.length == 0) {
                return new MultiGetResult(CacheResultCode.SUCCESS, null, resultMap);
            }
            RedisFuture>> mgetResults = stringAsyncCommands.mget(newKeys);
            MultiGetResult result = new MultiGetResult<>(mgetResults.handleAsync((list, ex) -> {
                if (ex != null) {
                    logError("GET_ALL", "keys(" + keys.size() + ")", ex);
                    return new ResultData(ex);
                } else {
                    try {
                        for (int i = 0; i < list.size(); i++) {
                            KeyValue kv = list.get(i);
                            K key = keyList.get(i);
                            if (kv != null && kv.hasValue()) {
                                CacheValueHolder holder = (CacheValueHolder) valueDecoder.apply((byte[]) kv.getValue());
                                if (System.currentTimeMillis() >= holder.getExpireTime()) {
                                    resultMap.put(key, CacheGetResult.EXPIRED_WITHOUT_MSG);
                                } else {
                                    CacheGetResult r = new CacheGetResult(CacheResultCode.SUCCESS, null, holder);
                                    resultMap.put(key, r);
                                }
                            } else {
                                resultMap.put(key, CacheGetResult.NOT_EXISTS_WITHOUT_MSG);
                            }
                        }
                        return new ResultData(CacheResultCode.SUCCESS, null, resultMap);
                    } catch (Exception exception) {
                        logError("GET_ALL", "keys(" + keys.size() + ")", exception);
                        return new ResultData(exception);
                    }
                }
            }, JetCacheExecutor.defaultExecutor()));
            setTimeout(result);
            return result;
        } catch (Exception ex) {
            logError("GET_ALL", "keys(" + keys.size() + ")", ex);
            return new MultiGetResult(ex);
        }
    }

    @Override
    protected CacheResult do_REMOVE(K key) {
        try {
            RedisFuture future = keyAsyncCommands.del(buildKey(key));
            CacheResult result = new CacheResult(future.handle((rt, ex) -> {
                if (ex != null) {
                    JetCacheExecutor.defaultExecutor().execute(() -> logError("REMOVE", key, ex));
                    return new ResultData(ex);
                } else {
                    if (rt == null) {
                        return new ResultData(CacheResultCode.FAIL, null, null);
                    } else if (rt == 1) {
                        return new ResultData(CacheResultCode.SUCCESS, null, null);
                    } else if (rt == 0) {
                        return new ResultData(CacheResultCode.NOT_EXISTS, null, null);
                    } else {
                        return new ResultData(CacheResultCode.FAIL, null, null);
                    }
                }
            }));
            setTimeout(result);
            return result;
        } catch (Exception ex) {
            logError("REMOVE", key, ex);
            return new CacheResult(ex);
        }
    }

    @Override
    protected CacheResult do_REMOVE_ALL(Set keys) {
        try {
            byte[][] newKeys = keys.stream().map((k) -> buildKey(k)).toArray((len) -> new byte[keys.size()][]);
            RedisFuture future = keyAsyncCommands.del(newKeys);
            CacheResult result = new CacheResult(future.handle((v, ex) -> {
                if (ex != null) {
                    JetCacheExecutor.defaultExecutor().execute(() -> logError("REMOVE_ALL", "keys(" + keys.size() + ")", ex));
                    return new ResultData(ex);
                } else {
                    return new ResultData(CacheResultCode.SUCCESS, null, null);
                }
            }));
            setTimeout(result);
            return result;
        } catch (Exception ex) {
            logError("REMOVE_ALL", "keys(" + keys.size() + ")", ex);
            return new CacheResult(ex);
        }
    }

    @Override
    protected CacheResult do_PUT_IF_ABSENT(K key, V value, long expireAfterWrite, TimeUnit timeUnit) {
        try {
            CacheValueHolder holder = new CacheValueHolder(value, timeUnit.toMillis(expireAfterWrite));
            byte[] newKey = buildKey(key);
            RedisFuture future = stringAsyncCommands.set(newKey, valueEncoder.apply(holder), SetArgs.Builder.nx().px(timeUnit.toMillis(expireAfterWrite)));
            CacheResult result = new CacheResult(future.handle((rt, ex) -> {
                if (ex != null) {
                    JetCacheExecutor.defaultExecutor().execute(() -> logError("PUT_IF_ABSENT", key, ex));
                    return new ResultData(ex);
                } else {
                    if ("OK".equals(rt)) {
                        return new ResultData(CacheResultCode.SUCCESS, null, null);
                    } else if (rt == null) {
                        return new ResultData(CacheResultCode.EXISTS, null, null);
                    } else {
                        return new ResultData(CacheResultCode.FAIL, rt , null);
                    }
                }
            }));
            setTimeout(result);
            return result;
        } catch (Exception ex) {
            logError("PUT_IF_ABSENT", key, ex);
            return new CacheResult(ex);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy