Redis搭配RedisTemplate实现分布式锁实战实例

前言:

研究加锁的初衷是:监听redis过期消息提醒,同一个数据(键)过期会有多次通知提醒。原因是:可能是由于 Redis 的主从复制或者分片集群等机制导致的。在主从复制或者分片集群中,可能会发生多个节点同时订阅了相同的键空间通知,从而导致同一个键空间事件被多次触发。
我的解决方法是:给键过期后提醒的回调函数加锁,收到多个通知提醒,回调函数加锁后最终只会有一个执行,其他没有获得锁的回调不会执行,这样就避免了重复执行任务代码。

一、使用redis命令

(1) 在加锁时就要给锁设置一个标识,进程要记住这个标识。当进程解锁的时候,要进行判断,是自己持有的锁才能释放,否则不能释放。可以为key 赋一个随机值,来充当进程的标识。
(2)解锁时要先判断、再释放,这两步需要保证原子性,否则第二步失败的话,就会出现死锁。而获取和删除命令不是原子的,这就需要采用Lua 脚本,通过 Lua 脚本将两个命令编排在一起,而整个Lua脚本的执行过程是原子的。
按照以上思路最终方案如下:

加锁

set key random-value nx ex seconds

解锁

if redis.call("get",KEYS[1]) == ARGV[1] then 
    return redis.call("del",KEYS[1]) 
else
    return 0 end

二、使用RedisTemplate实现

封装出自己的RedisLock类

/**
 * Redis 分布式锁
 *
 **/
@Component
public class RedisLockUtils {
 
    @Autowired
    private RedisTemplate redisTemplate;
 
    //分布式锁过期时间 s  可以根据业务自己调节
    private static final Long LOCK_REDIS_TIMEOUT = 10L;
    //分布式锁休眠 至 再次尝试获取 的等待时间 ms 可以根据业务自己调节
    public static final Long LOCK_REDIS_WAIT = 500L;
 
 
    /**
     *  加锁
     **/
    public Boolean getLock(String key,String value){
        Boolean lockStatus = this.redisTemplate.opsForValue().setIfAbsent(key,value, Duration.ofSeconds(LOCK_REDIS_TIMEOUT));
        return lockStatus;
    }
 
    /**
     *  释放锁
     **/
    public Long releaseLock(String key,String value){
        String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        RedisScript<Long> redisScript = new DefaultRedisScript<>(luaScript,Long.class);
        Long releaseStatus = (Long)this.redisTemplate.execute(redisScript, Collections.singletonList(key),value);
        return releaseStatus;
    }
}

调用例子

@Service
public class SysUserServiceImpl implements ISysUserService
{
    private static final Logger log = LoggerFactory.getLogger(SysUserServiceImpl.class);
 
    @Autowired
    private RedisLockUtils redisLockUtils;
 
    @Override
    public Boolean demo1(Double money) throws InterruptedException {
        String key = "test:key";
        String value = IdUtils.randomUUID();
        //redis尝试获取锁,加锁
        Boolean getLock = this.redisLockUtils.getLock(key,value);
        if(getLock){
            log.info("{}:成功获取[{}]锁",Thread.currentThread().getName(),key);
            //业务开始
            SysUser addMoenyPO = new SysUser();
            Long userId = SecurityUtils.getUserId();
            addMoenyPO.setUserId(userId);
            addMoenyPO.setMoney(money);
            this.userMapper.toUpMoney(addMoenyPO);
            //业务结束
            //释放分布式锁
            this.redisLockUtils.releaseLock(key,value);
            log.info("{}:释放[{}]锁",Thread.currentThread().getName(),key);
        }else{
            //线程休眠 然后尝试递归获取锁
            log.info("{}:尝试获取[{}]锁",Thread.currentThread().getName(),key);
            Thread.sleep(RedisLockUtils.LOCK_REDIS_WAIT);
            this.topUp(money);
        }
        return true;
    }
}

你可能感兴趣的:(redis,分布式,java)