"老王,咱们的秒杀系统又出问题了!"测试组的小张急匆匆地跑过来,"刚才模拟测试时,有用户连续点击秒杀按钮,系统居然给同一个用户发了三件商品!"
作为团队的技术骨干,老王皱起了眉头,这已经是本月第三次出现超卖问题了,他们之前使用Redis实现的分布式锁在高峰期经常出现锁失效的情况,而且不支持可重入特性——当同一个线程需要多次获取锁时就会死锁。
"我们需要一个更可靠的方案,"老王心想,"ZooKeeper的可重入锁或许能解决这个问题..."
在单机环境下,Java的synchronized和ReentrantLock都支持可重入特性——同一个线程可以多次获取同一个锁,但在分布式系统中,实现这一特性就复杂得多。
Curator是Apache开源的ZooKeeper客户端,它提供的InterProcessMutex不仅实现了分布式锁,还支持可重入特性,今天我们就深入它的源码,看看它是如何实现多次加锁与释放机制的。
首先看看创建锁实例的代码:
public InterProcessMutex(CuratorFramework client, String path) { this(client, path, new StandardLockInternalsDriver()); }
这里需要传入Curator客户端实例和一个路径,/locks/order_lock",这个路径将成为ZooKeeper上的节点。
当我们第一次调用acquire()
方法时:
public void acquire() throws Exception { if (!internalLock(-1, null)) { throw new IOException("Lost connection while trying to acquire lock: " + basePath); } }
真正的魔法发生在internalLock
方法中,简化后的核心逻辑:
Curator使用两个关键数据结构来维护可重入状态:
private final ConcurrentMap<Thread, LockData> threadData = Maps.newConcurrentMap(); private static class LockData { final Thread owningThread; final String lockPath; final AtomicInteger lockCount = new AtomicInteger(1); }
当线程首次获取锁时,会在threadData中记录:
LockData lockData = threadData.get(currentThread); if (lockData != null) { // 重入情况 lockData.lockCount.incrementAndGet(); return true; } // 首次获取锁 lockData = new LockData(currentThread, lockPath); threadData.put(currentThread, lockData);
这个lockCount就是实现可重入的核心——每次重入时计数器加1,而不是重复创建ZK节点。
释放锁的逻辑同样精彩:
public void release() throws Exception { Thread currentThread = Thread.currentThread(); LockData lockData = threadData.get(currentThread); if (lockData == null) { throw new IllegalMonitorStateException("You do not own the lock: " + basePath); } int newLockCount = lockData.lockCount.decrementAndGet(); if (newLockCount > 0) { return; // 还有重入锁未释放 } if (newLockCount < 0) { throw new IllegalMonitorStateException("Lock count has gone negative for lock: " + basePath); } try { // 真正释放ZK锁 internals.releaseLock(lockData.lockPath); } finally { threadData.remove(currentThread); } }
可以看到,只有当lockCount减到0时,才会真正删除ZK节点释放锁,这种设计确保了:
分布式环境下网络问题不可避免,Curator对此做了精心处理:
回到开头的秒杀问题,使用Curator可重入锁的正确姿势:
InterProcessMutex lock = new InterProcessMutex(client, "/locks/item_" + itemId); public void deductStock(Long itemId, int num) { try { lock.acquire(); // 重入场景:内部方法也需要加锁 doDeduct(itemId, num); } finally { lock.release(); } } private void doDeduct(Long itemId, int num) throws Exception { lock.acquire(); // 可重入 try { // 真正的业务逻辑 Item item = itemDao.get(itemId); if (item.getStock() >= num) { item.setStock(item.getStock() - num); itemDao.update(item); } } finally { lock.release(); } }
虽然ZK锁很强大,但也要注意:
Curator的可重入锁实现展示了分布式系统设计的精妙之处:
"老王,新方案上线后压力测试通过了!"小张兴奋地报告,"模拟5000并发也没有出现超卖!"
老王满意地点点头,分布式锁的选择确实是一门艺术,而Curator无疑提供了一件精良的工具,理解它的实现原理,才能更好地驾驭分布式系统的复杂性。
本文由 咎尔柳 于2025-08-06发表在【云服务器提供商】,文中图片由(咎尔柳)上传,本平台仅提供信息存储服务;作者观点、意见不代表本站立场,如有侵权,请联系我们删除;若有图片侵权,请您准备原始证明材料和公证书后联系我方删除!
本文链接:https://up.7tqx.com/wenda/552565.html
发表评论