概述
为了保证一个方法在高并发情况下的同一时间只能被同一个线程执行,在传统单体应用单机部署的情况下,可以使用Java并发处理相关的API(如synchronized)进行互斥控制。但是,随着业务发展的需要,原单体单机部署的系统被演化成分布式系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题
特点
分布式锁要具备以下特点
- 在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行;
- 高可用的获取锁与释放锁;
- 具备可重入特性;
- 具备锁失效机制,防止死锁;
- 具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败。
常见的分布式锁有redis 分布式锁和zookeeper分布式锁
redis分布式锁
- 单机锁(核心代码)
/**
* 加锁操作
* @param key
* @param requestId:唯一
* @return
*/
public boolean lock(String key, String requestId){
/**
* @param NX,则只有当key不存在是才进行set,
* @param EX: 秒:key的过期时间
* @return 设置成功返回“OK”,否则返回null
*/
String ok = jedis.set(key, requestId, "NX", "EX", 30);
if("OK".equals(ok)){
return true;
}
return false;
}
/**
* 释放锁操作
* @param key
* @param requestId
* @return
*/
public boolean unLock(String key,String requestId){
// if (jedis.get(key).equals(requestId)){
// jedis.del(key);
// }
String script =
"if redis.call('get',KEYS[1]) == ARGV[1] then " +
"return redis.call('del',KEYS[1]) " +
"else " +
"return 0 " +
"end";
//释放锁:通过执行一段lua脚本
//释放锁涉及到两条指令,这两条指令不是原子性的
//需要用到redis的lua脚本,redis执行lua脚本是原子性的
Object result = jedis.eval(script, Collections.singletonList(key),
Collections.singletonList(requestId));
if ("1".equals(result.toString())) {
return true;
}
return false;
}
- redisson框架
Redis官方提出一种算法,叫Redlock,认为这种实现比普通的单实例实现更安全。
RedLock有多种语言的实现包,其中Java版本的实现包叫做:Redisson
RedissonClient redisson = Redisson.create(config);
RLock lock = redisson.getLock("anyLock");
try{
// 1. 最常见的使用方法
//lock.lock();
// 2. 支持过期解锁功能,10秒钟以后自动解锁, 无需调用unlock方法手动解锁
//lock.lock(10, TimeUnit.SECONDS);
// 3. 尝试加锁,最多等待3秒,上锁以后10秒自动解锁
boolean res = lock.tryLock(3, 10, TimeUnit.SECONDS);
if(res){ //成功
// do your business
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
zookeeper分布式锁
-
原理如下图所示
- 流程:
- 创建临时顺序节点my_lock,zk服务器会在节点后自动创建序号(顺序节点特性)
- 判断是否是第一个节点,若是则加锁成功!
- 若不是在首位,给上一个节点加监听器,监听上一个节点的变化
- 上一个节点释放锁后,删除顺序节点
- zk服务端通知监听器,上一个节点删除(对应图示步骤7)
- 再次尝试加锁,判断排在首位,则加锁成功!
- 核心代码(Curator框架的封装)
//定义锁
InterProcessLock lock = new InterProcessMutex(client, "/lock/zectec");
lock.acquire();//加锁
//..... //业务代码
lock.release();//释放锁
如图所示,此时0000005加锁中。
redis和zookeeper分布式锁的区别
- Zookeeper通过创建临时节点和利用监听事件实现分布式锁,Redis使用setnx命令创建相同的key,因为Redis的key保证唯一,先创建的先获取锁。不断的去尝试,去获取锁,比较耗性能
- Zookeeper实现分布式锁,即使获取不到锁,创建对锁的监听即可,不需要不断去尝试获取锁,性能开销小
- Redis实现分布式锁,如果客户端获取到锁的时候遇到bug或挂了,还需要等到超时时间过了以后才能重新获取锁
- Zookeeper实现分布式锁,创建的是临时节点,客户端挂了,节点自然删除,也就达到了自动释放锁的效果