手写Redis分布式锁+RedisUtil二次封装

发布于:2024-12-18 ⋅ 阅读:(107) ⋅ 点赞:(0)

1.手写Redis分布式锁

1.RedisShareLockUtil
package com.sunxiansheng.redis.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Component
public class RedisShareLockUtil {

    private static final Logger logger = LoggerFactory.getLogger(RedisShareLockUtil.class);
    private static final long DEFAULT_TIMEOUT = 10; // 默认超时时间(秒)
    private static final String DEFAULT_LOCK_NAME_PREFIX = "lock:"; // 默认锁名前缀

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 尝试获取锁
     * @param lockKey 锁的key
     * @param timeout 超时时间(秒)
     * @return 锁的value,用于释放锁
     */
    public String tryLock(String lockKey, long timeout) {
        String value = UUID.randomUUID().toString();
        Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, value, timeout, TimeUnit.SECONDS);
        while (locked == null || !locked) {
            if (locked == null) {
                logger.error("尝试获取锁时出现错误,可能是 Redis 连接问题,key: {}", lockKey);
            } else {
                logger.warn("获取锁失败,重试中,key: {}", lockKey);
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                logger.error("尝试获取锁时线程被中断,key: {}", lockKey, e);
                return null;
            }
            locked = redisTemplate.opsForValue().setIfAbsent(lockKey, value, timeout, TimeUnit.SECONDS);
        }
        logger.info("成功获取锁,key: {}", lockKey);
        return value;
    }

    /**
     * 释放锁
     * @param lockKey 锁的key
     * @param value 锁的value
     */
    public boolean releaseLock(String lockKey, String value) {
        String scriptText = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        script.setScriptText(scriptText);
        script.setResultType(Long.class);
        Long result = redisTemplate.execute(script, Collections.singletonList(lockKey), value);
        if (result != null && result > 0) {
            logger.info("成功释放锁,key: {}", lockKey);
            return true;
        } else {
            if (result == null) {
                logger.error("释放锁失败,Redis 执行错误,key: {}", lockKey);
            } else {
                logger.warn("释放锁失败,锁不存在或锁的值不匹配,key: {}", lockKey);
            }
            return false;
        }
    }


    /**
     * 使用锁执行操作,使用默认超时时间
     * @param lockKey 锁的key
     * @param action 要执行的操作
     */
    public void executeWithLock(String lockKey, Runnable action) {
        executeWithLock(lockKey, DEFAULT_TIMEOUT, action);
    }

    /**
     * 使用锁执行操作
     * @param lockKey 锁的key
     * @param timeout 超时时间(秒)
     * @param action 要执行的操作
     */
    public void executeWithLock(String lockKey, long timeout, Runnable action) {
        String fullLockKey = DEFAULT_LOCK_NAME_PREFIX + lockKey;
        String value = tryLock(fullLockKey, timeout);
        if (value != null) {
            try {
                action.run();
            } finally {
                releaseLock(fullLockKey, value);
            }
        } else {
            logger.warn("无法获取锁,key: {}", fullLockKey);
        }
    }
}
2.使用方式
    @RequestMapping("/testLock")
    public void testLock() {
        // 默认超时时间10s,自定义锁名字
        redisShareLockUtil.executeWithLock("mylock", () -> {
            // 需要加锁的逻辑
        });
        // 
        // 自定义超时时间30s,自定义锁名字
        redisShareLockUtil.executeWithLock("mylock", 30, () -> {
            // 需要加锁的逻辑
        });
    }

2.RedisUtil二次封装

1.RedisUtil
package com.sunxiansheng.redis.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * Description: RedisUtil工具类
 * @Author sun
 * @Create 2024/6/5 14:17
 * @Version 1.0
 */
@Component
public class RedisUtil {

    private static final Logger logger = LoggerFactory.getLogger(RedisUtil.class);

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    private static final String CACHE_KEY_SEPARATOR = ".";

