Redis和ZooKeeper都是用来实现分布式锁的常用工具,但它们在实现机制、性能、可用性和一致性方面有显著区别。以下是详细的比较和结合代码的解释。
1. 实现机制
Redis分布式锁:
- 实现机制 :基于简单的字符串操作,通过
SET NX PX
命令来实现锁,使用Lua脚本确保原子性。Redis是一个内存数据库,操作速度非常快。 - 一致性:Redis提供的是最终一致性,不能完全保证严格的强一致性。
- 可用性:Redis是AP系统(高可用性和分区容错性),可能在网络分区时提供较高的可用性,但在一致性方面有所妥协。
ZooKeeper分布式锁:
- 实现机制:基于ZNode(ZooKeeper节点)来实现锁,通过创建临时顺序节点来竞争锁。ZooKeeper依赖于ZAB协议(ZooKeeper Atomic Broadcast),确保严格的一致性。
- 一致性:ZooKeeper提供的是强一致性,保证严格的顺序一致性。
- 可用性:ZooKeeper是CP系统(强一致性和分区容错性),在网络分区时可能会牺牲部分可用性来保证一致性。
2. 性能
- Redis:由于是内存数据库,操作非常快,适合高性能、高并发的场景。
- ZooKeeper:性能较Redis稍逊,但在一致性和可靠性方面更强,适合需要严格一致性的场景。
3. 可用性和一致性
- Redis:在网络分区时可能会牺牲一致性来保证可用性,属于AP系统。
- ZooKeeper:在网络分区时可能会不可用来保证一致性,属于CP系统。
4. 示例代码
Redis分布式锁代码示例
java
import redis.clients.jedis.Jedis;
import redis.clients.jedis.params.SetParams;
public class RedisDistributedLock {
private Jedis jedis;
private String lockKey;
private String lockValue;
private int expireTime;
public RedisDistributedLock(Jedis jedis, String lockKey, int expireTime) {
this.jedis = jedis;
this.lockKey = lockKey;
this.expireTime = expireTime;
this.lockValue = String.valueOf(Thread.currentThread().getId());
}
public boolean acquireLock() {
SetParams params = new SetParams().nx().px(expireTime);
String result = jedis.set(lockKey, lockValue, params);
return "OK".equals(result);
}
public boolean releaseLock() {
String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
"return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(luaScript, 1, lockKey, lockValue);
return result.equals(1L);
}
public void executeWithLock(Runnable task) {
if (acquireLock()) {
try {
task.run();
} finally {
boolean released = releaseLock();
if (!released) {
System.out.println("Failed to release lock.");
}
}
} else {
System.out.println("Failed to acquire lock, try again later.");
}
}
public static void main(String[] args) {
Jedis jedis = new Jedis("localhost", 6379);
RedisDistributedLock lock = new RedisDistributedLock(jedis, "resource_lock", 10000);
lock.executeWithLock(() -> {
System.out.println("Executing critical section.");
// Simulate long running task
try {
Thread.sleep(8000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("Critical section completed.");
});
jedis.close();
}
}
ZooKeeper分布式锁代码示例
java
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.io.IOException;
public class ZookeeperDistributedLock implements Watcher {
private ZooKeeper zooKeeper;
private String lockName;
private String lockPath;
private String currentLockNode;
public ZookeeperDistributedLock(String hostPort, String lockName) throws IOException {
this.zooKeeper = new ZooKeeper(hostPort, 3000, this);
this.lockName = lockName;
this.lockPath = "/locks/" + lockName;
try {
Stat stat = zooKeeper.exists("/locks", false);
if (stat == null) {
zooKeeper.create("/locks", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
}
public boolean acquireLock() {
try {
currentLockNode = zooKeeper.create(lockPath + "/lock_", new byte[0],
ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
while (true) {
Stat predecessorStat = null;
String predecessorPath = null;
for (String node : zooKeeper.getChildren(lockPath, false)) {
String fullPath = lockPath + "/" + node;
if (currentLockNode.compareTo(fullPath) > 0) {
if (predecessorStat == null || fullPath.compareTo(predecessorPath) > 0) {
predecessorStat = zooKeeper.exists(fullPath, true);
predecessorPath = fullPath;
}
}
}
if (predecessorStat == null) {
return true;
}
synchronized (this) {
wait();
}
}
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
return false;
}
}
public boolean releaseLock() {
try {
zooKeeper.delete(currentLockNode, -1);
return true;
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
return false;
}
}
public void executeWithLock(Runnable task) {
if (acquireLock()) {
try {
task.run();
} finally {
boolean released = releaseLock();
if (!released) {
System.out.println("Failed to release lock.");
}
}
} else {
System.out.println("Failed to acquire lock, try again later.");
}
}
@Override
public void process(WatchedEvent event) {
if (event.getType() == Event.EventType.NodeDeleted) {
synchronized (this) {
notifyAll();
}
}
}
public static void main(String[] args) throws IOException {
ZookeeperDistributedLock lock = new ZookeeperDistributedLock("localhost:2181", "resource_lock");
lock.executeWithLock(() -> {
System.out.println("Executing critical section.");
// Simulate long running task
try {
Thread.sleep(8000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("Critical section completed.");
});
}
}
代码详解
Redis分布式锁代码详解
-
锁的获取与释放:
acquireLock()
方法使用NX
和PX
参数设置锁的唯一性和过期时间,确保锁的原子性和自动过期。releaseLock()
方法使用Lua脚本确保释放锁的原子性,避免误删其他线程持有的锁。
-
执行带锁的任务:
executeWithLock(Runnable task)
方法封装了锁的获取、任务执行和锁的释放逻辑,确保任务在锁的保护下执行。
ZooKeeper分布式锁代码详解
-
锁的获取与释放:
acquireLock()
方法通过创建临时顺序节点来竞争锁,并监听前一个节点的删除事件来确保锁的公平性。releaseLock()
方法删除当前的临时顺序节点,释放锁。
-
执行带锁的任务:
executeWithLock(Runnable task)
方法封装了锁的获取、任务执行和锁的释放逻辑,确保任务在锁的保护下执行。
-
事件处理:
process(WatchedEvent event)
方法处理节点删除事件,确保在前一个节点释放锁后,当前节点能及时获取锁。
总结来说,Redis和ZooKeeper都有各自的优势和适用场景。Redis适合高性能、高并发的场景,ZooKeeper适合需要严格一致性的场景。选择哪一种锁机制,主要取决于具体的应用需求和系统架构。