cl
9 小时以前 13fbd2d9fdc7146cc5709b30b3f04e81c829f86f
src/main/java/com/zy/common/utils/RedisUtil.java
@@ -1,25 +1,49 @@
package com.zy.common.utils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
/**
 * redisTemplate封装
 *
 */
//@Component
@Slf4j
@Component
public class RedisUtil {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    private static final long LOCAL_NO_EXPIRE = Long.MAX_VALUE;
    /** 无 TTL 写入 Redis 失败时,内存条目默认保留时长(秒) */
    private static final long LOCAL_DEFAULT_TTL_SEC = 86400L;
    public RedisUtil(RedisTemplate<String, Object> redisTemplate) {
//    @Autowired
//    private RedisTemplate<String, Object> redisTemplate;
    @Autowired
    private RedisTemplate redisTemplate;
    private final ConcurrentHashMap<String, LocalCacheEntry> localCache = new ConcurrentHashMap<>();
    private final Object fallbackLock = new Object();
    private volatile boolean redisFallbackMode = false;
    private final Set<String> fallbackLocalKeys = ConcurrentHashMap.newKeySet();
    public RedisTemplate getRedisTemplate() {
        return redisTemplate;
    }
    public RedisUtil(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
@@ -63,7 +87,7 @@
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
            return localHasValid(key);
        }
    }
@@ -75,10 +99,19 @@
    @SuppressWarnings("unchecked")
    public void del(String... key) {
        if (key != null && key.length > 0) {
            if (key.length == 1) {
                redisTemplate.delete(key[0]);
            } else {
                redisTemplate.delete(CollectionUtils.arrayToList(key));
            try {
                if (key.length == 1) {
                    redisTemplate.delete(key[0]);
                } else {
                    redisTemplate.delete(CollectionUtils.arrayToList(key));
                }
            } catch (Exception e) {
                // Redis 不可用时仍清理本地条目
            }
            for (String k : key) {
                if (k != null) {
                    localCache.remove(k);
                }
            }
        }
    }
@@ -92,7 +125,33 @@
     * @return 值
     */
    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
        if (key == null) {
            return null;
        }
        try {
            Object v = redisTemplate.opsForValue().get(key);
            notifyRedisAvailable();
            return v;
        } catch (Exception e) {
            enterFallbackIfNeeded();
            log.info("Redis 不可用,从 JVM 内存读取 key={},原因: {}", key, e.getMessage());
            return localGet(key);
        }
    }
    /**
     * 获取全部数据
     * @return
     */
    public HashMap<Object, Object> getRedis() {
        Set<String> keys = redisTemplate.keys("*");
        HashMap<Object, Object> map = new HashMap<>();
        for (String key : keys) {
            Object value = redisTemplate.opsForValue().get(key);
            map.put(key, value);
        }
        return map;//返回全部数据集合
    }
    /**
@@ -105,10 +164,44 @@
    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            long start = System.currentTimeMillis();
            while (System.currentTimeMillis() - start < 10000) {//有效期10s
                Object o = redisTemplate.opsForValue().get(key);
                if (o == null) {
                    continue;
                }
                if (o.equals(value)) {
                    break;
                }
            }
            localCache.remove(key);
            notifyRedisAvailable();
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
            log.debug("Redis set 失败", e);
            localPutFallback(key, value, LOCAL_DEFAULT_TTL_SEC);
            return true;
        }
    }
    /**
     * 普通缓存放入-异步
     *
     * @param key   键
     * @param value 值
     * @return true成功 false失败
     */
    public boolean setAsync(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            localCache.remove(key);
            notifyRedisAvailable();
            return true;
        } catch (Exception e) {
            log.debug("Redis setAsync 失败", e);
            localPutFallback(key, value, LOCAL_DEFAULT_TTL_SEC);
            return true;
        }
    }
@@ -126,11 +219,16 @@
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                set(key, value);
                return true;
            }
            localCache.remove(key);
            notifyRedisAvailable();
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
            log.debug("Redis set(expire) 失败", e);
            long ttl = time > 0 ? time : LOCAL_DEFAULT_TTL_SEC;
            localPutFallback(key, value, ttl);
            return true;
        }
    }