    /**
     * 构建缓存key
     * @param strObjs 多个字符串拼接成缓存key
     * @return 拼接后的缓存key
     * @example
     * <pre>
     *     String key = redisUtil.buildKey("user", "123");
     *     System.out.println(key);  // 输出: user.123
     * </pre>
     */
    public String buildKey(String... strObjs) {
        return String.join(CACHE_KEY_SEPARATOR, strObjs);
    }

    // =============================Common============================

    /**
     * 是否存在key
     * @param key Redis中的key
     * @return true如果key存在,否则false
     * @example
     * <pre>
     *     boolean exists = redisUtil.exists("myKey");
     *     System.out.println(exists);
     * </pre>
     */
    public boolean exists(String key) {
        return execute(() -> redisTemplate.hasKey(key));
    }

    /**
     * 删除key
     * @param key Redis中的key
     * @return true如果删除成功,否则false
     * @example
     * <pre>
     *     boolean deleted = redisUtil.delete("myKey");
     *     System.out.println(deleted);
     * </pre>
     */
    public boolean delete(String key) {
        return execute(() -> redisTemplate.delete(key));
    }

    // =============================String============================

    /**
     * 设置key-value对
     * @param key Redis中的key
     * @param value 要设置的值
     * @example
     * <pre>
     *     redisUtil.set("myKey", "myValue");
     * </pre>
     */
    public void set(String key, Object value) {
        execute(() -> {
            redisTemplate.opsForValue().set(key, value);
            return null;
        });
    }

    /**
     * 设置key-value对,并设置过期时间
     * @param key Redis中的key
     * @param value 要设置的值
     * @param timeout 过期时间
     * @param unit 时间单位
     * @example
     * <pre>
     *     redisUtil.set("myKey", "myValue", 10, TimeUnit.MINUTES);
     * </pre>
     */
    public void set(String key, Object value, long timeout, TimeUnit unit) {
        execute(() -> {
            redisTemplate.opsForValue().set(key, value, timeout, unit);
            return null;
        });
    }

    /**
     * 设置key-value对,如果key不存在,则设置成功,并指定过期时间
     * @param key Redis中的key
     * @param value 要设置的值
     * @param timeout 过期时间
     * @param unit 时间单位
     * @return true如果设置成功,否则false
     * @example
     * <pre>
     *     boolean result = redisUtil.setIfAbsent("myKey", "myValue", 10, TimeUnit.MINUTES);
     *     System.out.println(result);
     * </pre>
     */
    public boolean setIfAbsent(String key, Object value, long timeout, TimeUnit unit) {
        return execute(() -> redisTemplate.opsForValue().setIfAbsent(key, value, timeout, unit));
    }

    /**
     * 获取指定key的值
     * @param key Redis中的key
     * @param clazz 值的类型
     * @return key对应的值
     * @example
     * <pre>
     *     String value = redisUtil.get("myKey", String.class);
     *     System.out.println(value);
     * </pre>
     */
    public <T> Optional<T> get(String key, Class<T> clazz) {
        return Optional.ofNullable(execute(() -> clazz.cast(redisTemplate.opsForValue().get(key))));
    }

    /**
     * 递增
     * @param key Redis中的key
     * @param delta 增量
     * @example
     * <pre>
     *     redisUtil.increment("myKey", 1);
     * </pre>
     */
    public void increment(String key, long delta) {
        execute(() -> {
            redisTemplate.opsForValue().increment(key, delta);
            return null;
        });
    }

    // =============================Hash============================

    /**
     * 向hash中存入数据
     * @param key Redis中的key
     * @param hashKey hash中的小key
     * @param value hash中的小value
     * @example
     * <pre>
     *     redisUtil.hPut("myHash", "field1", "value1");
     * </pre>
     */
    public void hPut(String key, String hashKey, Object value) {
        execute(() -> {
            redisTemplate.opsForHash().put(key, hashKey, value);
            return null;
        });
    }

