Redis实现分布式锁
-
什么是锁?
我们在火车上上厕所,同一时间只能有一个人进入厕所,其他人只能等候,等这个人用完了,其他人才能进去,这就是锁。 -
为什么要使用锁
一个程序中大量线程同时访问一个共享资源,如果有的线程做修改操作,有的则获取这资源,这个资源就不安全了。好比火车售票系统,我们将库存放入Redis 缓存中,大量并发同时访问redis,这时候如果不加锁,就会出现“超卖现象”-----卖多了实际没那么多票。
单机锁
1、java 本身提供了单机锁的解决方案,在方法上添加 synchronized关键字或者使用ReentrantLock 锁对象。在单体架构中是没有问题的,但是在集群项目中,就不管用了,因为多个进程访问一个外部共享资源。
分布式锁
分布式锁,是一种思想,它的实现方式有很多。
- MySQL 可以实现利用 它的乐观锁和悲观锁可以实现,这种方式对数据库的压力毫无疑问。
- zookeeper 也可以实现,需要实现zookeeper集群,这样就增加了项目的复杂度。
- Redis实现分布式锁,本身项目中使用Redis作为缓存使用,同时也能实现分布式锁。
实现思路:
使用 SETNX key value key这个变量存在互斥性刚好符合锁的特性。
如果key不存在senxt成功返回int的1,这个key存在了返回0。
加锁实际上就是在redis中,给Key键设置一个值,为避免死锁,并给定一个过期时间。
代码如下:
lock 是锁的名称。
UUID 是这个锁的值 。
1000 是过期时间单位是毫秒。
SETNX lock uuid 1000
第一种使用递归方式,第二种使用for循环方式或while循环。
缺点自己写复杂,效率不高,不能恢复时效等。
@Service
public class RedisLock {
Logger logger = LoggerFactory.getLogger(this.getClass());
private String lock_key = "lock"; //锁键
protected long internalLockLeaseTime = 30000;//锁过期时间
private long timeout = 999999; //获取锁的超时时间
//SET命令的参数
SetParams params = SetParams.setParams().nx().px(internalLockLeaseTime);
@Autowired
JedisPool jedisPool;
/**
* 加锁
* @param id
* @return
*/
public boolean lock(String id){
Jedis jedis = jedisPool.getResource();
Long start = System.currentTimeMillis();
UUID uuid = UUID.randomUUID()
try{
// 死循环
for(;;){
//SET命令返回OK ,则证明获取锁成功
String lock = jedis.set(lock_key, id, params);
if("OK".equals(lock)){
return true;
}
//否则循环等待,在timeout时间内仍未获取到锁,则获取失败
long l = System.currentTimeMillis() - start;
if (l>=timeout) {
return false;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}finally {
//防止误删
if(uuid.equals(jedis.get(lock_key)) ){
jedis.close();
}
}
}
}
在实际开发中我们使用Redisson 框架实现分布式锁
Redisson是架设在Redis基础上的一个Java驻内存数据网格(In-Memory Data Grid)。
充分的利用了Redis键值数据库提供的一系列优势,基于Java实用工具包中常用接口,为使用者提供了一系列具有分布式特性的常用工具类。使得原本作为协调单机多线程并发程序的工具包获得了协调分布式多机多线程并发系统的能力,大大降低了设计和研发大规模分布式系统的难度。同时结合各富特色的分布式服务,更进一步简化了分布式环境中程序相互之间的协作。
相对于Jedis而言,Redisson强大的一批。当然了,随之而来的就是它的复杂性。它里面也实现了分布式锁,而且包含多种类型的锁。
直接上代码:
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.10.1</version>
</dependency>
public static void main(String[] args) {
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
config.useSingleServer().setPassword("redis1234");
final RedissonClient client = Redisson.create(config);
RLock lock = client.getLock("lock1");
try{
lock.lock();
}finally{
lock.unlock();
}
}
但值得注意的是,上面的实现方式是针对单机Redis实例而进行的。如果我们有多个Redis实例,请参阅Redlock算法。