一、基于Redis的分布式锁
1.引入相关依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.16.6</version>
</dependency>
2.代码示例
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import java.util.concurrent.TimeUnit;
public class RedissonDistributedLockExample {
public static void main(String[] args) {
// 配置Redisson客户端
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
// 初始化Redisson客户端
RedissonClient redisson = Redisson.create(config);
// 获取一个分布式锁,lockName可以根据需要自定义
RLock lock = redisson.getLock("myLock");
try {
// 尝试获取锁,等待时间为3秒,锁的超时时间为5秒
boolean acquired = lock.tryLock(3, 5, TimeUnit.SECONDS);
if (acquired) {
System.out.println("Lock acquired by " + Thread.currentThread().getName());
// 执行临界区代码
criticalSection();
} else {
System.out.println("Lock not acquired by " + Thread.currentThread().getName());
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放锁
if (lock.isHeldByCurrentThread()) {
lock.unlock();
System.out.println("Lock released by " + Thread.currentThread().getName());
}
// 关闭Redisson客户端
redisson.shutdown();
}
}
private static void criticalSection() throws InterruptedException {
System.out.println("Inside critical section by " + Thread.currentThread().getName());
Thread.sleep(2000); // 模拟耗时操作
System.out.println("Exiting critical section by " + Thread.currentThread().getName());
}
}
在这个示例中,我们使用tryLock方法尝试获取锁,如果在指定时间内没有获取到锁,则不会阻塞当前线程。此外,我们还展示了如何在完成业务逻辑后正确释放锁。最后,通过调用shutdown方法关闭Redisson客户端,释放资源。
注意:在实际生产环境中,你可能需要根据你的部署环境调整Redis连接参数,比如使用集群模式或者设置密码等。
二、基于ZooKeeper的分布式锁
1.引入相关依赖
<dependencies>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>5.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>5.1.0</version>
</dependency>
</dependencies>
2.代码示例
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.curator.utils.CloseableUtils;
import org.apache.curator.x.discovery.ServiceDiscovery;
import org.apache.curator.x.discovery.ServiceDiscoveryBuilder;
import org.apache.curator.x.discovery.ServiceInstance;
import org.apache.curator.x.discovery.UriSpec;
import org.apache.curator.x.discovery.details.JsonInstanceSerializer;
import org.apache.curator.x.discovery.ServiceType;
import java.io.Closeable;
import java.util.concurrent.TimeUnit;
public class ZookeeperDistributedLockExample implements Closeable {
private final CuratorFramework client;
private final org.apache.curator.framework.recipes.locks.InterProcessMutex lock;
public ZookeeperDistributedLockExample(String connectionString, String lockPath) {
// 初始化Curator客户端
client = CuratorFrameworkFactory.builder()
.connectString(connectionString)
.retryPolicy(new ExponentialBackoffRetry(1000, 3))
.build();
// 启动Curator客户端
client.start();
// 创建分布式锁
lock = new org.apache.curator.framework.recipes.locks.InterProcessMutex(client, lockPath);
}
public void acquireLock() throws Exception {
lock.acquire();
System.out.println("Lock acquired by " + Thread.currentThread().getName());
}
public void releaseLock() throws Exception {
lock.release();
System.out.println("Lock released by " + Thread.currentThread().getName());
}
public void criticalSection() throws InterruptedException {
System.out.println("Inside critical section by " + Thread.currentThread().getName());
Thread.sleep(2000); // 模拟耗时操作
System.out.println("Exiting critical section by " + Thread.currentThread().getName());
}
@Override
public void close() throws Exception {
CloseableUtils.closeQuietly(lock);
CloseableUtils.closeQuietly(client);
}
public static void main(String[] args) throws Exception {
ZookeeperDistributedLockExample example = new ZookeeperDistributedLockExample("localhost:2181", "/distributed-lock");
try {
example.acquireLock();
example.criticalSection();
} finally {
example.releaseLock();
}
example.close();
}
}
在这个示例中,我们使用了Curator框架的InterProcessMutex类来创建一个ZooKeeper分布式锁。acquireLock和releaseLock方法用于获取和释放锁,而criticalSection方法代表了在锁保护下的临界区代码。
需要注意的是,在生产环境中,你可能需要将localhost:2181替换为实际的ZooKeeper服务器地址列表,以及根据实际情况调整重试策略和连接超时时间等参数。在完成所有操作后,务必调用close方法来关闭Curator客户端,释放相关资源。
此外,Curator框架还提供了许多其他功能,如分布式队列、服务发现、配置管理等,可以根据你的需求进一步探索和使用。
三、两种锁的比较
分类比较 | Redis分布式锁 | ZooKeeper分布式锁 |
---|---|---|
实现原理 | Redis分布式锁通常基于SETNX(或SET命令的NX选项)和EX/PX选项实现,其中SETNX用于尝试获取锁,而EX/PX用于设置锁的过期时间。释放锁时,通常使用Lua脚本保证原子性,避免竞态条件。 | ZooKeeper的分布式锁基于其提供的顺序节点和监听器机制。当一个进程请求锁时,它会在特定的路径下创建一个顺序节点。第一个创建的节点即为锁的拥有者。释放锁时,删除对应的节点即可。 |
一致性模型 | Redis的分布式锁主要依赖于单个Redis实例或集群的一致性,对于网络分区等情况,可能无法保证全局的一致性。 | ZooKeeper提供了最终一致性,即使在网络分区的情况下,也能保证最终所有节点的状态一致。这使得ZooKeeper在需要强一致性的场景下表现更佳。 |
高可用性 | 在单点故障情况下,基于单个Redis实例的分布式锁可能会失效。使用Redis集群或哨兵模式可以提高高可用性,但增加了实现的复杂性。 | ZooKeeper天生具备高可用性,通过多副本和选举机制保证服务的连续性。 |
应用场景 | 适合对性能要求较高,且可以接受一定程度不一致性的场景,如缓存、计数器等。 | 适合对一致性要求严格,需要协调多个服务或组件的场景,如配置管理、服务发现、任务调度等。 |
四、对两种锁的个人理解
redis 分布式锁:其实需要自己不断去尝试获取锁,比较消耗性能。
zk 分布式锁:获取不到锁,注册个监听器即可,不需要不断主动尝试获取锁,性能开销较小。
另外一点就是,如果是 Redis 获取锁的那个客户端 出现 bug 挂了,那么只能等待超时时间之后才能释放锁;而 zk 的话,因为创建的是临时 znode,只要客户端挂了,znode 就没了,此时就自动释放锁。
Redis 分布式锁大家没发现好麻烦吗?遍历上锁,计算时间等等…zk 的分布式锁语义清晰实现简单。
所以先不分析太多的东西,就说这两点,我个人实践认为 zk 的分布式锁比 Redis 的分布式锁牢靠、而且模型简单易用。