    /**
     * 获取hash中的数据
     * @param key Redis中的key
     * @param hashKey hash中的小key
     * @param clazz 值的类型
     * @return hash中的小value
     * @example
     * <pre>
     *     String value = redisUtil.hGet("myHash", "field1", String.class);
     *     System.out.println(value);
     * </pre>
     */
    public <T> Optional<T> hGet(String key, String hashKey, Class<T> clazz) {
        return Optional.ofNullable(execute(() -> clazz.cast(redisTemplate.opsForHash().get(key, hashKey))));
    }

    /**
     * 获取hash中的所有数据
     * @param key Redis中的key
     * @return hash中的所有数据
     * @example
     * <pre>
     *     Map<Object, Object> map = redisUtil.hGetAll("myHash");
     *     map.forEach((k, v) -> System.out.println(k + ": " + v));
     * </pre>
     */
    public Optional<Map<Object, Object>> hGetAll(String key) {
        return Optional.ofNullable(execute(() -> redisTemplate.opsForHash().entries(key)));
    }

    /**
     * 删除hash中的指定字段
     * @param key Redis中的key
     * @param hashKey hash中的小key
     * @example
     * <pre>
     *     redisUtil.hDelete("myHash", "field1");
     * </pre>
     */
    public void hDelete(String key, Object... hashKey) {
        execute(() -> {
            redisTemplate.opsForHash().delete(key, hashKey);
            return null;
        });
    }

    /**
     * 获取并删除hash中的所有数据
     * @param key Redis中的key
     * @return hash中的所有数据
     * @example
     * <pre>
     *     Map<Object, Object> map =redisUtil.hGetAndDelete(“myHash”);
     *     map.forEach((k, v) -> System.out.println(k + “: “ + v));
     * </pre>
     */
    public Optional<Map<Object, Object>> hGetAndDelete(String key) {
        Map<Object, Object> map = new HashMap<>();
        try (Cursor<Map.Entry<Object, Object>> cursor = redisTemplate.opsForHash().scan(key, ScanOptions.NONE)) {
            while (cursor.hasNext()) {
                Map.Entry<Object, Object> entry = cursor.next();
                Object hashKey = entry.getKey();
                Object hashValue = entry.getValue();
                map.put(hashKey, hashValue);
                redisTemplate.opsForHash().delete(key, hashKey);
            }
        } catch (Exception e) {
            logger.error("Redis hGetAndDelete error: key={}", key, e);
        }
        return Optional.of(map);
    }
// =============================List============================

    /**
     * 向list中左侧推入数据
     * @param key Redis中的key
     * @param value list中的值
     * @example
     * <pre>
     *     redisUtil.lPush("myList", "value1");
     * </pre>
     */
    public void lPush(String key, Object value) {
        execute(() -> {
            redisTemplate.opsForList().leftPush(key, value);
            return null;
        });
    }

    /**
     * 向list中右侧推入数据
     * @param key Redis中的key
     * @param value list中的值
     * @example
     * <pre>
     *     redisUtil.rPush("myList", "value1");
     * </pre>
     */
    public void rPush(String key, Object value) {
        execute(() -> {
            redisTemplate.opsForList().rightPush(key, value);
            return null;
        });
    }

    /**
     * 从list中左侧弹出数据
     * @param key Redis中的key
     * @param clazz 值的类型
     * @return list中的值
     * @example
     * <pre>
     *     String value = redisUtil.lPop("myList", String.class).orElse(null);
     *     System.out.println(value);
     * </pre>
     */
    public <T> Optional<T> lPop(String key, Class<T> clazz) {
        return Optional.ofNullable(execute(() -> clazz.cast(redisTemplate.opsForList().leftPop(key))));
    }

    /**
     * 从list中右侧弹出数据
     * @param key Redis中的key
     * @param clazz 值的类型
     * @return list中的值
     * @example
     * <pre>
     *     String value = redisUtil.rPop("myList", String.class).orElse(null);
     *     System.out.println(value);
     * </pre>
     */
    public <T> Optional<T> rPop(String key, Class<T> clazz) {
        return Optional.ofNullable(execute(() -> clazz.cast(redisTemplate.opsForList().rightPop(key))));
    }

