Redis学习笔记十二(分布式锁)

分布式锁

由来

原单体单机部署的系统被演化成分布式集群系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,单纯的Java API并不能提供分布式锁的能力。为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题!

实现方案

  1. 基于数据库实现分布式锁
  2. 基于缓存(Redis等)
  3. 基于Zookeeper
    每一种分布式锁解决方案都有各自的优缺点:
  4. 性能:redis最高
  5. 可靠性:zookeeper最高

redis分布式锁

image

  1. 使用setnx上锁,通过del释放锁
  2. 锁一直没有释放,设置key过期时间,自动释放
  3. 上锁后突然出现异常(上锁没有设置过期时间)
    • 上锁的同时设置过期时间set lock 10 nx ex 12

java实现(需要配置springboot)

@GetMapping("testLock")
public void testLock(){
	//1获取锁,setnx
	//优化之设置锁的过期时间:在設置锁的过程中,加入过期时间
	//优化防因服务器卡顿,重复释放锁导致锁误删,加入UUID
	String uuid = UUID.randomUUID().toString();
	Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "111",3,TimeUnit.SECONDS);
	//2获取锁成功、查询num的值
	if(lock){
		Object value = redisTemplate.opsForValue().get("num");
		//2.1判断num为空return
		if(StringUtils.isEmpty(value)){
			return;
		}
		//2.2有值就转成成int
		int num = Integer.parseInt(value+"");
		//2.3把redis的num加1
		redisTemplate.opsForValue().set("num", ++num);
		//2.4释放锁之前先判断uuid值是否一样,del
		/*使用lua脚本来锁*/
		// 定义lua 脚本
		String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
		// 使用redis执行lua执行
		DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
		redisScript.setScriptText(script);
		// 设置一下返回值类型 为Long
		// 因为删除判断的时候,返回的0,给其封装为数据类型。如果不封装那么默认返回String 类型,
		// 那么返回字符串与0 会有发生错误。
		redisScript.setResultType(Long.class);
		// 第一个要是script 脚本 ,第二个需要判断的key,第三个就是key所对应的值。
		redisTemplate.execute(redisScript, Arrays.asList(locKey), uuid);
	}else{
		//3获取锁失败、每隔0.1秒再获取
		try {
			Thread.sleep(100);
			testLock();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

分布锁的原子性

LUA脚本是类似redis事务,有一定的原子性,不会被其他命令插队,可以完成一些redis事务性的操作。
但是注意redis的lua脚本功能,只有在Redis 2.6以上的版本才可以使用。

为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:

  • 互斥性。在任意时刻,只有一个客户端能持有锁。
  • 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
  • 解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。
  • 加锁和解锁必须具有原子性。
posted @ 2022-06-25 15:35  小懒虫LK  阅读(68)  评论(0)    收藏  举报