@@ -578,7 +676,15 @@
     * @return
     */
    public Set keys(String pattern) {
        return redisTemplate.keys(pattern);
        try {
            Set<?> s = redisTemplate.keys(pattern);
            notifyRedisAvailable();
            return s;
        } catch (Exception e) {
            enterFallbackIfNeeded();
            log.info("Redis 不可用,从 JVM 内存匹配 keys,pattern={},原因: {}", pattern, e.getMessage());
            return localKeysMatching(pattern);
        }
    }
    /**
@@ -591,6 +697,122 @@
        redisTemplate.convertAndSend(channel, message);
    }
    private void enterFallbackIfNeeded() {
        synchronized (fallbackLock) {
            if (!redisFallbackMode) {
                redisFallbackMode = true;
                log.warn("Redis 不可用,字符串相关缓存将暂存于 JVM 内存,待 Redis 恢复后自动切回 Redis 并清理上述键");
            }
        }
    }
    private void notifyRedisAvailable() {
        if (!redisFallbackMode && fallbackLocalKeys.isEmpty()) {
            return;
        }
        synchronized (fallbackLock) {
            if (!redisFallbackMode && fallbackLocalKeys.isEmpty()) {
                return;
            }
            int n = fallbackLocalKeys.size();
            for (String k : new ArrayList<>(fallbackLocalKeys)) {
                localCache.remove(k);
            }
            fallbackLocalKeys.clear();
            redisFallbackMode = false;
            if (n > 0) {
                log.info("Redis 已恢复,已清理 JVM 内存中 {} 个因 Redis 不可用而暂存的键", n);
            } else {
                log.info("Redis 已恢复");
            }
        }
    }
    private void localPutFallback(String key, Object value, long ttlSeconds) {
        if (key == null) {
            return;
        }
        synchronized (fallbackLock) {
            if (!redisFallbackMode) {
                redisFallbackMode = true;
                log.warn("Redis 不可用,字符串相关缓存将暂存于 JVM 内存,待 Redis 恢复后自动切回 Redis 并清理上述键");
            }
            fallbackLocalKeys.add(key);
            localPut(key, value, ttlSeconds);
        }
        log.info("Redis 不可用,键已写入 JVM 内存: {}", key);
    }
    private void localPut(String key, Object value, long ttlSeconds) {
        if (key == null) {
            return;
        }
        long expireAt = ttlSeconds > 0
                ? System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(ttlSeconds)
                : LOCAL_NO_EXPIRE;
        localCache.put(key, new LocalCacheEntry(value, expireAt));
    }
    private Object localGet(String key) {
        LocalCacheEntry e = localCache.get(key);
        if (e == null) {
            return null;
        }
        if (e.expireAtMillis != LOCAL_NO_EXPIRE && System.currentTimeMillis() > e.expireAtMillis) {
            localCache.remove(key, e);
            return null;
        }
        return e.value;
    }
    private boolean localHasValid(String key) {
        return localGet(key) != null;
    }
    private Set<String> localKeysMatching(String pattern) {
        if (pattern == null) {
            return new HashSet<>();
        }
        Pattern p = Pattern.compile(globToRegex(pattern));
        Set<String> out = new HashSet<>();
        for (String k : localCache.keySet()) {
            if (localGet(k) == null) {
                continue;
            }
            if (p.matcher(k).matches()) {
                out.add(k);
            }
        }
        return out;
    }
    private static String globToRegex(String glob) {
        StringBuilder sb = new StringBuilder("^");
        for (int i = 0; i < glob.length(); i++) {
            char c = glob.charAt(i);
            if (c == '*') {
                sb.append(".*");
            } else if (c == '?') {
                sb.append('.');
            } else if ("\\.[]{}()+-^$|".indexOf(c) >= 0) {
                sb.append('\\').append(c);
            } else {
                sb.append(c);
            }
        }
        sb.append('$');
        return sb.toString();
    }
    private static final class LocalCacheEntry {
        final Object value;
        final long expireAtMillis;
        LocalCacheEntry(Object value, long expireAtMillis) {
            this.value = value;
            this.expireAtMillis = expireAtMillis;
        }
    }
}