|
@@ -1,687 +0,0 @@
|
|
|
-package com.chelvc.framework.redis.cache;
|
|
|
-
|
|
|
-import java.util.Collection;
|
|
|
-import java.util.Collections;
|
|
|
-import java.util.List;
|
|
|
-import java.util.Objects;
|
|
|
-import java.util.Set;
|
|
|
-import java.util.concurrent.TimeUnit;
|
|
|
-import java.util.function.BiFunction;
|
|
|
-import java.util.function.Supplier;
|
|
|
-import java.util.stream.Collectors;
|
|
|
-
|
|
|
-import com.chelvc.framework.base.cache.Caching;
|
|
|
-import com.chelvc.framework.base.cache.Converter;
|
|
|
-import com.chelvc.framework.base.cache.Naming;
|
|
|
-import com.chelvc.framework.common.util.AssertUtils;
|
|
|
-import com.chelvc.framework.common.util.DateUtils;
|
|
|
-import com.chelvc.framework.common.util.ObjectUtils;
|
|
|
-import com.google.common.collect.Lists;
|
|
|
-import lombok.NonNull;
|
|
|
-import lombok.extern.slf4j.Slf4j;
|
|
|
-import org.apache.commons.lang3.tuple.Pair;
|
|
|
-import org.springframework.data.redis.core.RedisTemplate;
|
|
|
-import org.springframework.data.redis.core.script.DefaultRedisScript;
|
|
|
-import org.springframework.data.redis.core.script.RedisScript;
|
|
|
-
|
|
|
-/**
|
|
|
- * 基于Redis ZSet的缓存实现
|
|
|
- *
|
|
|
- * @author Woody
|
|
|
- * @date 2024/1/30
|
|
|
- */
|
|
|
-@Slf4j
|
|
|
-public class RedisZSetCaching implements Caching {
|
|
|
- /**
|
|
|
- * 缓存获取并延长过期时间脚本
|
|
|
- */
|
|
|
- private static final RedisScript<Object> GET_DEFER_SCRIPT = new DefaultRedisScript<>(
|
|
|
- "local value = redis.call('GET', KEYS[1]) " +
|
|
|
- "if value ~= false then local ttl = redis.call('TTL', KEYS[1]) " +
|
|
|
- "if ttl > 0 and ttl < 60 then redis.call('EXPIRE', KEYS[1], ARGV[1]) end end " +
|
|
|
- "return value", Object.class
|
|
|
- );
|
|
|
-
|
|
|
- /**
|
|
|
- * 缓存设置脚本
|
|
|
- */
|
|
|
- private static final RedisScript<Boolean> SET_SCRIPT = new DefaultRedisScript<>(
|
|
|
- "redis.call('SET', KEYS[1]..':'..ARGV[1], ARGV[2]) " +
|
|
|
- "return redis.call('ZADD', KEYS[2], ARGV[3], ARGV[1])", Boolean.class
|
|
|
- );
|
|
|
-
|
|
|
- /**
|
|
|
- * 缓存设置并设置过期时间脚本
|
|
|
- */
|
|
|
- private static final RedisScript<Boolean> SET_EXPIRE_SCRIPT = new DefaultRedisScript<>(
|
|
|
- "redis.call('SET', KEYS[1]..':'..ARGV[1], ARGV[2], 'EX', ARGV[4]) " +
|
|
|
- "local result = redis.call('ZADD', KEYS[2], ARGV[3], ARGV[1]) " +
|
|
|
- "redis.call('EXPIRE', KEYS[2], ARGV[4]) return result", Boolean.class
|
|
|
- );
|
|
|
-
|
|
|
- /**
|
|
|
- * 缓存批量设置脚本
|
|
|
- */
|
|
|
- private static final RedisScript<Long> BATCH_SET_SCRIPT = new DefaultRedisScript<>(
|
|
|
- "local number = 0 for i = 1, #ARGV, 3 do " +
|
|
|
- "redis.call('SET', KEYS[1]..':'..ARGV[i], ARGV[i + 1]) " +
|
|
|
- "redis.call('ZADD', KEYS[2], ARGV[i + 2], ARGV[i]) " +
|
|
|
- "number = number + 1 end return number", Long.class
|
|
|
- );
|
|
|
-
|
|
|
- /**
|
|
|
- * 缓存批量设置并设置过期时间脚本
|
|
|
- */
|
|
|
- private static final RedisScript<Long> BATCH_SET_EXPIRE_SCRIPT = new DefaultRedisScript<>(
|
|
|
- "local number = 0 for i = 2, #ARGV, 3 do " +
|
|
|
- "redis.call('SET', KEYS[1]..':'..ARGV[i], ARGV[i + 1], 'EX', ARGV[1]) " +
|
|
|
- "redis.call('ZADD', KEYS[2], ARGV[i + 2], ARGV[i]) number = number + 1 end " +
|
|
|
- "redis.call('EXPIRE', KEYS[2], ARGV[1]) return number", Long.class
|
|
|
- );
|
|
|
-
|
|
|
- /**
|
|
|
- * 缓存更新脚本
|
|
|
- */
|
|
|
- private static final RedisScript<?> UPDATE_SCRIPT = new DefaultRedisScript<>(
|
|
|
- "local function update(key) local cursor = 0 " +
|
|
|
- "repeat local result = redis.call('SCAN', cursor, 'MATCH', key, 'COUNT', 1000) " +
|
|
|
- "if (result ~= nil and #result > 0) then cursor = tonumber(result[1]) local matches = result[2] " +
|
|
|
- "for i = 1, #matches do redis.call('ZADD', matches[i], ARGV[3], ARGV[1]) end " +
|
|
|
- "end until (cursor <= 0) end local unique = KEYS[1]..':'..ARGV[1] " +
|
|
|
- "redis.call('SET', unique, ARGV[2]) for i = 2, #KEYS do update(KEYS[i]) end"
|
|
|
- );
|
|
|
-
|
|
|
- /**
|
|
|
- * 缓存更新并设置过期时间脚本
|
|
|
- */
|
|
|
- private static final RedisScript<?> UPDATE_EXPIRE_SCRIPT = new DefaultRedisScript<>(
|
|
|
- "local function update(key) local cursor = 0 " +
|
|
|
- "repeat local result = redis.call('SCAN', cursor, 'MATCH', key, 'COUNT', 1000) " +
|
|
|
- "if (result ~= nil and #result > 0) then cursor = tonumber(result[1]) local matches = result[2] " +
|
|
|
- "for i = 1, #matches do redis.call('ZADD', matches[i], ARGV[3], ARGV[1]) end " +
|
|
|
- "end until (cursor <= 0) end redis.call('SET', KEYS[1]..':'..ARGV[1], ARGV[2], 'EX', ARGV[4]) " +
|
|
|
- "for i = 2, #KEYS do update(KEYS[i]) end"
|
|
|
- );
|
|
|
-
|
|
|
- /**
|
|
|
- * 缓存批量更新脚本
|
|
|
- */
|
|
|
- private static final RedisScript<?> BATCH_UPDATE_SCRIPT = new DefaultRedisScript<>(
|
|
|
- "local function update(key, id, order) local cursor = 0 " +
|
|
|
- "repeat local result = redis.call('SCAN', cursor, 'MATCH', key, 'COUNT', 1000) " +
|
|
|
- "if (result ~= nil and #result > 0) then cursor = tonumber(result[1]) local matches = result[2] " +
|
|
|
- "for i = 1, #matches do redis.call('ZADD', matches[i], order, id) end " +
|
|
|
- "end until (cursor <= 0) end local n = 0 for i = 1, #ARGV, 5 do " +
|
|
|
- "for j = n + 2, n + ARGV[i + 4] do update(KEYS[j], ARGV[i], ARGV[i + 2]) end " +
|
|
|
- "local unique = KEYS[n + 1]..':'..ARGV[i] " +
|
|
|
- "if tonumber(ARGV[i + 3]) < 1 then redis.call('SET', unique, ARGV[i + 1]) else " +
|
|
|
- "redis.call('SET', unique, ARGV[i + 1], 'EX', ARGV[i + 3]) n = n + ARGV[i + 4] end end"
|
|
|
- );
|
|
|
-
|
|
|
- /**
|
|
|
- * 缓存升序批量获取脚本
|
|
|
- */
|
|
|
- @SuppressWarnings("rawtypes")
|
|
|
- private static final RedisScript<List> ASC_LIST_SCRIPT = new DefaultRedisScript<>(
|
|
|
- "if redis.call('EXISTS', KEYS[2]) == 0 then return nil end " +
|
|
|
- "local ids = redis.call('ZRANGEBYSCORE', KEYS[2], '('..ARGV[1], ARGV[2], 'LIMIT', 0, ARGV[3]) " +
|
|
|
- "local values = {} for i = 1, #ids do local value = redis.call('GET', KEYS[1]..':'..ids[i]) " +
|
|
|
- "if value ~= false then table.insert(values, value) end end return values", List.class
|
|
|
- );
|
|
|
-
|
|
|
- /**
|
|
|
- * 缓存升序批量获取并设置过期时间脚本
|
|
|
- */
|
|
|
- @SuppressWarnings("rawtypes")
|
|
|
- private static final RedisScript<List> ASC_LIST_DEFER_SCRIPT = new DefaultRedisScript<>(
|
|
|
- "if redis.call('EXISTS', KEYS[2]) == 0 then return nil end " +
|
|
|
- "local ids = redis.call('ZRANGEBYSCORE', KEYS[2], '('..ARGV[1], ARGV[2], 'LIMIT', 0, ARGV[3]) " +
|
|
|
- "local values = {} for i = 1, #ids do local unique = KEYS[1]..':'..ids[i] " +
|
|
|
- "local value = redis.call('GET', unique) if value ~= false then " +
|
|
|
- "local ttl = redis.call('TTL', unique) if ttl > 0 and ttl < 60 then " +
|
|
|
- "redis.call('EXPIRE', unique, ARGV[4]) end table.insert(values, value) end end " +
|
|
|
- "local ttl = redis.call('TTL', KEYS[2]) if ttl > 0 and ttl < 60 then " +
|
|
|
- "redis.call('EXPIRE', KEYS[2], ARGV[4]) end return values", List.class
|
|
|
- );
|
|
|
-
|
|
|
- /**
|
|
|
- * 缓存降序批量获取脚本
|
|
|
- */
|
|
|
- @SuppressWarnings("rawtypes")
|
|
|
- private static final RedisScript<List> DESC_LIST_SCRIPT = new DefaultRedisScript<>(
|
|
|
- "if redis.call('EXISTS', KEYS[2]) == 0 then return nil end " +
|
|
|
- "local ids = redis.call('ZREVRANGEBYSCORE', KEYS[2], '('..ARGV[1], ARGV[2], 'LIMIT', 0, ARGV[3]) " +
|
|
|
- "local values = {} for i = 1, #ids do local value = redis.call('GET', KEYS[1]..':'..ids[i]) " +
|
|
|
- "if value ~= false then table.insert(values, value) end end return values", List.class
|
|
|
- );
|
|
|
-
|
|
|
- /**
|
|
|
- * 缓存降序批量获取并设置过期时间脚本
|
|
|
- */
|
|
|
- @SuppressWarnings("rawtypes")
|
|
|
- private static final RedisScript<List> DESC_LIST_DEFER_SCRIPT = new DefaultRedisScript<>(
|
|
|
- "if redis.call('EXISTS', KEYS[2]) == 0 then return nil end " +
|
|
|
- "local ids = redis.call('ZREVRANGEBYSCORE', KEYS[2], '('..ARGV[1], ARGV[2], 'LIMIT', 0, ARGV[3]) " +
|
|
|
- "local values = {} for i = 1, #ids do local unique = KEYS[1]..':'..ids[i] " +
|
|
|
- "local value = redis.call('GET', unique) " +
|
|
|
- "if value ~= false then local ttl = redis.call('TTL', unique) if ttl > 0 and ttl < 60 then " +
|
|
|
- "redis.call('EXPIRE', unique, ARGV[4]) end table.insert(values, value) end end " +
|
|
|
- "local ttl = redis.call('TTL', KEYS[2]) if ttl > 0 and ttl < 60 then " +
|
|
|
- "redis.call('EXPIRE', KEYS[2], ARGV[4]) end return values", List.class
|
|
|
- );
|
|
|
-
|
|
|
- /**
|
|
|
- * 缓存随机全部获取脚本
|
|
|
- */
|
|
|
- @SuppressWarnings("rawtypes")
|
|
|
- private static final RedisScript<List> RANDOM_ALL_SCRIPT = new DefaultRedisScript<>(
|
|
|
- "if redis.call('EXISTS', KEYS[2]) == 0 then return nil end " +
|
|
|
- "local randoms = {} local function random(bound) " +
|
|
|
- "local index = math.random(0, bound) " +
|
|
|
- "if randoms[index] == nil then randoms[index] = 0 return index " +
|
|
|
- "else return random(bound) end end " +
|
|
|
- "local values = {} local total = redis.call('ZCOUNT', KEYS[2], ARGV[1], ARGV[2]) " +
|
|
|
- "if total == 0 then return values end math.randomseed(ARGV[3]) " +
|
|
|
- "for i = 1, total do local index = random(total - 1) " +
|
|
|
- "local ids = redis.call('ZRANGE', KEYS[2], index, index) " +
|
|
|
- "local value = redis.call('GET', KEYS[1]..':'..ids[1]) " +
|
|
|
- "if value ~= false then table.insert(values, value) end end return values", List.class
|
|
|
- );
|
|
|
-
|
|
|
- /**
|
|
|
- * 缓存随机批量获取脚本
|
|
|
- */
|
|
|
- @SuppressWarnings("rawtypes")
|
|
|
- private static final RedisScript<List> RANDOM_LIST_SCRIPT = new DefaultRedisScript<>(
|
|
|
- "if redis.call('EXISTS', KEYS[2]) == 0 then return nil end " +
|
|
|
- "local randoms = {} local function random(bound) " +
|
|
|
- "local index = math.random(0, bound) " +
|
|
|
- "if randoms[index] == nil then randoms[index] = 0 return index " +
|
|
|
- "else return random(bound) end end " +
|
|
|
- "local values = {} local total = redis.call('ZCOUNT', KEYS[2], ARGV[1], ARGV[2]) " +
|
|
|
- "if total == 0 then return values end " +
|
|
|
- "local offset = redis.call('ZCOUNT', KEYS[2], '-INF', '('..ARGV[1]) " +
|
|
|
- "local size = tonumber(ARGV[4]) " +
|
|
|
- "if size < 0 then size = total elseif size > total then size = total end " +
|
|
|
- "math.randomseed(ARGV[3]) " +
|
|
|
- "for i = 1, size do local index = random(total - 1) + offset " +
|
|
|
- "local ids = redis.call('ZRANGE', KEYS[2], index, index) " +
|
|
|
- "local value = redis.call('GET', KEYS[1]..':'..ids[1]) " +
|
|
|
- "if value ~= false then table.insert(values, value) end end return values", List.class
|
|
|
- );
|
|
|
-
|
|
|
- /**
|
|
|
- * 缓存随机全部获取并设置过期时间脚本
|
|
|
- */
|
|
|
- @SuppressWarnings("rawtypes")
|
|
|
- private static final RedisScript<List> RANDOM_ALL_DEFER_SCRIPT = new DefaultRedisScript<>(
|
|
|
- "if redis.call('EXISTS', KEYS[2]) == 0 then return nil end " +
|
|
|
- "local randoms = {} local function random(bound) " +
|
|
|
- "local index = math.random(0, bound) " +
|
|
|
- "if randoms[index] == nil then randoms[index] = 0 return index " +
|
|
|
- "else return random(bound) end end " +
|
|
|
- "local values = {} local total = redis.call('ZCOUNT', KEYS[2], ARGV[1], ARGV[2]) " +
|
|
|
- "if total == 0 then return values end math.randomseed(ARGV[3]) " +
|
|
|
- "for i = 1, total do local index = random(total - 1) " +
|
|
|
- "local ids = redis.call('ZRANGE', KEYS[2], index, index) " +
|
|
|
- "local unique = KEYS[1]..':'..ids[1] local value = redis.call('GET', unique) " +
|
|
|
- "if value ~= false then local ttl = redis.call('TTL', unique) if ttl > 0 and ttl < 60 then " +
|
|
|
- "redis.call('EXPIRE', unique, ARGV[4]) end table.insert(values, value) end " +
|
|
|
- "end local ttl = redis.call('TTL', KEYS[2]) if ttl > 0 and ttl < 60 then " +
|
|
|
- "redis.call('EXPIRE', KEYS[2], ARGV[4]) end return values", List.class
|
|
|
- );
|
|
|
-
|
|
|
- /**
|
|
|
- * 缓存随机批量获取并设置过期时间脚本
|
|
|
- */
|
|
|
- @SuppressWarnings("rawtypes")
|
|
|
- private static final RedisScript<List> RANDOM_LIST_DEFER_SCRIPT = new DefaultRedisScript<>(
|
|
|
- "if redis.call('EXISTS', KEYS[2]) == 0 then return nil end " +
|
|
|
- "local randoms = {} local function random(bound) " +
|
|
|
- "local index = math.random(0, bound) " +
|
|
|
- "if randoms[index] == nil then randoms[index] = 0 return index " +
|
|
|
- "else return random(bound) end end " +
|
|
|
- "local values = {} local total = redis.call('ZCOUNT', KEYS[2], ARGV[1], ARGV[2]) " +
|
|
|
- "if total == 0 then return values end " +
|
|
|
- "local offset = redis.call('ZCOUNT', KEYS[2], '-INF', '('..ARGV[1]) " +
|
|
|
- "local size = tonumber(ARGV[4]) " +
|
|
|
- "if size < 0 then size = total elseif size > total then size = total end " +
|
|
|
- "math.randomseed(ARGV[3]) " +
|
|
|
- "for i = 1, size do local index = random(total - 1) + offset " +
|
|
|
- "local ids = redis.call('ZRANGE', KEYS[2], index, index) " +
|
|
|
- "local unique = KEYS[1]..':'..ids[1] local value = redis.call('GET', unique) " +
|
|
|
- "if value ~= false then local ttl = redis.call('TTL', unique) if ttl > 0 and ttl < 60 then " +
|
|
|
- "redis.call('EXPIRE', unique, ARGV[5]) end table.insert(values, value) end " +
|
|
|
- "end local ttl = redis.call('TTL', KEYS[2]) if ttl > 0 and ttl < 60 then " +
|
|
|
- "redis.call('EXPIRE', KEYS[2], ARGV[5]) end return values", List.class
|
|
|
- );
|
|
|
-
|
|
|
- /**
|
|
|
- * 缓存清理脚本
|
|
|
- */
|
|
|
- private static final RedisScript<Boolean> CLEAR_SCRIPT = new DefaultRedisScript<>(
|
|
|
- "local count = 0 local function clear(key) local cursor = 0 " +
|
|
|
- "repeat local result = redis.call('SCAN', cursor, 'MATCH', key, 'COUNT', 1000) " +
|
|
|
- "if (result ~= nil and #result > 0) then " +
|
|
|
- "cursor = tonumber(result[1]) local matches = result[2] " +
|
|
|
- "for i = 1, #matches do count = count + redis.call('ZREM', matches[i], ARGV[1]) end " +
|
|
|
- "end until (cursor <= 0) end for i = 2, #KEYS do clear(KEYS[i]) end " +
|
|
|
- "count = count + redis.call('DEL', KEYS[1]..':'..ARGV[1]) " +
|
|
|
- "if count > 0 then return 1 else return 0 end", Boolean.class
|
|
|
- );
|
|
|
-
|
|
|
- /**
|
|
|
- * 缓存批量清理脚本
|
|
|
- */
|
|
|
- private static final RedisScript<Boolean> BATCH_CLEAR_SCRIPT = new DefaultRedisScript<>(
|
|
|
- "local count = 0 local function clear(key, value) local cursor = 0 " +
|
|
|
- "repeat local result = redis.call('SCAN', cursor, 'MATCH', key, 'COUNT', 1000) " +
|
|
|
- "if (result ~= nil and #result > 0) then " +
|
|
|
- "cursor = tonumber(result[1]) local matches = result[2] " +
|
|
|
- "for i = 1, #matches do count = count + redis.call('ZREM', matches[i], value) end " +
|
|
|
- "end until (cursor <= 0) end local n = 0 for i = 1, #ARGV, 2 do " +
|
|
|
- "for j = n + 2, n + ARGV[i + 1] do clear(KEYS[j], ARGV[i]) end " +
|
|
|
- "count = count + redis.call('DEL', KEYS[n + 1]..':'..ARGV[i]) n = n + ARGV[i + 1] end " +
|
|
|
- "if count > 0 then return 1 else return 0 end", Boolean.class
|
|
|
- );
|
|
|
-
|
|
|
- /**
|
|
|
- * 缓存移除脚本
|
|
|
- */
|
|
|
- private static final RedisScript<Boolean> REMOVE_SCRIPT = new DefaultRedisScript<>(
|
|
|
- "local count = redis.call('ZREM', KEYS[2], ARGV[1]) + redis.call('DEL', KEYS[1]..':'..ARGV[1]) " +
|
|
|
- "if count > 0 then return 1 else return 0 end", Boolean.class
|
|
|
- );
|
|
|
-
|
|
|
- /**
|
|
|
- * 缓存移批量除脚本
|
|
|
- */
|
|
|
- private static final RedisScript<Boolean> BATCH_REMOVE_SCRIPT = new DefaultRedisScript<>(
|
|
|
- "local count = 0 for i = 1, #ARGV do count = count + redis.call('ZREM', KEYS[i * 2], ARGV[i]) " +
|
|
|
- "count = count + redis.call('DEL', KEYS[i * 2 - 1]..':'..ARGV[i]) end " +
|
|
|
- "if count > 0 then return 1 else return 0 end", Boolean.class
|
|
|
- );
|
|
|
-
|
|
|
- /**
|
|
|
- * 缓存全部移除脚本
|
|
|
- */
|
|
|
- private static final RedisScript<Boolean> REMOVE_ALL_SCRIPT = new DefaultRedisScript<>(
|
|
|
- "local count = 0 for i = 1, #KEYS do local cursor = 0 local pattern = KEYS[i]..':'..'*' " +
|
|
|
- "repeat local result = redis.call('SCAN', cursor, 'MATCH', pattern, 'COUNT', 1000) " +
|
|
|
- "if (result ~= nil and #result > 0) then cursor = tonumber(result[1]) local matches = result[2] " +
|
|
|
- "for i = 1, #matches do count = count + redis.call('DEL', matches[i]) end end " +
|
|
|
- "until (cursor <= 0) end if count > 0 then return 1 else return 0 end", Boolean.class
|
|
|
- );
|
|
|
-
|
|
|
- private final RedisTemplate<String, Object> template;
|
|
|
-
|
|
|
- public RedisZSetCaching(@NonNull RedisTemplate<String, Object> template) {
|
|
|
- this.template = template;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 批量获取缓存内容,如果对应ZSet Key不存在则返回null
|
|
|
- *
|
|
|
- * @param naming 缓存命名
|
|
|
- * @param min 最小排序数字
|
|
|
- * @param max 最大排序数字
|
|
|
- * @param size 获取数量
|
|
|
- * @param <V> 缓存内容类型
|
|
|
- * @return 缓存对象实例列表
|
|
|
- */
|
|
|
- @SuppressWarnings("unchecked")
|
|
|
- private <V> List<V> listing(@NonNull Naming naming, long min, long max, int size) {
|
|
|
- AssertUtils.check(max >= min, () -> "max must not be less than min");
|
|
|
- AssertUtils.check(size > 0, () -> "size must be greater than 0");
|
|
|
- List<String> keys = naming.getKeys();
|
|
|
- List<V> values = null;
|
|
|
- try {
|
|
|
- if (naming.isReversed()) {
|
|
|
- if (naming.getDuration() > 0) {
|
|
|
- values = this.template.execute(DESC_LIST_DEFER_SCRIPT, keys, max, min, size, naming.getDuration());
|
|
|
- } else {
|
|
|
- values = this.template.execute(DESC_LIST_SCRIPT, keys, max, min, size);
|
|
|
- }
|
|
|
- } else {
|
|
|
- if (naming.getDuration() > 0) {
|
|
|
- values = this.template.execute(ASC_LIST_DEFER_SCRIPT, keys, min, max, size, naming.getDuration());
|
|
|
- } else {
|
|
|
- values = this.template.execute(ASC_LIST_SCRIPT, keys, min, max, size);
|
|
|
- }
|
|
|
- }
|
|
|
- } catch (Throwable t) {
|
|
|
- log.warn("Redis cache list failed: {}, {}", naming, t.getMessage());
|
|
|
- }
|
|
|
- return values != null && values.size() == 1 && values.get(0) == null ? null : values;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 批量随机获取缓存内容,如果对应ZSet Key不存在则返回null
|
|
|
- *
|
|
|
- * @param naming 缓存命名
|
|
|
- * @param min 最小排序数字
|
|
|
- * @param max 最大排序数字
|
|
|
- * @param size 获取数量(当size < 0 时获取全部内容)
|
|
|
- * @param <V> 缓存内容类型
|
|
|
- * @return 缓存对象实例列表
|
|
|
- */
|
|
|
- @SuppressWarnings("unchecked")
|
|
|
- private <V> List<V> randomizing(@NonNull Naming naming, long min, long max, int size) {
|
|
|
- AssertUtils.check(max >= min, () -> "max must not be less than min");
|
|
|
- AssertUtils.check(size != 0, () -> "size must be less than or greater than 0");
|
|
|
- List<String> keys = naming.getKeys();
|
|
|
- long seed = Long.parseLong(new StringBuilder(String.valueOf(DateUtils.seconds())).reverse().substring(0, 7));
|
|
|
- List<V> values = null;
|
|
|
- try {
|
|
|
- if (size < 0) {
|
|
|
- if (naming.getDuration() > 0) {
|
|
|
- values = this.template.execute(RANDOM_ALL_DEFER_SCRIPT, keys, min, max, seed, naming.getDuration());
|
|
|
- } else {
|
|
|
- values = this.template.execute(RANDOM_ALL_SCRIPT, keys, min, max, seed);
|
|
|
- }
|
|
|
- } else {
|
|
|
- if (naming.getDuration() > 0) {
|
|
|
- values = this.template.execute(RANDOM_LIST_DEFER_SCRIPT, keys, min, max, seed, size,
|
|
|
- naming.getDuration());
|
|
|
- } else {
|
|
|
- values = this.template.execute(RANDOM_LIST_SCRIPT, keys, min, max, seed, size);
|
|
|
- }
|
|
|
- }
|
|
|
- } catch (Throwable t) {
|
|
|
- log.warn("Redis cache random list failed: {}, {}", naming, t.getMessage());
|
|
|
- }
|
|
|
- return values != null && values.size() == 1 && values.get(0) == null ? null : values;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- @SuppressWarnings("unchecked")
|
|
|
- public <K, V> V get(@NonNull Naming naming, @NonNull K id) {
|
|
|
- String key = naming.getPrimary() + ":" + id;
|
|
|
- V value = null;
|
|
|
- try {
|
|
|
- if (naming.getDuration() > 0) {
|
|
|
- List<String> keys = Collections.singletonList(key);
|
|
|
- value = (V) this.template.execute(GET_DEFER_SCRIPT, keys, naming.getDuration());
|
|
|
- } else {
|
|
|
- value = (V) this.template.opsForValue().get(key);
|
|
|
- }
|
|
|
- } catch (Throwable t) {
|
|
|
- log.warn("Redis cache get failed: {}, {}", key, t.getMessage());
|
|
|
- }
|
|
|
- return value;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public <K, V> V get(@NonNull Naming naming, @NonNull K id, @NonNull Supplier<V> supplier) {
|
|
|
- V value = this.get(naming, id);
|
|
|
- if (value == null && (value = supplier.get()) != null) {
|
|
|
- this.set(naming, id, value);
|
|
|
- }
|
|
|
- return value;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public <V> List<V> list(@NonNull Naming naming, long min, long max, int size) {
|
|
|
- List<V> values = this.listing(naming, min, max, size);
|
|
|
- return ObjectUtils.ifEmpty(values, Collections::emptyList);
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public <K, V> List<V> list(@NonNull Naming naming, long min, long max, int size,
|
|
|
- @NonNull BiFunction<Naming, Boolean, List<V>> supplier,
|
|
|
- @NonNull Converter<K, V> converter) {
|
|
|
- // 从缓存加载数据
|
|
|
- List<V> values = this.listing(naming, min, max, size);
|
|
|
- boolean initial = Objects.isNull(values);
|
|
|
- if (!initial && values.size() >= size) {
|
|
|
- return values;
|
|
|
- }
|
|
|
-
|
|
|
- // 重新加载并更新缓存数据
|
|
|
- List<V> suppliers = supplier.apply(naming, initial);
|
|
|
- if (ObjectUtils.isEmpty(suppliers)) {
|
|
|
- return ObjectUtils.ifEmpty(values, Collections::emptyList);
|
|
|
- } else if (ObjectUtils.isEmpty(values)) {
|
|
|
- this.set(naming, suppliers, converter);
|
|
|
- } else {
|
|
|
- Set<K> exists = values.stream().map(converter::id).collect(Collectors.toSet());
|
|
|
- List<V> news = suppliers.stream().filter(value -> !exists.contains(converter.id(value)))
|
|
|
- .collect(Collectors.toList());
|
|
|
- if (ObjectUtils.notEmpty(news)) {
|
|
|
- this.set(naming, news, converter);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 返回最多 size 个数据
|
|
|
- return suppliers.size() <= size ? suppliers : suppliers.subList(0, size);
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public <V> List<V> random(@NonNull Naming naming, long min, long max, int size) {
|
|
|
- List<V> values = this.randomizing(naming, min, max, size);
|
|
|
- return ObjectUtils.ifEmpty(values, Collections::emptyList);
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public <K, V> List<V> random(@NonNull Naming naming, long min, long max, int size,
|
|
|
- @NonNull BiFunction<Naming, Boolean, List<V>> supplier,
|
|
|
- @NonNull Converter<K, V> converter) {
|
|
|
- // 从缓存加载数据
|
|
|
- List<V> values = this.randomizing(naming, min, max, size);
|
|
|
- boolean initial = Objects.isNull(values);
|
|
|
- if (!initial && values.size() >= size) {
|
|
|
- return values;
|
|
|
- }
|
|
|
-
|
|
|
- // 重新加载并更新缓存数据
|
|
|
- List<V> suppliers = supplier.apply(naming, initial);
|
|
|
- if (ObjectUtils.isEmpty(suppliers)) {
|
|
|
- return ObjectUtils.ifEmpty(values, Collections::emptyList);
|
|
|
- } else if (ObjectUtils.isEmpty(values)) {
|
|
|
- this.set(naming, suppliers, converter);
|
|
|
- } else {
|
|
|
- Set<K> exists = values.stream().map(converter::id).collect(Collectors.toSet());
|
|
|
- List<V> news = suppliers.stream().filter(value -> !exists.contains(converter.id(value)))
|
|
|
- .collect(Collectors.toList());
|
|
|
- if (ObjectUtils.notEmpty(news)) {
|
|
|
- this.set(naming, news, converter);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 如果size < 0 则返回所有数据,否则返回最多 size 个数据
|
|
|
- return size < 0 ? suppliers : suppliers.size() <= size ? suppliers : suppliers.subList(0, size);
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- @Override
|
|
|
- public <K, V> boolean set(@NonNull Naming naming, @NonNull K id, @NonNull V value) {
|
|
|
- String key = naming.getPrimary() + ":" + id;
|
|
|
- try {
|
|
|
- if (naming.getDuration() > 0) {
|
|
|
- this.template.opsForValue().set(key, value, naming.getDuration(), TimeUnit.SECONDS);
|
|
|
- } else {
|
|
|
- this.template.opsForValue().set(key, value);
|
|
|
- }
|
|
|
- } catch (Throwable t) {
|
|
|
- log.warn("Redis cache set failed: {}, {}", key, t.getMessage());
|
|
|
- return false;
|
|
|
- }
|
|
|
- return true;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public <K, V> boolean set(@NonNull Naming naming, @NonNull K id, @NonNull V value, long order) {
|
|
|
- List<String> keys = naming.getKeys();
|
|
|
- Boolean success = null;
|
|
|
- try {
|
|
|
- if (naming.getDuration() > 0) {
|
|
|
- success = this.template.execute(SET_EXPIRE_SCRIPT, keys, id, value, order, naming.getDuration());
|
|
|
- } else {
|
|
|
- success = this.template.execute(SET_SCRIPT, keys, id, value, order);
|
|
|
- }
|
|
|
- } catch (Throwable t) {
|
|
|
- log.warn("Redis cache set failed: {}, {}, {}", naming, id, t.getMessage());
|
|
|
- }
|
|
|
- return Boolean.TRUE.equals(success);
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public <K, V> boolean set(@NonNull Naming naming, @NonNull Collection<V> values,
|
|
|
- @NonNull Converter<K, V> converter) {
|
|
|
- if (ObjectUtils.isEmpty(values)) {
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- int i = 0;
|
|
|
- Object[] args;
|
|
|
- if (naming.getDuration() > 0) {
|
|
|
- args = new Object[values.size() * 3 + 1];
|
|
|
- args[i++] = naming.getDuration();
|
|
|
- } else {
|
|
|
- args = new Object[values.size() * 3];
|
|
|
- }
|
|
|
- for (V value : values) {
|
|
|
- args[i++] = converter.id(value);
|
|
|
- args[i++] = value;
|
|
|
- args[i++] = converter.order(value);
|
|
|
- }
|
|
|
- List<String> keys = naming.getKeys();
|
|
|
- Long number = null;
|
|
|
- try {
|
|
|
- if (naming.getDuration() > 0) {
|
|
|
- number = this.template.execute(BATCH_SET_EXPIRE_SCRIPT, keys, args);
|
|
|
- } else {
|
|
|
- number = this.template.execute(BATCH_SET_SCRIPT, keys, args);
|
|
|
- }
|
|
|
- } catch (Throwable t) {
|
|
|
- log.warn("Redis cache batch set failed: {}, {}", naming, t.getMessage());
|
|
|
- }
|
|
|
- return number != null && number == values.size();
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public <K, V> boolean update(@NonNull Naming naming, @NonNull K id, @NonNull V value, long order) {
|
|
|
- List<String> keys = naming.getKeys();
|
|
|
- try {
|
|
|
- if (naming.getDuration() > 0) {
|
|
|
- this.template.execute(UPDATE_EXPIRE_SCRIPT, keys, id, value, order, naming.getDuration());
|
|
|
- } else {
|
|
|
- this.template.execute(UPDATE_SCRIPT, keys, id, value, order);
|
|
|
- }
|
|
|
- } catch (Throwable t) {
|
|
|
- log.warn("Redis cache update failed: {}, {}, {}", naming, id, t.getMessage());
|
|
|
- return false;
|
|
|
- }
|
|
|
- return true;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public <K, V> boolean update(@NonNull Naming naming, @NonNull V value, @NonNull Converter<K, V> converter) {
|
|
|
- return this.update(naming, converter.id(value), value, converter.order(value));
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public <K, V> boolean update(@NonNull Collection<Pair<Naming, V>> pairs, @NonNull Converter<K, V> converter) {
|
|
|
- if (ObjectUtils.isEmpty(pairs)) {
|
|
|
- return false;
|
|
|
- }
|
|
|
- List<String> keys = Lists.newLinkedList();
|
|
|
- Object[] args = new Object[pairs.size() * 5];
|
|
|
- int i = 0;
|
|
|
- for (Pair<Naming, V> pair : pairs) {
|
|
|
- keys.addAll(pair.getKey().getKeys());
|
|
|
- args[i++] = converter.id(pair.getValue());
|
|
|
- args[i++] = pair.getValue();
|
|
|
- args[i++] = converter.order(pair.getValue());
|
|
|
- args[i++] = pair.getKey().getDuration();
|
|
|
- args[i++] = pair.getKey().getKeys().size();
|
|
|
- }
|
|
|
- try {
|
|
|
- this.template.execute(BATCH_UPDATE_SCRIPT, keys, args);
|
|
|
- } catch (Throwable t) {
|
|
|
- log.warn("Redis cache batch update failed: {}", t.getMessage());
|
|
|
- return false;
|
|
|
- }
|
|
|
- return true;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public boolean remove(@NonNull Naming naming) {
|
|
|
- List<String> keys = naming.getKeys().stream().distinct().collect(Collectors.toList());
|
|
|
- Boolean success = null;
|
|
|
- try {
|
|
|
- success = this.template.execute(REMOVE_ALL_SCRIPT, keys);
|
|
|
- } catch (Throwable t) {
|
|
|
- log.warn("Redis cache remove failed: {}, {}", naming, t.getMessage());
|
|
|
- }
|
|
|
- return Boolean.TRUE.equals(success);
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public <K> boolean remove(@NonNull Naming naming, @NonNull K id) {
|
|
|
- List<String> keys = naming.getKeys();
|
|
|
- Boolean success = null;
|
|
|
- try {
|
|
|
- success = this.template.execute(REMOVE_SCRIPT, keys, id);
|
|
|
- } catch (Throwable t) {
|
|
|
- log.warn("Redis cache remove failed: {}, {}, {}", naming, id, t.getMessage());
|
|
|
- }
|
|
|
- return Boolean.TRUE.equals(success);
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public <K> boolean remove(@NonNull Collection<Pair<Naming, K>> pairs) {
|
|
|
- if (ObjectUtils.isEmpty(pairs)) {
|
|
|
- return false;
|
|
|
- }
|
|
|
- List<String> keys = Lists.newArrayListWithCapacity(pairs.size() * 2);
|
|
|
- Object[] args = new Object[pairs.size()];
|
|
|
- int i = 0;
|
|
|
- for (Pair<Naming, K> pair : pairs) {
|
|
|
- keys.add(pair.getKey().getPrimary());
|
|
|
- keys.add(pair.getKey().getIndexes().get(0));
|
|
|
- args[i++] = pair.getValue();
|
|
|
- }
|
|
|
- Boolean success = null;
|
|
|
- try {
|
|
|
- success = this.template.execute(BATCH_REMOVE_SCRIPT, keys, args);
|
|
|
- } catch (Throwable t) {
|
|
|
- log.warn("Redis cache batch remove failed: {}, ", t.getMessage());
|
|
|
- }
|
|
|
- return Boolean.TRUE.equals(success);
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public <K> boolean clear(@NonNull Naming naming, @NonNull K id) {
|
|
|
- List<String> keys = naming.getKeys();
|
|
|
- Boolean success = null;
|
|
|
- try {
|
|
|
- success = this.template.execute(CLEAR_SCRIPT, keys, id);
|
|
|
- } catch (Throwable t) {
|
|
|
- log.warn("Redis cache clear failed: {}, {}, {}", naming, id, t.getMessage());
|
|
|
- }
|
|
|
- return Boolean.TRUE.equals(success);
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public <K> boolean clear(@NonNull Collection<Pair<Naming, K>> pairs) {
|
|
|
- if (ObjectUtils.isEmpty(pairs)) {
|
|
|
- return false;
|
|
|
- }
|
|
|
- List<String> keys = Lists.newLinkedList();
|
|
|
- Object[] args = new Object[pairs.size() * 2];
|
|
|
- int i = 0;
|
|
|
- for (Pair<Naming, K> pair : pairs) {
|
|
|
- keys.addAll(pair.getKey().getKeys());
|
|
|
- args[i++] = pair.getValue();
|
|
|
- args[i++] = pair.getKey().getKeys().size();
|
|
|
- }
|
|
|
- Boolean success = null;
|
|
|
- try {
|
|
|
- success = this.template.execute(BATCH_CLEAR_SCRIPT, keys, args);
|
|
|
- } catch (Throwable t) {
|
|
|
- log.warn("Redis cache batch clear failed: {}", t.getMessage());
|
|
|
- }
|
|
|
- return Boolean.TRUE.equals(success);
|
|
|
- }
|
|
|
-}
|