cl
10 小时以前 13fbd2d9fdc7146cc5709b30b3f04e81c829f86f
src/main/java/com/zy/common/utils/RedisUtil.java
@@ -1,28 +1,43 @@
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封装
 *
 */
@Slf4j
@Component
public class RedisUtil {
    private static final long LOCAL_NO_EXPIRE = Long.MAX_VALUE;
    /** 无 TTL 写入 Redis 失败时,内存条目默认保留时长(秒) */
    private static final long LOCAL_DEFAULT_TTL_SEC = 86400L;
//    @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;
@@ -72,7 +87,7 @@
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
            return localHasValid(key);
        }
    }
@@ -84,10 +99,19 @@
    @SuppressWarnings("unchecked")
    public void del(String... key) {
        if (key != null && key.length > 0) {
            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);
                }
            }
        }
    }
@@ -101,7 +125,18 @@
     * @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);
        }
    }
    /**
@@ -140,10 +175,13 @@
                    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;
        }
    }
@@ -157,10 +195,13 @@
    public boolean setAsync(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            localCache.remove(key);
            notifyRedisAvailable();
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
            log.debug("Redis setAsync 失败", e);
            localPutFallback(key, value, LOCAL_DEFAULT_TTL_SEC);
            return true;
        }
    }
@@ -178,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;
        }
    }
@@ -630,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);
        }
    }
    /**
@@ -643,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;
        }
    }
}