zoukankan      html  css  js  c++  java
  • Redis分布式锁实现

    思路一:watch

    1).watch,监视键值对,作用时如果事务提交exec时发现监视的监视对发生变化,事务将被取消。

    2).multi,开启Redis的事务,置客户端为事务态。

    3).exec,提交事务,执行从multi到此命令前的命令队列,置客户端为非事务态。

    4).unwathc,释放锁

    public static void testWatch2(){  
       Jedis jedis = RedisCacheClient.getInstrance().getClient();  
       String watch = jedis.watch("testabcd2");  
       System.out.println(Thread.currentThread().getName()+"--"+watch);  
       Transaction multi = jedis.multi();  
       multi.set("testabcd", "125");  
       List<Object> exec = multi.exec();  
       System.out.println("--->>"+exec);  
    }

    思路二:setnx,getset

    1).setnx设置key的值为一个过期时间

    1.1)成功(返回1),获取锁成功

    1.2)失败(返回0),获取锁失败

    1.2.1)获取key的值,值不为空,且已过期,通过getset重新获取值,判断不为空,且已过期,则获得锁

    2).删除key

    public boolean tryLock() {
        long lockExpireTime = System.currentTimeMillis() + lockExpire + 1;//锁超时时间
        String stringOfLockExpireTime = String.valueOf(lockExpireTime);
        if (jedis.setnx(lockKey, stringOfLockExpireTime) == 1) {//获取到锁
            //设置相关标识
            locked = true;
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        String value = jedis.get(lockKey);
        if (value != null && isTimeExpired(value)) {//锁是过期的
            //假设多个线程(非单jvm)同时走到这里
            String oldValue = jedis.getSet(lockKey, stringOfLockExpireTime);//原子操作  
            // 但是走到这里时每个线程拿到的oldValue肯定不可能一样(因为getset是原子性的)  
            // 假如拿到的oldValue依然是expired的,那么就说明拿到锁了 
            if (oldValue != null && isTimeExpired(oldValue)) {//拿到锁
                //设置相关标识
                locked = true;
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
        }
        return false;
    }
    public void doUnlock() {
        jedis.del(lockKey);
    }
    package com.mallplus.common.redis.lock;
    
    import com.mallplus.common.lock.AbstractDistributedLock;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.RedisCallback;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.stereotype.Component;
    import org.springframework.util.StringUtils;
    import redis.clients.jedis.Jedis;
    import redis.clients.jedis.JedisCluster;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.UUID;
    
    /**
     * redis分布式锁实现
     *
     * @author mall
     * @date 2018/5/29 14:16
     */
    @Slf4j
    @Component
    public class RedisDistributedLock extends AbstractDistributedLock {
        @Autowired
        private RedisTemplate<String, Object> redisTemplate;
    
        private ThreadLocal<String> lockFlag = new ThreadLocal<>();
    
        private static final String UNLOCK_LUA;
    
        /*
         * 通过lua脚本释放锁,来达到释放锁的原子操作
         */
        static {
            UNLOCK_LUA = "if redis.call("get",KEYS[1]) == ARGV[1] " +
                    "then " +
                    "    return redis.call("del",KEYS[1]) " +
                    "else " +
                    "    return 0 " +
                    "end ";
        }
    
        public RedisDistributedLock(RedisTemplate<String, Object> redisTemplate) {
            super();
            this.redisTemplate = redisTemplate;
        }
    
        @Override
        public boolean lock(String key, long expire, int retryTimes, long sleepMillis) {
            boolean result = setRedis(key, expire);
            // 如果获取锁失败,按照传入的重试次数进行重试
            while ((!result) && retryTimes-- > 0) {
                try {
                    log.debug("get redisDistributeLock failed, retrying..." + retryTimes);
                    Thread.sleep(sleepMillis);
                } catch (InterruptedException e) {
                    log.warn("Interrupted!", e);
                    Thread.currentThread().interrupt();
                }
                result = setRedis(key, expire);
            }
            return result;
        }
    
        private boolean setRedis(final String key, final long expire) {
            try {
                String status = redisTemplate.execute((RedisCallback<String>) connection -> {
                    Object nativeConnection = connection.getNativeConnection();
                    String uuid = UUID.randomUUID().toString();
                    lockFlag.set(uuid);
                    String result = null;
                    // 集群模式和单机模式虽然执行脚本的方法一样,但是没有共同的接口,所以只能分开执行
                    // 集群模式
                    if (nativeConnection instanceof JedisCluster) {
                        result = ((JedisCluster) nativeConnection).set(key, uuid, "NX", "PX", expire);
                    }
                    // 单机模式
                    else if (nativeConnection instanceof Jedis) {
                        result = ((Jedis) nativeConnection).set(key, uuid, "NX", "PX", expire);
                    }
                    return result;
                });
                return !StringUtils.isEmpty(status);
            } catch (Exception e) {
                log.error("set redisDistributeLock occured an exception", e);
            }
            return false;
        }
    
        @Override
        public boolean releaseLock(String key) {
            // 释放锁的时候,有可能因为持锁之后方法执行时间大于锁的有效期,此时有可能已经被另外一个线程持有锁,所以不能直接删除
            try {
                final List<String> keys = new ArrayList<>();
                keys.add(key);
                final List<String> args = new ArrayList<>();
                args.add(lockFlag.get());
    
                // 使用lua脚本删除redis中匹配value的key,可以避免由于方法执行时间过长而redis锁自动过期失效的时候误删其他线程的锁
                // spring自带的执行脚本方法中,集群模式直接抛出不支持执行脚本的异常,所以只能拿到原redis的connection来执行脚本
    
                Long result = redisTemplate.execute((RedisCallback<Long>) connection -> {
                    Object nativeConnection = connection.getNativeConnection();
                    // 集群模式和单机模式虽然执行脚本的方法一样,但是没有共同的接口,所以只能分开执行
                    // 集群模式
                    if (nativeConnection instanceof JedisCluster) {
                        return (Long) ((JedisCluster) nativeConnection).eval(UNLOCK_LUA, keys, args);
                    }
    
                    // 单机模式
                    else if (nativeConnection instanceof Jedis) {
                        return (Long) ((Jedis) nativeConnection).eval(UNLOCK_LUA, keys, args);
                    }
                    return 0L;
                });
    
                return result != null && result > 0;
            } catch (Exception e) {
                log.error("release redisDistributeLock occured an exception", e);
            } finally {
                lockFlag.remove();
            }
            return false;
        }
    }

    有追求,才有动力!

    向每一个软件工程师致敬!

    by wujf

    mail:921252375@qq.com

  • 相关阅读:
    FastMM、FastCode、FastMove的使用(图文并茂)
    12种JavaScript MVC框架之比较
    十款最佳Node.js MVC框架
    Couchbase 服务器
    C#程序员阅读的书籍
    ORM的实现
    Linux内核策略介绍
    ASP.NET MVC + EF 利用存储过程读取大数据
    面向.Net程序员的dump分析
    动态加载与插件化
  • 原文地址:https://www.cnblogs.com/wujf/p/9213953.html
Copyright © 2011-2022 走看看