分布式锁 - 手写单机redis分布式锁

1 篇文章 0 订阅
1 篇文章 0 订阅

需要注意的点:

  1. 抢占:redis setnx,只有一个线程可以成功,成功即意味着抢锁成功
  2. 防死锁:设置过期时间 expire,并且可重入(ReentrantLock),同个线程每次加锁state加1,解锁state减1,当为0的时候释放锁
  3. 防止误删:设置唯一标识 (uuid+线程id),在删除前进行判断是否是自己的锁
  4. 原子性: lua脚本,reids EVAL 或者redisTemplate.execute()
  5. 自动续期:Timer定时器

示例及详细解释如下代码:

package self.jason.distribute.lock;

import java.util.Arrays;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;

public class MyDistributedRedisLock implements Lock {
	private StringRedisTemplate redisTemplate;

	private String lockName;
	private String lockId;
	private long expire = 30;

	// 使用工厂模式来创建该对象实例,所以没有在本类上添加@Component和@Autowired注解
	public MyDistributedRedisLock(StringRedisTemplate redisTemplate, String lockName, String uuid) {
		this.redisTemplate = redisTemplate;
		this.lockName = lockName;
		// lockId使用uuid和当前线程id进行拼接,以确保排他(uuid)和可重入(thread id)
		this.lockId = uuid + ":" + Thread.currentThread().getId();
	}

	@Override
	public void lock() {
		tryLock();

	}

	@Override
	public void lockInterruptibly() throws InterruptedException {
		// TODO Auto-generated method stub

	}

	@Override
	public boolean tryLock() {
		try {
			return tryLock(-1, TimeUnit.SECONDS);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return false;
	}

	@Override
	public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
		if (time != -1) {
			expire = unit.toSeconds(time);
		}
		// lua脚本进行判断,如果当前没有锁或者是自己线程对应的锁(重入),则将锁的value加1(类似ReentantLock中的state加1),并设置(或重置)过期时间
		// 否则返回0,利用while循环进行重试
		String script = "if redis.call('exists',KEYS[1])==0 or redis.call('hexists',KEYS[1],ARGV[1])==1 " //
				+ "then " //
				+ "     redis.call('hincrby',KEYS[1],ARGV[1],1) " //
				+ "     redis.call('expire',KEYS[1],ARGV[2]) " //
				+ "     return 1 " //
				+ "else " //
				+ "     return 0 " //
				+ "end";
		while (!redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), lockId,
				String.valueOf(expire))) {
			// sleep一定时间以防止频繁争抢锁
			Thread.sleep(50);
		}
		// 给锁续期
		renewLock();
		return true;
	}

	@Override
	public void unlock() {
		// lua脚本进行解锁:
		// 如果锁不存在或者不是自己的锁,则抛异常
		// 否则对锁的value减1(类似state减1),当为0时表示应该释放锁,则通过删除来进行释放
		// 否则直接返回即可(比如可重入退出的情况)
		String script = "if redis.call('hexists',KEYS[1],ARGV[1])==0 " //
				+ "then " //
				+ "     return nil " //
				+ "elseif redis.call('hincrby',KEYS[1],ARGV[1],-1)==0 " //
				+ "then " //
				+ "     return redis.call('del',KEYS[1]) " //
				+ "else " //
				+ "     return 0 " //
				+ "end";
		Long flag = redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockName),
				lockId);
		if (flag == null) {
			throw new IllegalMonitorStateException("try to release others' lock");
		}

	}

	@Override
	public Condition newCondition() {
		// TODO Auto-generated method stub
		return null;
	}

	private void renewLock() {
		// lua脚本进行续期,需要判断是自己的锁才可以进行续期
		String script = "if redis.call('hexists',KEYS[1],ARGV[1]) " //
				+ "then " //
				+ "		return redis.call('expire',KEYS[1],ARGV[2]) " //
				+ "else "//
				+ "		return 0 "//
				+ "end";
		// 当过期时间的1/3时进行判断续期,如果脚本返回0,则表示该锁已经不存,不需要在重新启timer,否则调用本方法进行reschedule
		new Timer().schedule(new TimerTask() {
			@Override
			public void run() {
				if (redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName),
						lockId, String.valueOf(expire))) {
					renewLock();
				}
			}
		}, expire * 1000 / 3);

	}

}

所有实现基本都在上面的类中,使用时和JDK的锁类似,redisLock.lock(), finally里redisLock.unlock()即可。

但是本类仅可以在单redis中使用,对于redis集群不适用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值