使用Redis实现分布式锁
Redis能实现分布式锁,是因为Redis的单进程单线程模式,它采用队列将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系。本篇记录一下如何使用Redis的Java客户端Jedis和Redisson实现分布式锁。
使用Jedis实现分布式锁
一、加锁的条件
1、加锁必须有时间控制
防止某个线程出现问题,长时间或永久不释放锁,其他线程无法获得锁
2、加锁的线程和解锁的线程是同一个
A在加锁期间内没有执行完,Redis使锁过期,A会继续往下执行,通常在finally块内会释放锁,但此时B已抢到锁了,那么A释放的就是B的锁。
同理B线程继续释放C线程的锁。。。
二、代码实现
1、代码示例
String script="if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; String key = "mylock"; String requestId = UUID.randomUUID().toString(); String lock = null; Jedis jedis = jedisPool.getResource(); try{ while (lock == null) { lock = jedis.set(key,requestId,"NX","EX",120);//key在120秒后自动删除,如果执行set时key存在则返回null,否则返回OK if("OK".equals(lock)){ break; //获得锁,跳出循环,执行业务代码 } Thread.sleep(1000);//没有获得锁,休眠1s,继续尝试获得锁 } //业务代码
...... }catch (Exception e){ e.printStackTrace(); }finally { jedis.eval(script,Collections.singletonList(key),Collections.singletonList(requestId)); jedis.close(); }
2、代码说明
加锁时使用的jedis.set(key,requestId,"NX","EX",120)是一条语句,满足原子性操作,下面的写法不可取: jedis.setnx(key,requestId); jedis.expire(key,120); 解锁时使用了Lua脚本,先通过key获取value,如果value与参数requestId相等就删除key。 之所以使用Lua脚本是因为将两个语句合为一个脚本,满足原子性操作,下面的写法不可取: String value = jedis.get(key); if("requestId".equals(value)){ jedis.del(key); }
三、存在的问题
上面的实现方式在某些业务场景中无法满足,比如业务场景要求我们必须等到执行完方法才能释放锁。这就需要加锁的时间随着方法的执行时间而延长,如果自己实现的话就需要写一个监控线程,监控方法的执行时间,从而延长Redis的加锁时间。其实这些在Redisson中已经实现,推荐使用Redisson实现分布式锁。
使用Redisson实现分布式锁
一、加锁的条件
Redisson已经帮我们实现
二、代码实现
String key = "mylock"; RLock lock = null; try{ lock = redissonClient.getLock(key); while (true) { if(lock.tryLock()){ break; } Thread.sleep(1000); } //业务代码
......
}catch (Exception e){ e.printStackTrace(); }finally { if(null != lock && lock.isHeldByCurrentThread()) { lock.unlock(); } }
三、存在问题
主从复制时可能出现问题,如何解决下一篇再讲