    /**
     * 获取list中的指定范围的数据
     * @param key Redis中的key
     * @param start 起始位置
     * @param end 结束位置
     * @return list中的值
     * @example
     * <pre>
     *     List<Object> list = redisUtil.lRange("myList", 0, -1).orElse(Collections.emptyList());
     *     list.forEach(System.out::println);
     * </pre>
     */
    public Optional<List<Object>> lRange(String key, long start, long end) {
        return Optional.ofNullable(execute(() -> redisTemplate.opsForList().range(key, start, end)));
    }

// =============================Set============================

    /**
     * 向set中添加数据
     * @param key Redis中的key
     * @param values set中的值
     * @example
     * <pre>
     *     redisUtil.sAdd("mySet", "value1", "value2");
     * </pre>
     */
    public void sAdd(String key, Object... values) {
        execute(() -> {
            redisTemplate.opsForSet().add(key, values);
            return null;
        });
    }

    /**
     * 获取set中的所有数据
     * @param key Redis中的key
     * @return set中的所有值
     * @example
     * <pre>
     *     Set<Object> set = redisUtil.sMembers("mySet").orElse(Collections.emptySet());
     *     set.forEach(System.out::println);
     * </pre>
     */
    public Optional<Set<Object>> sMembers(String key) {
        return Optional.ofNullable(execute(() -> redisTemplate.opsForSet().members(key)));
    }

    /**
     * 判断set中是否存在指定的值
     * @param key Redis中的key
     * @param value set中的值
     * @return true如果存在,否则false
     * @example
     * <pre>
     *     boolean exists = redisUtil.sIsMember("mySet", "value1").orElse(false);
     *     System.out.println(exists);
     * </pre>
     */
    public Optional<Boolean> sIsMember(String key, Object value) {
        return Optional.ofNullable(execute(() -> redisTemplate.opsForSet().isMember(key, value)));
    }

    /**
     * 从set中随机弹出一个值
     * @param key Redis中的key
     * @return set中的值
     * @example
     * <pre>
     *     Object value = redisUtil.sPop("mySet").orElse(null);
     *     System.out.println(value);
     * </pre>
     */
    public Optional<Object> sPop(String key) {
        return Optional.ofNullable(execute(() -> redisTemplate.opsForSet().pop(key)));
    }

    /**
     * 获取set的大小
     * @param key Redis中的key
     * @return set的大小
     * @example
     * <pre>
     *     long size = redisUtil.sCard("mySet").orElse(0L);
     *     System.out.println(size);
     * </pre>
     */
    public Optional<Long> sCard(String key) {
        return Optional.ofNullable(execute(() -> redisTemplate.opsForSet().size(key)));
    }

// =============================ZSet============================

    /**
     * 向有序集合中添加元素
     * @param key Redis中的key
     * @param value 元素的值
     * @param score 元素的分数
     * @return true如果添加成功,否则false
     * @example
     * <pre>
     *     boolean added = redisUtil.zAdd("myZSet", "value1", 1.0).orElse(false);
     *     System.out.println(added);
     * </pre>
     */
    public Optional<Boolean> zAdd(String key, Object value, double score) {
        return Optional.ofNullable(execute(() -> redisTemplate.opsForZSet().add(key, value, score)));
    }

    /**
     * 获取有序集合的元素数量
     * @param key Redis中的key
     * @return 元素数量
     * @example
     * <pre>
     *     long size = redisUtil.zCard("myZSet").orElse(0L);
     *     System.out.println(size);
     * </pre>
     */
    public Optional<Long> zCard(String key) {
        return Optional.ofNullable(execute(() -> redisTemplate.opsForZSet().size(key)));
    }

