RedisSet缓存List数据并设置过期时间——Lua实现

该博客介绍了如何使用Redis的Lua脚本进行高效的数据缓存操作,包括批量缓存List数据并删除原有数据、删除并返回指定范围的数据以及缓存Map的同时设置过期时间。文中提供了自定义的Lua脚本执行器及具体使用示例,适用于需要在集群环境中进行定制化缓存管理的场景。
摘要由CSDN通过智能技术生成

RedisSet缓存List数据操作并设置过期时间—Lua脚本实现

工作中自己开发需要,现有的工具不能满足,于是自己定制了一套

注意:集群环境下Lua脚本还有pipeline是不支持在所有节点上执行的

批量缓存List内容数据,而不是将整个List作为一个value缓存,并且删除原有的数据,同时设置过期时间,

定义方法内容
    /**
     * @description 批量缓存List数据,并且删除原有的数据,同时设置过期时间
     * @param clazz List的数据泛型
     * @param duration 过期时间 单位/秒
     * @author Lutong Sun
     * @date 2020/11/25
     */
    public static <T> void listRightPushAllNewAndDeleteOldALL(List<String> keyList, Class<T> clazz, Long duration, List lists) {
        if (!CollectionUtils.isEmpty(lists)) {
            String scriptString = "local key = KEYS[1];\n" +
                    "local duration = tonumber(ARGV[1]);\n" +
                    "table.remove(ARGV, 1);\n" +
                    "redis.call(\"DEL\", key)\n" +  // 删除原有的数据 不需要可以删除这一行
                    "for i=1, #(ARGV) do\n" +
                    "\tredis.call(\"RPUSH\", key, ARGV[i]);\n" +
                    "end\n" +
                    "redis.call(\"EXPIRE\", key, duration)";
            RedisScript<String> redisScript = new DefaultRedisScript(scriptString, String.class);
            
            // 自定义脚本执行器,为了将所有arg作为一个数组处理
            redisTemplate.setScriptExecutor(new MultipleArgumentsScriptExecutor(redisTemplate));
            redisTemplate.execute(redisScript, new FastJsonRedisSerializer<>(clazz), new StringRedisSerializer(), keyList, duration, lists);
        }
    }
MultipleArgumentsScriptExecutor自定义Lua脚本执行器
import com.google.common.collect.Lists;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultScriptExecutor;
import org.springframework.data.redis.serializer.RedisSerializer;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;

public class MultipleArgumentsScriptExecutor extends DefaultScriptExecutor {
    /**
     * @param template The {@link RedisTemplate} to use
     */
    public MultipleArgumentsScriptExecutor(RedisTemplate template) {
        super(template);
    }

    @Override
    protected byte[][] keysAndArgs(RedisSerializer argsSerializer, List keys, Object[] args) {
        List<Object> argList = Lists.newArrayList();
        if (!Objects.isNull(args) && args.length > 0) {
            Arrays.stream(args)
                    .forEach(arg -> {
                        if (arg instanceof Collection) {
                            argList.addAll((Collection<?>) arg);
                        } else {
                            argList.add(arg);
                        }
                    });
        }
        return super.keysAndArgs(argsSerializer, keys, argList.toArray());
    }
}
使用方法
RedisUtils.listRightPushAllNewAndDeleteOldALL(
Lists.newArrayList(cacheKey), // cacheKey是Redis key
ProductBehaviourDomain.class, // 缓存数据类型
100l, // 过期周期 单位s
productBehaviourDomainList  // 要缓存的数据, 类型是List<ProductBehaviourDomain>
);

删除并返回对应下标的数据, 如果下标超过现有的最大下标则按现有最大下标操作—Lua脚本实现

定义方法内容
    /**
     * @description 删除并返回对应下标的数据, 如果下标超过现有的最大下标则按现有最大下标操作
     * @author Lutong Sun
     * @date 2020/11/25
     */
    public static <T> List<T> listRangeAndTrim(List<String> keyList,  Class<T> clazz, String...argvs)  {
        String scriptString = "local key = KEYS[1];\n" +
                "local fromIndex = tonumber(ARGV[1]);\n" +
                "local endIndex = tonumber(ARGV[2]);\n" +
                "local len = redis.call(\"LLEN\", key);\n" +
                "if len <= endIndex then\n" +
                "\tendIndex = len;\n" +
                "end\n" +
                "local array = redis.call(\"LRANGE\", key, fromIndex, endIndex)\n" +
                "redis.call(\"LTRIM\", key, endIndex+1, -1)\n" +
                "local result = \"[\";\n" +
                "for i=1, #(array) do\n" +
                "\tresult = result .. array[i] .. \",\";\n" +
                "end\n" +
                "string.sub(result, 1, string.len(result)-1);\n" +
                "result = result .. \"]\";\n" +
                "return result;";
        RedisScript<String> redisScript = new DefaultRedisScript(scriptString, String.class);
        String jsonString = (String)redisTemplate.execute(redisScript, new StringRedisSerializer(), new FastJsonRedisSerializer(String.class), keyList, argvs);
        return JSONObject.parseArray(jsonString, clazz);
    }
使用方法
List<ProductBehaviourDomain> productBehaviourDomainCacheList = RedisUtils.listRangeAndTrim(
Lists.newArrayList(cacheKey), // cacheKey是redis key
ProductBehaviourDomain.class, // 返回的数据元素类型
"0", // 要返回数据的起始下标 
"100" // 要返回数据的结束下标
//以 0 表示列表的第一个元素,以 1 表示列表的第二个元素,以此类推。你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推, 返回数据是闭区间,就是0,100.会返回101个数据
);

