基于redis分布式锁的实现

一、实现图

二、实现原理

        当A、B两个线程同时想要操作资源key时,两者只有一个能够成功获取到资源Key。假如A线程获取到了资源key,A线程就会对资源进行锁定并对资源进行操作,在操作的同时开启一个守护线程,定时监听业务执行情况,若A线程锁即将过期但业务并未执行完,守护线程会对锁进行延时。直到A线程执行完业务释放锁资源。
        未抢到锁资源的B线程会尝试重新获取资源key,只有当A线程释放锁资源之后,B线程才能抢到资源Key并对其进行操作。

三、注意事项

  针对锁的过期时间设置需要根据业务进行特别处理。设置过程与过短都会对服务器资源造成不必要的消耗,建议根据实际业务处理时间设置超出1/3的时间。最短不建议低于10秒。

四、代码实现

4.1:依赖

            <dependency>
                <groupId>org.redisson</groupId>
                <artifactId>redisson</artifactId>
                <version>3.19.0</version>
            </dependency>

            <dependency>
                <groupId>redis.clients</groupId>
                <artifactId>jedis</artifactId>
                <version>3.6.0</version>
            </dependency>

            <dependency>
                <groupId>org.junit.platform</groupId>
                <artifactId>junit-platform-commons</artifactId>
                <version>1.3.2</version>
            </dependency>

4.2:工具类

RedisService

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * Description   :redis工具类
 * Date          :2023-03-17 16:13:55
 * Author        :FM
 */
@SuppressWarnings(value = {"unchecked", "rawtypes"})
@Component
public class RedisService
{
    @Autowired
    public RedisTemplate redisTemplate;

    /**
     * 缓存基本的对象,Integer、String、实体类等
     *
     * @param key   缓存的键值
     * @param value 缓存的值
     */
    public <T> void setCacheObject(final String key, final T value)
    {
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * 基于setNx实现的set方法
     *
     * @param key   缓存的键值
     * @param value 缓存的值
     * @return true=设置成功;false=设置失败
     */
    public boolean setIfAbsent(final String key, final String value)
    {
        return redisTemplate.opsForValue().setIfAbsent(key, value);
    }

    /**
     * 缓存基本的对象,Integer、String、实体类等
     *
     * @param key      缓存的键值
     * @param value    缓存的值
     * @param timeout  时间
     * @param timeUnit 时间颗粒度
     */
    public <T> void setCacheObject(final String key, final T value, final Long timeout, final TimeUnit timeUnit)
    {
        redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
    }

    /**
     * 设置有效时间
     *
     * @param key     Redis键
     * @param timeout 超时时间
     * @return true=设置成功;false=设置失败
     */
    public boolean expire(final String key, final long timeout)
    {
        return expire(key, timeout, TimeUnit.SECONDS);
    }

    /**
     * 设置有效时间
     *
     * @param key     Redis键
     * @param timeout 超时时间
     * @param unit    时间单位
     * @return true=设置成功;false=设置失败
     */
    public boolean expire(final String key, final long timeout, final TimeUnit unit)
    {
        return redisTemplate.expire(key, timeout, unit);
    }

    /**
     * 获取有效时间
     *
     * @param key Redis键
     * @return 有效时间
     */
    public long getExpire(final String key)
    {
        return redisTemplate.getExpire(key);
    }

    /**
     * 判断 key是否存在
     *
     * @param key 键
     * @return true 存在 false不存在
     */
    public Boolean hasKey(String key)
    {
        return redisTemplate.hasKey(key);
    }

    /**
     * 获得缓存的基本对象。
     *
     * @param key 缓存键值
     * @return 缓存键值对应的数据
     */
    public <T> T getCacheObject(final String key)
    {
        ValueOperations<String, T> operation = redisTemplate.opsForValue();
        return operation.get(key);
    }

    /**
     * 删除单个对象
     *
     * @param key
     */
    public boolean deleteObject(final String key)
    {
        return redisTemplate.delete(key);
    }

    /**
     * 删除集合对象
     *
     * @param collection 多个对象
     * @return
     */
    public boolean deleteObject(final Collection collection)
    {
        return redisTemplate.delete(collection) > 0;
    }

    /**
     * 缓存List数据
     *
     * @param key      缓存的键值
     * @param dataList 待缓存的List数据
     * @return 缓存的对象
     */
    public <T> long setCacheList(final String key, final List<T> dataList)
    {
        Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
        return count == null ? 0 : count;
    }

    /**
     * 获得缓存的list对象
     *
     * @param key 缓存的键值
     * @return 缓存键值对应的数据
     */
    public <T> List<T> getCacheList(final String key)
    {
        return redisTemplate.opsForList().range(key, 0, -1);
    }

    /**
     * 缓存Set
     *
     * @param key     缓存键值
     * @param dataSet 缓存的数据
     * @return 缓存数据的对象
     */
    public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet)
    {
        BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
        Iterator<T> it = dataSet.iterator();
        while (it.hasNext())
        {
            setOperation.add(it.next());
        }
        return setOperation;
    }