    /**
     * 获取有序集合指定范围内的元素
     * @param key Redis中的key
     * @param start 起始位置
     * @param end 结束位置
     * @return 指定范围内的元素集合
     * @example
     * <pre>
     *     Set<Object> set = redisUtil.zRange("myZSet", 0, -1).orElse(Collections.emptySet());
     *     set.forEach(System.out::println);
     * </pre>
     */
    public Optional<Set<Object>> zRange(String key, long start, long end) {
        return Optional.ofNullable(execute(() -> redisTemplate.opsForZSet().range(key, start, end)));
    }

    /**
     * 删除有序集合中的指定元素
     * @param key Redis中的key
     * @param value 要删除的元素
     * @return 被删除的元素数量
     * @example
     * <pre>
     *     long removed = redisUtil.zRemove("myZSet", "value1").orElse(0L);
     *     System.out.println(removed);
     * </pre>
     */
    public Optional<Long> zRemove(String key, Object value) {
        return Optional.ofNullable(execute(() -> redisTemplate.opsForZSet().remove(key, value)));
    }

    /**
     * 获取有序集合中指定元素的分数
     * @param key Redis中的key
     * @param value 元素的值
     * @return 元素的分数
     * @example
     * <pre>
     *     double score = redisUtil.zScore("myZSet", "value1").orElse(null);
     *     System.out.println(score);
     * </pre>
     */
    public Optional<Double> zScore(String key, Object value) {
        return Optional.ofNullable(execute(() -> redisTemplate.opsForZSet().score(key, value)));
    }

    /**
     * 获取有序集合中指定分数范围内的元素
     * @param key Redis中的key
     * @param start 起始分数
     * @param end 结束分数
     * @return 指定分数范围内的元素集合
     * @example
     * <pre>
     *     Set<Object> set = redisUtil.zRangeByScore("myZSet”, 0, 100).orElse(Collections.emptySet());
     *     set.forEach(System.out::println);
     *
     */
    public Optional<Set> zRangeByScore(String key, double start, double end) {
        return Optional.ofNullable(execute(() -> redisTemplate.opsForZSet().rangeByScore(key, start, end)));
    }

    /**
     * 增加有序集合中指定元素的分数
     * @param key Redis中的key
     * @param value 元素的值
     * @param score 增加的分数
     * @return 增加后的分数
     * @example
     * <pre>
     *     double newScore = redisUtil.zIncrementScore("myZSet", "value1", 10.0).orElse(null);
     *     System.out.println(newScore);
     * </pre>
     */
    public Optional<Double> zIncrementScore(String key, Object value, double score) {
        return Optional.ofNullable(execute(() -> redisTemplate.opsForZSet().incrementScore(key, value, score)));
    }

    /**
     * 获取有序集合中指定元素的排名
     * @param key Redis中的key
     * @param value 元素的值
     * @return 元素的排名
     * @example
     * <pre>
     *     long rank = redisUtil.zRank("myZSet", "value1").orElse(null);
     *     System.out.println(rank);
     * </pre>
     */
    public Optional<Long> zRank(String key, Object value) {
        return Optional.ofNullable(execute(() -> redisTemplate.opsForZSet().rank(key, value)));
    }

    /**
     * 从有序集合中按分数范围获取成员及其分数
     * @param key 排行榜的key
     * @param start 起始位置(包含)
     * @param end 结束位置(包含)
     * @return Set<ZSetOperations.TypedTuple < Object>> 每个TypedTuple对象包含以下内容:value: 集合中的成员,score: 成员的分数。
     * @example
     * <pre>
     *     Set<ZSetOperations.TypedTuple<Object>> set = redisUtil.zRangeWithScores("myZSet", 0, 100).orElse(Collections.emptySet());
     *     set.forEach(tuple -> System.out.println(tuple.getValue() + ": " + tuple.getScore()));
     * </pre>
     */
    public Optional<Set<ZSetOperations.TypedTuple<Object>>> zRangeWithScores(String key, long start, long end) {
        return Optional.ofNullable(execute(() -> redisTemplate.opsForZSet().rangeWithScores(key, start, end)));
    }

