Redis--分布式锁实现

获取锁:

@Override
	public String acquireLockWithTimeout(Jedis jedis,String lockName, long acquireTimeout, long lockTimeout) {
		//Jedis jedis =getJedis();
		try {
			String identifier = UUID.randomUUID().toString();
			String lockKey = "lock:" + lockName;
			int lockExpire = (int) (lockTimeout / 1000);
			long endTime = System.currentTimeMillis() + acquireTimeout;
			while (System.currentTimeMillis() < endTime) {
				logger.info("acquireLockWithTimeout->jedis->"+jedis);
				// 尝试设置key-value值,并且key不存在
				if (jedis.setnx(lockKey, identifier) == 1) {
					// key不存在,并且设置成功,获取锁成功
					jedis.expire(lockKey, lockExpire);
					return identifier;
				}
				
				//处理其他占用锁并且无法释放锁的情况
				if (jedis.ttl(lockKey) == -1) {
					// key 存在但没有设置剩余生存时间
					jedis.expire(lockKey, lockExpire);
				}
				try {
					Thread.sleep(1);
				} catch (InterruptedException ie) {
					Thread.currentThread().interrupt();
				}
			}
		}catch(Exception e) {
			e.printStackTrace();
		}finally {
			/*if(jedis!=null) {
				jedis.close();
			}*/
		}
		return null;
	}

