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分布式锁代码示例
1import redis.clients.jedis.Jedis; 2import redis.clients.jedis.params.SetParams; 3 4public class RedisDistributedLock { 5 private Jedis jedis; 6 private String lockKey; 7 private String lockValue; 8 private int expireTime; 9 10 public RedisDistributedLock(Jedis jedis, String lockKey, int expireTime) { 11 this.jedis = jedis; 12 this.lockKey = lockKey; 13 this.expireTime = expireTime; 14 this.lockValue = String.valueOf(Thread.currentThread().getId()); 15 } 16 17 public boolean acquireLock() { 18 SetParams params = new SetParams().nx().px(expireTime); 19 String result = jedis.set(lockKey, lockValue, params); 20 return "OK".equals(result); 21 } 22 23 public boolean releaseLock() { 24 String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then " + 25 "return redis.call('del', KEYS[1]) else return 0 end"; 26 Object result = jedis.eval(luaScript, 1, lockKey, lockValue); 27 return result.equals(1L); 28 } 29 30 public void executeWithLock(Runnable task) { 31 if (acquireLock()) { 32 try { 33 task.run(); 34 } finally { 35 boolean released = releaseLock(); 36 if (!released) { 37 System.out.println("Failed to release lock."); 38 } 39 } 40 } else { 41 System.out.println("Failed to acquire lock, try again later."); 42 } 43 } 44 45 public static void main(String[] args) { 46 Jedis jedis = new Jedis("localhost", 6379); 47 RedisDistributedLock lock = new RedisDistributedLock(jedis, "resource_lock", 10000); 48 49 lock.executeWithLock(() -> { 50 System.out.println("Executing critical section."); 51 // Simulate long running task 52 try { 53 Thread.sleep(8000); 54 } catch (InterruptedException e) { 55 Thread.currentThread().interrupt(); 56 } 57 System.out.println("Critical section completed."); 58 }); 59 60 jedis.close(); 61 } 62} 63
ZooKeeper分布式锁代码示例
1import org.apache.zookeeper.*; 2import org.apache.zookeeper.data.Stat; 3 4import java.io.IOException; 5 6public class ZookeeperDistributedLock implements Watcher { 7 private ZooKeeper zooKeeper; 8 private String lockName; 9 private String lockPath; 10 private String currentLockNode; 11 12 public ZookeeperDistributedLock(String hostPort, String lockName) throws IOException { 13 this.zooKeeper = new ZooKeeper(hostPort, 3000, this); 14 this.lockName = lockName; 15 this.lockPath = "/locks/" + lockName; 16 try { 17 Stat stat = zooKeeper.exists("/locks", false); 18 if (stat == null) { 19 zooKeeper.create("/locks", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); 20 } 21 } catch (KeeperException | InterruptedException e) { 22 e.printStackTrace(); 23 } 24 } 25 26 public boolean acquireLock() { 27 try { 28 currentLockNode = zooKeeper.create(lockPath + "/lock_", new byte[0], 29 ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); 30 while (true) { 31 Stat predecessorStat = null; 32 String predecessorPath = null; 33 for (String node : zooKeeper.getChildren(lockPath, false)) { 34 String fullPath = lockPath + "/" + node; 35 if (currentLockNode.compareTo(fullPath) > 0) { 36 if (predecessorStat == null || fullPath.compareTo(predecessorPath) > 0) { 37 predecessorStat = zooKeeper.exists(fullPath, true); 38 predecessorPath = fullPath; 39 } 40 } 41 } 42 if (predecessorStat == null) { 43 return true; 44 } 45 synchronized (this) { 46 wait(); 47 } 48 } 49 } catch (KeeperException | InterruptedException e) { 50 e.printStackTrace(); 51 return false; 52 } 53 } 54 55 public boolean releaseLock() { 56 try { 57 zooKeeper.delete(currentLockNode, -1); 58 return true; 59 } catch (KeeperException | InterruptedException e) { 60 e.printStackTrace(); 61 return false; 62 } 63 } 64 65 public void executeWithLock(Runnable task) { 66 if (acquireLock()) { 67 try { 68 task.run(); 69 } finally { 70 boolean released = releaseLock(); 71 if (!released) { 72 System.out.println("Failed to release lock."); 73 } 74 } 75 } else { 76 System.out.println("Failed to acquire lock, try again later."); 77 } 78 } 79 80 @Override 81 public void process(WatchedEvent event) { 82 if (event.getType() == Event.EventType.NodeDeleted) { 83 synchronized (this) { 84 notifyAll(); 85 } 86 } 87 } 88 89 public static void main(String[] args) throws IOException { 90 ZookeeperDistributedLock lock = new ZookeeperDistributedLock("localhost:2181", "resource_lock"); 91 92 lock.executeWithLock(() -> { 93 System.out.println("Executing critical section."); 94 // Simulate long running task 95 try { 96 Thread.sleep(8000); 97 } catch (InterruptedException e) { 98 Thread.currentThread().interrupt(); 99 } 100 System.out.println("Critical section completed."); 101 }); 102 } 103} 104
代码详解
Redis分布式锁代码详解
- 锁的获取与释放:
acquireLock()方法使用NX和PX参数设置锁的唯一性和过期时间,确保锁的原子性和自动过期。releaseLock()方法使用Lua脚本确保释放锁的原子性,避免误删其他线程持有的锁。
- 执行带锁的任务:
executeWithLock(Runnable task)方法封装了锁的获取、任务执行和锁的释放逻辑,确保任务在锁的保护下执行。
ZooKeeper分布式锁代码详解
- 锁的获取与释放:
acquireLock()方法通过创建临时顺序节点来竞争锁,并监听前一个节点的删除事件来确保锁的公平性。releaseLock()方法删除当前的临时顺序节点,释放锁。
- 执行带锁的任务:
executeWithLock(Runnable task)方法封装了锁的获取、任务执行和锁的释放逻辑,确保任务在锁的保护下执行。
- 事件处理:
process(WatchedEvent event)方法处理节点删除事件,确保在前一个节点释放锁后,当前节点能及时获取锁。
总结来说,Redis和ZooKeeper都有各自的优势和适用场景。Redis适合高性能、高并发的场景,ZooKeeper适合需要严格一致性的场景。选择哪一种锁机制,主要取决于具体的应用需求和系统架构。
