Redis分布式锁超详细java实现

本文是Redis分布式锁实操,具体原理请参考:<未完善>

定义redisLock注解

在需要加锁的方法上使用@RedisLock注解,可以指定加锁的key和锁的过期时间.

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisLock {
    //指定加锁的key
    String key() default "";

    //锁的过期时间 单位:秒
    int timeout() default 60;

}

定义RedisPool

redis分布式锁实现的核心:使用lua命令保证setnx 和 过期时间这两个操作的原子性.


@Component
public class RedisPool {

    private static JedisPool jedisPool;

    @PostConstruct
    private void init(){
        //初始化jedis
        JedisPoolConfig config  = new JedisPoolConfig();
        config.setMaxTotal(200);
        config.setMaxIdle(8);
        config.setMaxWaitMillis(100000L);
        config.setTestOnBorrow(true);
        this.jedisPool = new JedisPool(config,"host",port,"password");
    }



    /**
     * 尝试获取分布式锁
     * @param lockKey 锁
     * @param value 请求标识
     * @param expireTime 超期时间
     * @return 是否获取成功
     */
    public static boolean tryGetDistributedLock(String lockKey,String value,  int expireTime) {
        Jedis jedis = jedisPool.getResource();
        //没有锁(key),加锁并设置有效期  有锁不做任何操作.
        String script = "if redis.call('setNx',KEYS[1],ARGV[1]) == 1 then if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('expire',KEYS[1],ARGV[2]) else return 0 end else return 0 end";
        List<String> strings = new ArrayList<String>();
        strings.add(value);
        strings.add(expireTime+"");
        Object result = jedis.eval(script, Collections.singletonList(lockKey), strings);
        if (RELEASE_SUCCESS.equals(result)) {
            return true;
        }
        return false;

    }


    private static final Long RELEASE_SUCCESS = 1L;

    /**
     * 释放分布式锁
     * @param lockKey 锁
     * @param requestId 请求标识
     * @return 是否释放成功
     *
     * 问题一:不是我加的锁,但是我能获取到key就能释放   解决:添加业务id或者线程id
     * 问题二:业务执行时间大于锁的过期时间  解决:redisson-watch dog  ->待解决
     *          核心原理:另起一个线程,查询对应key是否还存在,存在就延长过期时间(原来过期时间的一半)
     */
    public static boolean releaseDistributedLock(String lockKey, String requestId) {
        Jedis jedis = jedisPool.getResource();
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));

        if (RELEASE_SUCCESS.equals(result)) {
            return true;
        }
        return false;

    }

}

定义AOP切面


@Component
@Aspect
public class RedisLockAop {


    @Pointcut("@annotation(redisLock)")
    public void setRedisLock(RedisLock redisLock) {
    }

    @Before("setRedisLock(redisLock)")
    public void doBefore(JoinPoint joinPoint, RedisLock redisLock) {
        String value = "";
        //获取方法名称
        String methodName = joinPoint.getSignature().getName();
        //获取类名称
        String className = joinPoint.getSignature().getDeclaringTypeName();
        //获取参数值
        Object[] args = joinPoint.getArgs();
        //获取加锁的key
        String key = redisLock.key();
        long threadId = Thread.currentThread().getId();
        if (ObjectUtils.isEmpty(args)) {
            //方法参数为空,不进行加锁操作
            //key = methodName + UUID.randomUUID().toString();
            return;
        } else {
            //对象属性
            Map<String, Object> map = getKeyAndValue(args[0], key);
            key = StringUtils.isEmpty(key) ? "id" : key;
            Object o = map.get(key);
            if (ObjectUtils.isEmpty(o)) {
                System.out.println("被锁的值为空");
                return;
            } else {
                key = o.toString();
            }
            value = key;
            key = threadId + "--" + key;
            System.out.println("将要被锁的键是:" + key + "====将要被锁的值是:" + value);
        }
        boolean result = RedisPool.tryGetDistributedLock(key, value, redisLock.timeout());
        if (result)
            System.out.println("key:" + key +  ":加锁成功");
        else
            System.out.println("key:" + key +  ":加锁失败");
    }

    @After("setRedisLock(redisLock)")
    public void doAfter(JoinPoint joinPoint, RedisLock redisLock) {
        String value = "";
        //获取参数值
        Object[] args = joinPoint.getArgs();
        //获取加锁的key
        String key = redisLock.key();
        long threadId = Thread.currentThread().getId();
        if (ObjectUtils.isEmpty(args)) {
            //方法参数为空,不进行解锁操作
            //key = methodName + UUID.randomUUID().toString();
            return;
        } else {
            //对象属性
            Map<String, Object> map = getKeyAndValue(args[0], key);
            key = StringUtils.isEmpty(key) ? "id" : key;
            Object o = map.get(key);
            if (ObjectUtils.isEmpty(o)) {
                System.out.println("将要解锁的值为空");
                return;
            } else {
                key = o.toString();
            }
            value = key;
            key = threadId + "--" + key;
            System.out.println("将要解锁的键是:" + key + "======" + "将要解锁的值是:" + value);
        }
        boolean b = RedisPool.releaseDistributedLock( key, value);
        if (b)
            System.out.println("key:" + key + "解锁成功!");
        else
            System.out.println("key:" + key + "解锁失败!");
    }


    public static Map<String, Object> getKeyAndValue(Object obj, String key) {
        Map<String, Object> map = new HashMap<String, Object>();
        Class<?> aClass = obj.getClass();
        //加锁入参是基本类型或者其包装类
        if (aClass.isPrimitive() || isWrapClass(aClass)) {
            map.put(StringUtils.isEmpty(key) ? "id" : key, obj);
        } else {
            //获取参数名
            Field[] declaredFields = aClass.getDeclaredFields();
            for (int i = 0; i < declaredFields.length; i++) {
                Field f = declaredFields[i];
                //反射的对象在使用时 取消java语言访问检查
                f.setAccessible(true);
                try {
                    Object val = f.get(obj);
                    map.put(f.getName(), val);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
        return map;
    }

    /**
     * 判断是否是基础数据类型的包装类型
     *
     * @param clz
     * @return
     */
    public static boolean isWrapClass(Class clz) {
        try {
            return ((Class) clz.getField("TYPE").get(null)).isPrimitive();
        } catch (Exception e) {
            return false;
        }
    }

}

具体使用

在service的方法上加上@RedisLock注解,可以指定加锁的key和锁的过期时间.也可以使用默认的参数.


@Service
public class RedisLockService {

    @RedisLock(key = "userId",timeout = 50)
    public String testRedisLock(User user) {
        return user.getName();
    }

    @RedisLock()
    public String testRedisLock1(User user) {
        return user.getName();
    }

    @RedisLock(timeout = 50)
    public String testRedisLockAboutStr(Integer userId) {
        return userId + "";
    }
}

版权声明:本文为wangzhenpeng233原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。