缓存MAP同时设置过期时间—Lua脚本实现

定义方法内容
    public static void hmsetWithExpire(String key, Map<String, String> map, Long expire) {
        String scriptString =
                "local key = KEYS[1];\n" +
                        "local mapSize = tonumber(ARGV[1]);\n" +
                        "table.remove(ARGV, 1);\n" +
                        "local expire = tonumber(ARGV[1]);\n" +
                        "table.remove(ARGV, 1);\n" +
                        "for i=1, mapSize do\n" +
                        "\tlocal mapKey = string.sub(ARGV[i], 2, string.len(ARGV[i])-1);\n" +
                        "\tredis.call(\"HSET\", key, mapKey, ARGV[i+mapSize]);\n" +
                        "end\n" +
                        "redis.call(\"EXPIRE\", key, expire);";
        RedisScript<String> redisScript = new DefaultRedisScript(scriptString, String.class);
        redisTemplate.setScriptExecutor(new MultipleArgumentsScriptExecutor(redisTemplate));
        redisTemplate.execute(redisScript, Lists.newArrayList(key), map.size(), expire, map.keySet(), map.values());
    }
使用方法:
        Map<String, String> map = new HashMap<>();
        map.put("name1", "swk1");
        map.put("name2", "swk2");
//        RedisUtils.hmset("test", map);
        RedisUtils.hmsetWithExpire(
        cacheKey, // 缓存key
        map, // 缓存map
        2000l); // 过期时间 单位s
好的,下面是解决缓存击穿问题的逻辑过期代码: ``` public class RedisUtil { private static final Logger logger = LoggerFactory.getLogger(RedisUtil.class); private static final Long SUCCESS = 1L; private static final String SET_IF_NOT_EXIST = "NX"; private static final String SET_WITH_EXPIRE_TIME = "PX"; private static final String LOCK_PREFIX = "redis_lock_"; private static final String OK = "OK"; private static final String UNLOCK_LUA; static { StringBuilder sb = new StringBuilder(); sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] "); sb.append("then "); sb.append(" return redis.call(\"del\",KEYS[1]) "); sb.append("else "); sb.append(" return 0 "); sb.append("end "); UNLOCK_LUA = sb.toString(); } private JedisPool jedisPool; public RedisUtil(JedisPool jedisPool) { this.jedisPool = jedisPool; } /** * 获取分布式锁 * * @param lockKey 锁 * @param requestId 请求标识 * @param expireTime 超期时间 * @return 是否获取成功 */ public boolean tryGetDistributedLock(String lockKey, String requestId, int expireTime) { Jedis jedis = jedisPool.getResource(); String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime); jedis.close(); return OK.equals(result); } /** * 释放分布式锁 * * @param lockKey 锁 * @param requestId 请求标识 * @return 是否释放成功 */ public boolean releaseDistributedLock(String lockKey, String requestId) { Jedis jedis = jedisPool.getResource(); Object result = jedis.eval(UNLOCK_LUA, Collections.singletonList(lockKey), Collections.singletonList(requestId)); jedis.close(); return SUCCESS.equals(result); } /** * 获取缓存 * * @param key 缓存key * @return 缓存值 */ public String get(String key) { Jedis jedis = jedisPool.getResource(); String value = jedis.get(key); jedis.close(); return value; } /** * 设置缓存 * * @param key 缓存key * @param value 缓存值 * @return 是否设置成功 */ public boolean set(String key, String value) { Jedis jedis = jedisPool.getResource(); String result = jedis.set(key, value); jedis.close(); return OK.equals(result); } /** * 设置缓存并带过期时间 * * @param key 缓存key * @param value 缓存值 * @param expireTime 过期时间,单位秒 * @return 是否设置成功 */ public boolean setWithExpireTime(String key, String value, int expireTime) { Jedis jedis = jedisPool.getResource(); String result = jedis.setex(key, expireTime, value); jedis.close(); return OK.equals(result); } /** * 缓存逻辑过期 * * @param key 缓存key * @param expireSeconds 过期时间,单位秒 * @param getDataFunc 获取数据的函数 * @return 缓存值 */ public String getOrSetWithExpireLogic(String key, int expireSeconds, Supplier<String> getDataFunc) { String value = get(key); if (value == null) { // 获取分布式锁 String requestId = UUID.randomUUID().toString(); boolean lockResult = tryGetDistributedLock(LOCK_PREFIX + key, requestId, expireSeconds * 1000); if (lockResult) { // 获取数据 value = getDataFunc.get(); if (value != null) { // 设置缓存并带过期时间 setWithExpireTime(key, value, expireSeconds); logger.info("set cache success, key={}, expireSeconds={}", key, expireSeconds); } // 释放分布式锁 releaseDistributedLock(LOCK_PREFIX + key, requestId); } else { // 获取锁失败,等待一段时间后重试 try { Thread.sleep(100); } catch (InterruptedException e) { logger.error("线程等待异常", e); } // 递归调用自身 return getOrSetWithExpireLogic(key, expireSeconds, getDataFunc); } } return value; } } ``` 这段代码实现了一个缓存逻辑过期的功能。当缓存失效时,先获取分布式锁,然后再次检查缓存是否存在,如果不存在,则执行获取数据的函数,然后设置缓存并带过期时间,最后释放分布式锁。如果获取分布式锁失败,则等待一段时间后重试。这样可以避免缓存击穿的问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值