通过数据库创建分布式锁
第一步,需要创建一个锁表,如下所示
CREATE TABLE `DistributedLock` (
`lock_name` VARCHAR(100) NOT NULL,
`lock_value` VARCHAR(100) NOT NULL,
`expiration_time` TIMESTAMP NULL,
PRIMARY KEY (`lock_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
- lock_name 是锁的名称,用作唯一标识。
lock_value
可以用来存储获取锁的客户端标识。expiration_time
是锁的过期时间,用于避免死锁。
第二步,获取锁:
要获取锁,可以尝试插入一条记录。如果lock_name
已经存在,并且expiration_time
表明锁未过期,那么插入将失败,表示获取锁失败。如果插入成功,表示获取了锁。
INSERT INTO `DistributedLock` (`lock_name`, `lock_value`, `expiration_time`)
VALUES ('myLock', 'client1', DATE_ADD(NOW(), INTERVAL 10 SECOND))
ON DUPLICATE KEY UPDATE `lock_value` = IF(`expiration_time` <= NOW(), VALUES(`lock_value`), `lock_value`),`expiration_time` = IF(`expiration_time` <= NOW(), VALUES(`expiration_time`), `expiration_time`);
这条SQL尝试插入一条记录。如果lock_name
已经存在,那么它会检查expiration_time
。如果锁已经过期(expiration_time
<= NOW()),则更新lock_value
和expiration_time
;如果锁没有过期,则保持不变,这意味着获取锁失败。
第三步,释放锁:
释放锁相对简单,只需更新或删除对应的记录即可。
DELETE FROM `DistributedLock` WHERE `lock_name` = 'myLock' AND `lock_value` = 'client1';
这个命令尝试删除锁记录。通常,只有持有锁的客户端(在本例中是client1
)才能成功删除记录,从而释放锁。
但这个方法的缺陷在于需要频繁和数据库打交道,给数据库增加压力。
Zookeeper实现分布式锁
ZooKeeper实现分布式锁的基本思想是利用ZooKeeper的临时有序节点来实现锁的功能。以下是使用ZooKeeper实现分布式锁的基本步骤:
-
连接ZooKeeper服务器:客户端首先需要连接到ZooKeeper集群。
-
创建锁节点:在ZooKeeper的某个固定根节点(例如
/locks
)下,为需要加锁的资源创建一个临时有序节点。比如,如果想对某个资源加锁,可以在/locks/myLock
下创建一个临时顺序节点。每个客户端尝试创建这样的节点时,ZooKeeper会自动在节点后添加一个递增的序号,形成如/locks/myLock/000000001
、/locks/myLock/000000002
等节点。 -
节点排序和检测:客户端获取
/locks/myLock
下的所有子节点,并进行排序。如果该客户端创建的节点序号是最小的,那么它就获得了锁。如果不是,它就找到比自己序号小的最近的那个节点,然后在该节点上注册一个监听器(watcher),等待这个节点被删除。 -
等待锁:如果没有获取到锁,客户端就会阻塞等待。一旦前面的节点(即客户端监听的节点)被删除(通常是因为持有锁的客户端完成任务并释放了锁),ZooKeeper会通知该客户端,客户端再次判断自己是否为最小节点,如果是,则获取锁;否则,继续步骤3的监听。
-
释放锁:当任务执行完成后,客户端删除自己创建的那个节点,从而释放锁。因为是临时节点,即使客户端崩溃或失去与ZooKeeper的连接,节点也会被自动删除,从而避免死锁。
使用ZooKeeper实现分布式锁的关键优点之一是其天然的顺序保证和强一致性,能够确保在任何时刻,只有一个客户端持有锁。此外,通过监听机制,能够有效减少客户端的轮询,提高锁的获取效率。然而,需要注意的是,频繁地在ZooKeeper上创建和删除节点可能会对其性能造成影响,因此,在设计分布式锁方案时,需要考虑到这一点。
RedissonClient实现分布式锁
RLock lock = redissonClient.getLock(LOCK_USER_REGISTER_KEY + requestParam.getUsername());
try {
// 尝试获取锁,这里可以根据实际情况设置等待时间和锁持有时间
if (lock.tryLock(10, 60, TimeUnit.SECONDS)) {
try {
// 执行同步操作
} finally {
// 确保在操作完成后释放锁
lock.unlock();
}
} else {
// 获取锁失败的处理逻辑
}
} catch (InterruptedException e) {
// 处理中断异常
Thread.currentThread().interrupt();
}
Redis的sex命令实现分布式锁
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
@Component
public class RedisDistributedLock {
private final StringRedisTemplate stringRedisTemplate;
@Autowired
public RedisDistributedLock(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
/**
* 尝试获取分布式锁
* @param lockKey 锁的键
* @param requestId 请求标识
* @param expireTime 过期时间(秒)
* @return 是否获取成功
*/
public boolean tryGetDistributedLock(String lockKey, String requestId, long expireTime) {
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, requestId, expireTime, TimeUnit.SECONDS);
return Boolean.TRUE.equals(result);
}
/**
* 使用Lua脚本安全释放分布式锁
* @param lockKey 锁的键
* @param requestId 请求标识
* @return 是否释放成功
*/
public boolean releaseDistributedLock(String lockKey, String requestId) {
// Lua脚本,检查给定的key是否存在,并且value与给定的requestId匹配
// 如果匹配则删除key释放锁,返回1;否则不做操作,返回0
String luaScript =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]) " +
"else " +
" return 0 " +
"end";
Long result = stringRedisTemplate.execute((connection) ->
connection.eval(luaScript.getBytes(),
ReturnType.INTEGER,
1,
lockKey.getBytes(),
requestId.getBytes()),
true);
return Long.valueOf(1).equals(result);
}
}