    /**
     * 获取有序集合中指定分数范围内的成员及其分数
     * @param key Redis中的key
     * @param min 最小分数
     * @param max 最大分数
     * @return Set<ZSetOperations.TypedTuple < Object>> 每个TypedTuple对象包含以下内容:value: 集合中的成员,score: 成员的分数。
     * @example
     * <pre>
     *     Set<ZSetOperations.TypedTuple<Object>> set = redisUtil.zRangeByScoreWithScores("myZSet", 0, 100).orElse(Collections.emptySet());
     *     set.forEach(tuple -> System.out.println(tuple.getValue() + ": " + tuple.getScore()));
     * </pre>
     */
    public Optional<Set<ZSetOperations.TypedTuple<Object>>> zRangeByScoreWithScores(String key, double min, double max) {
        return Optional.ofNullable(execute(() -> redisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max)));
    }

    /**
     * 获取有序集合中指定成员的分数范围排名
     * @param key Redis中的key
     * @param value 成员的值
     * @return 成员的分数排名
     * @example
     * <pre>
     *     long rank = redisUtil.zRevRank("myZSet", "value1").orElse(null);
     *     System.out.println(rank);
     * </pre>
     */
    public Optional<Long> zRevRank(String key, Object value) {
        return Optional.ofNullable(execute(() -> redisTemplate.opsForZSet().reverseRank(key, value)));
    }

    /**
     * 获取有序集合中指定分数范围内的元素数量
     * @param key Redis中的key
     * @param min 最小分数
     * @param max 最大分数
     * @return 元素数量
     * @example
     * <pre>
     *     long count = redisUtil.zCount("myZSet", 0, 100).orElse(0L);
     *     System.out.println(count);
     * </pre>
     */
    public Optional<Long> zCount(String key, double min, double max) {
        return Optional.ofNullable(execute(() -> redisTemplate.opsForZSet().count(key, min, max)));
    }

    /**
     * 移除有序集合中指定分数范围内的元素
     * @param key Redis中的key
     * @param min 最小分数
     * @param max 最大分数
     * @return 移除的元素数量
     * @example
     * <pre>
     *     long removed = redisUtil.zRemoveByScore("myZSet", 0, 100).orElse(0L);
     *     System.out.println(removed);
     * </pre>
     */
    public Optional<Long> zRemoveByScore(String key, double min, double max) {
        return Optional.ofNullable(execute(() -> redisTemplate.opsForZSet().removeRangeByScore(key, min, max)));
    }

    /**
     * 移除有序集合中指定排名范围内的元素
     * @param key Redis中的key
     * @param start 起始排名
     * @param end 结束排名
     * @return 移除的元素数量
     * @example
     * <pre>
     *     long removed = redisUtil.zRemoveByRank("myZSet", 0, 100).orElse(0L);
     *     System.out.println(removed);
     * </pre>
     */
    public Optional<Long> zRemoveByRank(String key, long start, long end) {
        return Optional.ofNullable(execute(() -> redisTemplate.opsForZSet().removeRange(key, start, end)));
    }

    private <T> T execute(RedisOperation<T> operation) {
        try {
            return operation.execute();
        } catch (Exception e) {
            logger.error("Redis operation error", e);
            return null;
        }
    }

    @FunctionalInterface
    private interface RedisOperation<T> {
        T execute();
    }
}
2.使用案例
    @RequestMapping("/testRedisUtil")
    public String testRedisUtil() {
        // 设置key-value
        redisUtil.set("testRedisUtil", "123456");
        // 获取key对应的value并指定转换的类型
        String res = redisUtil.get("testRedisUtil", String.class).orElse(null);
        if (res == null) {
            return "null";
        }
        return res;
    }

CleanShot 2024-07-20 at 11.08.21@2x


网站公告

今日签到

点亮在社区的每一天
去签到