    /**
     * 获得缓存的set
     *
     * @param key
     * @return
     */
    public <T> Set<T> getCacheSet(final String key)
    {
        return redisTemplate.opsForSet().members(key);
    }

    /**
     * 缓存Map
     *
     * @param key
     * @param dataMap
     */
    public <T> void setCacheMap(final String key, final Map<String, T> dataMap)
    {
        if (dataMap != null)
        {
            redisTemplate.opsForHash().putAll(key, dataMap);
        }
    }

    /**
     * 获得缓存的Map
     *
     * @param key
     * @return
     */
    public <T> Map<String, T> getCacheMap(final String key)
    {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * 往Hash中存入数据
     *
     * @param key   Redis键
     * @param hKey  Hash键
     * @param value 值
     */
    public <T> void setCacheMapValue(final String key, final String hKey, final T value)
    {
        redisTemplate.opsForHash().put(key, hKey, value);
    }

    /**
     * 获取Hash中的数据
     *
     * @param key  Redis键
     * @param hKey Hash键
     * @return Hash中的对象
     */
    public <T> T getCacheMapValue(final String key, final String hKey)
    {
        HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
        return opsForHash.get(key, hKey);
    }

    /**
     * 获取多个Hash中的数据
     *
     * @param key   Redis键
     * @param hKeys Hash键集合
     * @return Hash对象集合
     */
    public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys)
    {
        return redisTemplate.opsForHash().multiGet(key, hKeys);
    }

    /**
     * 删除Hash中的某条数据
     *
     * @param key  Redis键
     * @param hKey Hash键
     * @return 是否成功
     */
    public boolean deleteCacheMapValue(final String key, final String hKey)
    {
        return redisTemplate.opsForHash().delete(key, hKey) > 0;
    }

    /**
     * 获得缓存的基本对象列表
     *
     * @param pattern 字符串前缀
     * @return 对象列表
     */
    public Collection<String> keys(final String pattern)
    {
        return redisTemplate.keys(pattern);
    }
}


RedisLockGuard

/**
 * Description   :Redis锁延时守护线程
 * Date          :2024/1/10
 * Author        :FM
 */
@Slf4j
public class RedisLockGuard implements Runnable{

    private String key;
    private int expireTime;
    private boolean isRunning;
    private RedisLock redisLock;


    public RedisLockGuard(String key,int expireTime,RedisLock redisLock) {
        this.expireTime = expireTime;
        this.isRunning = Boolean.TRUE;
        this.redisLock = redisLock;
        this.key = key;
    }

