redis分布式锁的设计

redis分布式锁的设计

今天我们来说一说基于redis分布式锁的设计(基于springboot框架下的实现):
1、首先我们设计一个接口:

/**
 * 分布式锁
 * 
 * @author fengjie song
 *
 */
public interface DistributedLock {

	/**
	 * 分布式锁1
	 * 
	 * @param key
	 * @return
	 */
	boolean lock(String key);

	/**
	 * 分布式锁2
	 * 
	 * @param key
	 * @param expire
	 * @return
	 */
	boolean lock(String key, long expire);

	/**
	 * 分布式锁3
	 * 
	 * @param key
	 * @param expire
	 * @param retryTime
	 * @return
	 */
	boolean lock(String key, long expire, int retryTime);

	/**
	 * 分布式锁4
	 * 
	 * @param key         redis key
	 * @param expire      key的实效时间
	 * @param retryTime   获取锁的重试次数
	 * @param sleepMillis 睡眠时间(毫秒)
	 * @return
	 */
	boolean lock(String key, long expire, int retryTime, long sleepMillis);

	/**
	 * 释放锁
	 * 
	 * @param key
	 * @return
	 */
	boolean releaseLock(String key);
}

2、抽象类实现部分非核心的代码

/**
 * 模板设计模式</br>
 * 模板设计模式在这里可以有效避免方法多次实现,</br>
 * 例如增加基于zookeeper的分布式锁,我们只需要重写核心的方法即可
 * 
 * @author fengjie song
 *
 */
public abstract class AbstractDistributedLock implements DistributedLock {

	private static final long EXPIRE = 60;

	private static final int RETRY_TIME = 3;

	private static final long SLEEP_MILLIS = 1000;

	@Override
	public boolean lock(String key) {
		return lock(key, EXPIRE, RETRY_TIME, SLEEP_MILLIS);
	}

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

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

3、分布式锁实现类(基于redis),这里也可以设计基于zk等其他的分布式锁

/**
 * redis分布式锁的实现(策略模式)</br>
 * 这里只需要重写一个方法,其余方法在抽象类里面调用(模板设计模式)
 * 
 * @author fengjie song
 *
 */
public class RedisDistributedLock extends AbstractDistributedLock {

	/**
	 * 引入redisTemplate
	 */
	private RedisTemplate<String, String> redisTemplate;

	/**
	 * 记录redis锁的标识
	 */
	private ThreadLocal<String> lockFlag = new ThreadLocal<String>();

	/**
	 * 释放锁的LUA脚本
	 */
	private static final String UNLOCK_LUA;

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

	public RedisDistributedLock(RedisTemplate<String, String> redisTemplate) {
		super();
		this.redisTemplate = redisTemplate;
	}

	@Override
	public boolean lock(String key, long expire, int retryTime, long sleepMillis) {
		// 设置锁
		boolean result = setRedis(key, expire);
		// 如果设置失败,根据设定的重试次数进行重试
		while (!result && retryTime-- > 0) {
			try {
				Thread.sleep(sleepMillis);
			} catch (InterruptedException e) {
				return false;
			}
			result = setRedis(key, expire);
		}
		return result;
	}

	private boolean setRedis(String key, long expire) {

		String result = redisTemplate.execute(new RedisCallback<String>() {
			@Override
			public String doInRedis(RedisConnection connection) throws DataAccessException {
				// 这里我们不知道redis的连接方式是单机、读写分离的主从还是集群,所以在这里返回他们的父级接口JedisCommands
				JedisCommands rc = (JedisCommands) connection.getNativeConnection();
				String id = UUID.randomUUID().toString();
				lockFlag.set(id);
				return rc.set(key, id, "NX", "PX", expire);
			}
		});

		return !StringUtils.isEmpty(result);
	}

	@Override
	public boolean releaseLock(String key) {
		try {
			// 释放锁的时候,可能当前锁已经失效,对应的key锁可能已经被别的线程持有,所以不能直接删除锁,这里我们采用LUA脚本进行柔和删除
			List<String> keys = new ArrayList<String>();
			keys.add(key);
			List<String> args = new ArrayList<String>();
			args.add(lockFlag.get());
			// 利用LUA脚本进行匹配key-value进行删除,可以避免因锁失效,而误删别的线程已经持有的锁
			Long result = redisTemplate.execute(new RedisCallback<Long>() {

				@Override
				public Long doInRedis(RedisConnection connection) throws DataAccessException {
					Object conn = connection.getNativeConnection();
					// 虽然redis集群模式和单机模式有一样的接口,但是在这里无法确定是单机还是集群所以需要分开执行
					if (conn instanceof Jedis) {
						return (Long) ((Jedis) conn).eval(UNLOCK_LUA, keys, args);
					}
					if (conn instanceof JedisCluster) {
						return (Long) ((JedisCluster) conn).eval(UNLOCK_LUA, keys, args);
					}
					return 0L;
				}
			});
			return result != null && result > 0;
		} finally {
			// 清除过期或者已经删除的锁标识,避免内存溢出
			lockFlag.remove();
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值