采用 redis 设计分布式锁 (二)

上一篇我们介绍了一种redis 分布式锁,介绍了其应用场景和一些存在的问题,而今天我们介绍 redis 第二种分布式锁的使用;

这种锁同时采用lua脚本保证原子性能很好解决以上问题。实例如下:

1、代码如下:

package com.jy.utils;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCommands;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * RedisTemplate工具类
 */
@Component
public class RedisUtil implements ApplicationContextAware {

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

    private static RedisTemplate<Object, Object> redisTemplate;

    private static ThreadLocal<String> lockFlag = new ThreadLocal<String>();

    public static final String UNLOCK_LUA;

    static {
        StringBuilder sb = new StringBuilder();
        sb.append("if redis.call('get',KEYS[1]) == ARGV[1] ");
        sb.append("then ");
        sb.append("    return redis.call('del',KEYS[1]) ");
        sb.append("else ");
        sb.append("    return 0 ");
        sb.append("end ");
        UNLOCK_LUA = sb.toString();
    }

    //加锁核心方法
    public static boolean setRedisLock(String key, long expire) {
        try {
            String result = redisTemplate.execute(new RedisCallback<String>() {
                @Override
                public String doInRedis(RedisConnection connection) throws DataAccessException {
                    JedisCommands commands = (JedisCommands) connection.getNativeConnection();
                    String uuid = UUID.randomUUID().toString();
                    lockFlag.set(uuid);//给该线程设置唯一标识,释放所得时候会用到
                    return commands.set(key, uuid, "NX", "PX", expire);//锁和超时时间同时设置,保证改锁的原子性
                }
            });
            logger.info("获取锁成功[{}]结果:[{}]",key,result);
            return StringUtils.isNotEmpty(result);
        } catch (Exception e) {
            logger.error("set redis occured an exception", e);
        }
        return false;
    }

     //释放锁的核心方法
    public static boolean releaseRedisLock(String key) {
        /** 释放锁的时候,有可能因为持锁之后方法执行时间大于锁的有效期,
         *此时有可能已经被另外一个线程持有锁,所以不能直接删除,需要判断该线程的value 值(加锁时的uuid 值),
         */
        try {
            List<String> keys = new ArrayList<String>();
            keys.add(key);
            List<String> args = new ArrayList<String>();
            args.add(lockFlag.get());//设置需要删除的线程的标识value值(每个线程精准删除自己设置的锁)

            /** 使用lua脚本删除redis中匹配value的key,
               * 可以避免由于方法执行时间过长而redis锁自动过期失效的时候误删其他线程的锁
               * 从而避免锁的错乱
             */
            Long result = redisTemplate.execute(new RedisCallback<Long>() {
                public Long doInRedis(RedisConnection connection) throws DataAccessException {
                    Object nativeConnection = connection.getNativeConnection();
                    /** 单机模式 */
                    return (Long) ((Jedis) nativeConnection).eval(UNLOCK_LUA, keys, args);
                }
            });
            return result != null && result > 0;
        } catch (Exception e) {
            logger.error("release lock occured an exception", e);
        }
        return false;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        redisTemplate = applicationContext.getBean(RedisTemplate.class);
    }

}

2、这种redis 分布式锁,支持多线程、安全性更好,可以试一下。下篇我们再介绍一种redis 分布式锁,对比一下,根据不同场景选择合适的分布式锁,敬请期待!

猜你喜欢

转载自blog.csdn.net/nandao158/article/details/105866418