    @Override
    public void run() {
        long nano = System.nanoTime();
        long timeout = 3 * expireTime * 1000 * 1000 * 1000L;//最多持续锁超时时间的3倍,expireTime时间单位是秒,转化成微秒
        long waitTime = expireTime * 1000 * 2 / 3;// 线程等待多长时间后执行,expireTime时间单位是秒,转化成毫秒
        while (isRunning) {
            if((System.nanoTime()-nano)>=timeout){
                this.stop();
                log.error("RedisLock延时守护已超时:{}",key);
                continue;
            }
            try {
                Thread.sleep(waitTime);
                if (redisLock.lockDelay(expireTime)) {
                    log.info("RedisLock延时守护延时成功:{}",key);
                } else {
                    log.info("RedisLock延时守护已关闭:{}",key);
                    this.stop();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    private void stop() {
        this.isRunning = Boolean.FALSE;
    }

}

RedisLock

import com.ruoyi.common.core.utils.SpringUtils;
import lombok.extern.slf4j.Slf4j;

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

/**
 * Description   :Redis锁
 * Date          :2024/1/10 
 * Author        :FM
 */
@Slf4j
public class RedisLock {

    private RedisService redisService= SpringUtils.getBean(RedisService.class);

    /** 锁前缀*/
    private static final String LOCK_PREFIX = "redis:Lock:";
    /** 加锁标志 */
    private String lockedFlag;
    /** 毫秒与毫微秒的换算单位 1毫秒=1000000毫微秒. */
    private static final long MILLI_NANO_CONVERSION = 1000*1000L;
    /** 默认超时时间(毫秒). */
    private static final long DEFAULT_TIME_OUT = 10*1000;
    private static Random RANDOM = new Random();
    /** 锁的超时时间(秒),过期删除. */
    private static final int EXPIRE = 10;
    /**锁键**/
    private String key;
    /** 锁状态标志. */
    private boolean locked = false;

    /**
     * 获取锁对象
     * @param key
     * @return
     */
    public static RedisLock getRedisLock(String key){
        return new RedisLock(key, UUID.randomUUID().toString());
    }

    /**
     * 构造函数.
     * @param key key
     */
    private RedisLock(String key,String lockedFlag) {
        this.key = LOCK_PREFIX+key;
        this.lockedFlag=lockedFlag;
    }

    /**
     * 获取锁
     * @return true:成功;false:失败
     */
    public boolean lock() {
        return lock(DEFAULT_TIME_OUT);
    }

    /**
     * 释放锁
     * 无论加锁是否成功,都需要调用该方法进行释放锁
     */
    public void unlock() {
        if (locked) {
            //判断锁lockedFlag是否一致,防止释放同key的其他锁
            String value = redisService.getCacheObject(key);
            if(lockedFlag.equals(value)) redisService.deleteObject(key);
            locked=false;
        }
        log.info("RedisLock解锁:{}", key);
    }

    /**
     * 加锁
     * @param timeout 获取锁自旋等待时间(毫秒)
     * @return true:成功;false:失败
     */
    public boolean lock(long timeout) {
        return lock(timeout, EXPIRE);
    }

    /**
     * 加锁.
     * @param timeout 获取锁自旋等待时间(毫秒)
     * @param expire 锁的超时时间(秒),过期删除
     * @return true:成功;false:失败
     */
    public boolean lock(long timeout, int expire) {
        long nano = System.nanoTime();
        timeout *= MILLI_NANO_CONVERSION;
        try {
            if(timeout>0){
                while ((System.nanoTime()-nano)<timeout) {
                    if (redisService.setIfAbsent(key,lockedFlag)) {
                        if(expire>0){
                            redisService.expire(key, expire, TimeUnit.SECONDS);
                        }
                        locked = true;
                        log.info("RedisLock锁定:{},线程{}", key,Thread.currentThread().getName());
                        //开启守护线程
                        startGuard(expire);
                        return locked;
                    }
                    // 短暂休眠,避免出现死锁
                    Thread.sleep(3, RANDOM.nextInt(500));
                    log.info("RedisLock等待资源:{},线程{}", key,Thread.currentThread().getName());
                }
            }else{
                if (redisService.setIfAbsent(key,lockedFlag)) {
                    if(expire>0){
                        redisService.expire(key, expire, TimeUnit.SECONDS);
                    }
                    locked = true;
                    //开启守护线程
                    startGuard(expire);
                    return locked;
                }
            }
        } catch (Exception e) {
            throw new RuntimeException("Locking error", e);
        }
        return false;
    }

    //开启守护线程
    private void startGuard(int expire){
        RedisLockGuard redisLockGuard = new RedisLockGuard(key, expire, this);
        Thread thread = new Thread(redisLockGuard);
        //设置为守护线程,所服务的用户线程不存在时自动无效
        thread.setDaemon(Boolean.TRUE);
        thread.start();
    }

    /**
     * 锁延时
     * @param expireTime
     * @return
     */
    public Boolean lockDelay(int expireTime) {
        if(locked){
            //判断锁lockedFlag是否一致
            String value = redisService.getCacheObject(key);
            if(lockedFlag.equals(value)) {
                redisService.redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
                return true;
            }
        }
        return false;
    }

}

4.3:业务实现(服务层)

    @Autowired
    private RedisTemplate redisTemplate;

    /*** redis分布式锁业务实现
     * @Author FM
     * @Date 2024/1/10
     * @param id
     **/
    @Override
    @Transactional
    public Result updateTest(String id) {
        log.info("分布式抢锁:===>【{}】",id);
        //资源key
        String key =  "redis:key:"+id;
        //生成唯一标识
        String uuid = id + Thread.currentThread().getId() ;

        while (true){
            log.info("抢锁线程信息:===>【{}】",uuid);
            RedisLock redisLock = RedisLock.getRedisLock(uuid);
            boolean locked = redisLock.lock();
            log.info("使用当前线程【{}】给redis上锁:===>【{}】",uuid,locked);
            int flag = 0;
            if(locked){
                try {
                    flag = 0;
                    log.info("检查单个资源锁信息:===>【{}】",key);
                    Boolean lockKey = redisTemplate.hasKey(key);
                    log.info("检查单个资源锁是否存在:===>【{}】",lockKey);
                    if(lockKey){
                        //释放自己的锁,然后跳过此次循环
                        log.info("单个资源被其他线程占用,释放锁资源:===>【{}】",uuid);
                        redisLock.unlock();
                        flag = 1;
                        continue;
                    }
                    //以下是测试业务
                    //测试添加针对该条单个资源的成功消息,根据业务选择释放不释放
                    redisTemplate.opsForValue().set(key, "exist",2, TimeUnit.MINUTES);
                    //测试修改状态
                    int i = mapper.updateSQL(id);
                    Long beginTime = System.currentTimeMillis();
                    Long a = 1L;
                    //模拟业务执行超过过期时间
                    while (true){
                        Long endTime = System.currentTimeMillis();
                        a = endTime - beginTime;
                        if(a/20000>=1){
                            break;
                        }
                    }
                    if(i != 1){
                        throw new Exception("修改数据库条数大于1");
                    }
                    if(flag == 0){
                        break;
                    }
                } catch (Exception e){
                    log.info("业务报错,事务回滚:===>【{}】",key);
                    log.info(e.getMessage());
                    TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
                } finally {
                    log.info("业务执行完成,释放整个资源:===>【{}】 ",uuid);
                    redisLock.unlock();
                    if(flag == 0){
                        log.info("业务执行完成,释放单个资源:===>【{}】",key);
                        redisTemplate.delete(key);
                    }
                }
            }
        }
        return null;
    }

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值