流程图
在这里插入图片描述
R e d i s 做分布式锁时需要注意获取锁时,如果获取成功,设置过期时间, \color{#FF0000}{Redis做分布式锁时需要注意获取锁时,如果获取成功,设置过期时间,} Redis做分布式锁时需要注意获取锁时,如果获取成功,设置过期时间,
获取失败,要判断是否是否设置了过期时间,没有的话,要设置过期时间,防止获取成功了, \color{#FF0000}{获取失败,要判断是否是否设置了过期时间,没有的话,要设置过期时间,防止获取成功了,} 获取失败,要判断是否是否设置了过期时间,没有的话,要设置过期时间,防止获取成功了,
但是设置过期时间的时候 r e d i s 炸了。 \color{#FF0000}{但是设置过期时间的时候redis炸了。} 但是设置过期时间的时候redis炸了。

释放锁:

@Override
	public boolean releaseLock(Jedis jedis,String lockName, String identifier) {
		//Jedis jedis = getJedis();
		try {
			String lockKey = "lock:" + lockName;
			while (true) {
				// 监控key
				jedis.watch(lockKey);
				if (identifier.equals(jedis.get(lockKey))) {
					Transaction trans = jedis.multi();
					trans.del(lockKey);
					List<Object> results = trans.exec();
					if (results == null) {
						continue;
					}
					return true;
				}
				jedis.unwatch();
				break;
			}
		}catch(Exception e) {
			e.printStackTrace();
		}finally {
			/*if(jedis!=null) {
				jedis.close();
			}*/
		}
		return false;
	}

删除的时候需要注意先监控 k e y ,然后比较 v a l u e ,再删除,之所以需要监控,是因为再比较后可能值会出现变化 \color{#FF0000}{删除的时候需要注意先监控key,然后比较value,再删除,之所以需要监控,是因为再比较后可能值会出现变化} 删除的时候需要注意先监控key,然后比较value,再删除,之所以需要监控,是因为再比较后可能值会出现变化
那样就会出现把别人刚刚获取到的值给删除了 \color{#FF0000}{那样就会出现把别人刚刚获取到的值给删除了} 那样就会出现把别人刚刚获取到的值给删除了

测试:

@RunWith(SpringRunner.class)
@SpringBootTest
public class LockServiceTest {

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

	@Autowired
	private RedisService redisService;

	@Autowired
	private RedisTemplate<String, Object> redisTemplate;
	
	private AtomicInteger atomicI = new AtomicInteger(0);

	@Test
	public void testJedis() {
		/*ScheduledExecutorService executorService = Executors.newScheduledThreadPool(10);
		// for (int i = 0; i < 10; i++) {
		// 定时执行任务,每隔10秒钟执行一次
		executorService.scheduleWithFixedDelay(new Runnable() {
			@Override
			public void run() {
				Jedis jedis = redisService.getJedis();
				String value = jedis.get("aaa");
				logger.info("=================【{}】=================", value);
			}
		}, 0, 10, TimeUnit.SECONDS);
		// }
*/		
	}
	@Test
	public void testLock() {
		ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
		for (int i = 0; i < 10; i++) {
			// 定时执行任务,每隔500毫秒执行一次
			fixedThreadPool.execute(new Runnable() {
				@Override
				public void run() {
					Jedis jedis=null;
					atomicI.incrementAndGet();
					String lockName = "lock01";
					String id = "";
					try {
						long acquireTimeout = 10000;
						long lockTimeout = 10000;
						long startTime = System.currentTimeMillis();
						
						id = redisService.acquireLockWithTimeout(jedis,lockName, acquireTimeout, lockTimeout);
						long endTime = System.currentTimeMillis();
						if (id != null) {
							logger.info("获取锁成功,id:【{}】花费时间:【{}】" ,id,(endTime - startTime));
						} else {
							logger.info("获取锁失败,花费时间:【{}】",(endTime - startTime));
						}
						Thread.sleep(1000);
					} catch (Exception e) {
						e.printStackTrace();
					} finally {
						if (id != "" && id != null) {
							logger.info("开始释放锁id:【{}】",id);
							redisService.releaseLock(jedis,lockName, id);
						} else {
							logger.info("无锁需要释放");
						}
					}
				}
			});
		}
		
		while(true) {
			if(fixedThreadPool.isTerminated()) {
				System.out.println("run over");
				System.out.println("i="+atomicI);
				break;
			}
		}
	}
}

如上的实现方式在主备、哨兵模式下会存在问题,比如在主节点执行了set后,然后宕机了,从节点同步获取到锁信息,那么其他客户端也会再次获取到锁。然后就出现了红锁。
官网中提到的红锁,大概流程为:向所有的节点发送获取锁的逻辑,如果有超过一半+1的实例返回ok,并且在时间节点内,那么就算获取锁成功,释放锁时也是一样,向所有的实例发送删除的命令。

redission分布式锁的实现
在这里插入图片描述

//---------------获取锁的逻辑---------------------
//判断key是否存在,不存在就设置hash值,field为uuid:线程id value为1
"if (redis.call('exists', KEYS[1]) == 0) then " +
    "redis.call('hset', KEYS[1], ARGV[2], 1); " +
    "redis.call('pexpire', KEYS[1], ARGV[1]); " +
    "return nil; " +
"end; " +
//重入的场景
// 存在key就判断hash field中是否存在值,存在就将value+1
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
    "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
    "redis.call('pexpire', KEYS[1], ARGV[1]); " +
    "return nil; " +
"end; " +
// 获取锁失败
"return redis.call('pttl', KEYS[1]);
//动态续命的逻辑
//获取到锁了,并且存在对应的field
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
  //给key设置过期时间
  "redis.call('pexpire', KEYS[1], ARGV[1]); " +
  "return 1; " +
"end; " +
"return 0;

释放锁

                "if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
                    //当前线程对应的锁不存在
                    "return nil;" +
                "end; " +
                //锁的次数减一
                "local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
                "if (counter > 0) then " +
                    //锁的次数还没为0,设置过期时间
                    "redis.call('pexpire', KEYS[1], ARGV[2]); " +
                    "return 0; " +
                "else " +
                    //已经可以删除key了
                    "redis.call('del', KEYS[1]); " +
                    // 发布事件
                    "redis.call('publish', KEYS[2], ARGV[1]); " +
                    "return 1; "+
                "end; " +
                "return nil;",

参考:http://www.redis.cn/topics/distlock.html

摘自《Redis实战》。
参考:http://www.redis.cn/topics/distlock.html

以下是使用Spring Redis实现Redis分布式锁的例子: 首先,在pom.xml文件中添加以下依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> ``` 然后,创建一个Redis分布式锁的接口: ```java public interface RedisLock { /** * 获取锁 * * @param key 锁的key * @param expireTime 锁的过期时间 * @return 是否获取成功 */ boolean lock(String key, long expireTime); /** * 释放锁 * * @param key 锁的key */ void unlock(String key); } ``` 接着,实现Redis分布式锁的接口: ```java @Component public class RedisLockImpl implements RedisLock { private final RedisTemplate<String, String> redisTemplate; private final ThreadLocal<Map<String, Integer>> lockers = new ThreadLocal<>(); @Autowired public RedisLockImpl(RedisTemplate<String, String> redisTemplate) { this.redisTemplate = redisTemplate; } @Override public boolean lock(String key, long expireTime) { Map<String, Integer> localLockers = getLocalLockers(); Integer count = localLockers.get(key); if (count != null) { localLockers.put(key, count + 1); return true; } Boolean result = redisTemplate.opsForValue().setIfAbsent(key, ""); if (!result) { return false; } redisTemplate.expire(key, expireTime, TimeUnit.MILLISECONDS); localLockers.put(key, 1); return true; } @Override public void unlock(String key) { Map<String, Integer> localLockers = getLocalLockers(); Integer count = localLockers.get(key); if (count == null) { return; } if (count > 1) { localLockers.put(key, count - 1); } else { localLockers.remove(key); redisTemplate.delete(key); } } private Map<String, Integer> getLocalLockers() { Map<String, Integer> localLockers = lockers.get(); if (localLockers != null) { return localLockers; } lockers.set(new HashMap<>()); return lockers.get(); } } ``` 最后,在需要使用分布式锁的地方,注入RedisLock接口,使用lock()方法获取锁,使用unlock()方法释放锁: ```java @Service public class UserService { private final RedisLock redisLock; @Autowired public UserService(RedisLock redisLock) { this.redisLock = redisLock; } public void updateUser(String userId) { String lockKey = "user_" + userId; boolean locked = redisLock.lock(lockKey, 5000); if (!locked) { throw new RuntimeException("获取锁失败"); } try { // 更新用户信息 } finally { redisLock.unlock(lockKey); } } } ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值