springboot2.x redis lettuce实现分布式锁

3,921 阅读2分钟
我查了很多redis分布式锁都是通过jedisclient来实现的,因为springboot2.x默认连接客户端是lettuce,所以引用一个大佬用jedis写的锁修改下。


直接贴代码

1.接口类

public interface DistributedLock {
	
	public static final long TIMEOUT_MILLIS = 30000;
	
	public static final int RETRY_TIMES = 10;
	
	public static final long SLEEP_MILLIS = 500;
	
	public boolean lock(String key);
	
	public boolean lock(String key, int retryTimes);
	
	public boolean lock(String key, int retryTimes, long sleepMillis);
	
	public boolean lock(String key, long expire);
	
	public boolean lock(String key, long expire, int retryTimes);
	
	public boolean lock(String key, long expire, int retryTimes, long sleepMillis);
	
	public boolean releaseLock(String key);
}

2.依然是接口

public abstract class AbstractDistributedLock implements DistributedLock {

	@Override
	public boolean lock(String key) {
		return lock(key , TIMEOUT_MILLIS, RETRY_TIMES, SLEEP_MILLIS);
	}

	@Override
	public boolean lock(String key, int retryTimes) {
		return lock(key, TIMEOUT_MILLIS, retryTimes, SLEEP_MILLIS);
	}

	@Override
	public boolean lock(String key, int retryTimes, long sleepMillis) {
		return lock(key, TIMEOUT_MILLIS, retryTimes, sleepMillis);
	}

	@Override
	public boolean lock(String key, long expire) {
		return lock(key, expire, RETRY_TIMES, SLEEP_MILLIS);
	}

	@Override
	public boolean lock(String key, long expire, int retryTimes) {
		return lock(key, expire, retryTimes, SLEEP_MILLIS);
	}

}

3.实现类

@Component
public class RedisDistributedLock extends AbstractDistributedLock {
	
	@Autowired
	@Resource(name="redisTemplateMaster")
	private RedisTemplate<Object,Object> redisTemplate;
	
	private final Logger logger = LoggerFactory.getLogger(RedisDistributedLock.class);
	
	//private RedisTemplate<Object, Object> redisTemplate;
	
	private 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 RedisDistributedLock() {
		super();
	}

//	public RedisDistributedLock(RedisTemplate<Object, 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 {
				logger.debug("lock failed, retrying..." + retryTimes);
				Thread.sleep(sleepMillis);
			} catch (InterruptedException e) {
				return false;
			}
			result = setRedis(key , expire);
		}
		return result;
	}
	
	private boolean setRedis(final String key, final long expire ) {
		try{
			
            RedisCallback<Boolean> callback = (connection) -> {
            	String uuid = UUID.randomUUID().toString();
            	lockFlag.set(uuid);
                return connection.set(key.getBytes(Charset.forName("UTF-8")), uuid.getBytes(Charset.forName("UTF-8")), Expiration.milliseconds(expire), RedisStringCommands.SetOption.SET_IF_ABSENT);
            };
            return (Boolean)redisTemplate.execute(callback);
        } catch (Exception e) {
        	logger.error("redis lock error.", e);
        }
		return false;
	}
	
	
	@Override
	public boolean releaseLock(String key) {
		// 释放锁的时候,有可能因为持锁之后方法执行时间大于锁的有效期,此时有可能已经被另外一个线程持有锁,所以不能直接删除
		try {
			 RedisCallback<Boolean> callback = (connection) -> {
			    String value = lockFlag.get();
	            return connection.eval(UNLOCK_LUA.getBytes(), ReturnType.BOOLEAN ,1, key.getBytes(Charset.forName("UTF-8")), value.getBytes(Charset.forName("UTF-8")));
	        };
		     return (Boolean)redisTemplate.execute(callback);
		} catch (Exception e) {
			logger.error("release lock occured an exception", e);
		} finally {
			// 清除掉ThreadLocal中的数据,避免内存溢出
			lockFlag.remove();
		}
		return false;
	}
	
}