Apache Ignite 的分布式锁Distributed Locks的介绍

发布于:2025-07-30 ⋅ 阅读:(27) ⋅ 点赞:(0)

以下这段内容是关于 Apache Ignite 的分布式锁(Distributed Locks) 的介绍。这是一个非常重要的功能,用于在分布式系统中协调多个节点对共享资源的并发访问

下面我们来一步步深入理解它。


🎯 一、一句话理解:什么是 Ignite 分布式锁?

Ignite 分布式锁是一个跨多个服务器节点的“互斥锁”,确保同一时间只有一个节点可以操作某个共享数据(比如缓存中的某个 key)。

✅ 类比:

  • 就像一把“全球唯一的钥匙”:只有拿到这把钥匙的线程才能修改某个数据。
  • 单机环境下用 synchronizedReentrantLock
  • 分布式环境下就需要 IgniteCache.lock() 这种跨 JVM 的锁

🧩 二、核心概念解析

1️⃣ IgniteCache.lock(key) —— 获取一个分布式锁

Lock lock = cache.lock("keyLock");
  • 这个 lock 是一个实现了 java.util.concurrent.locks.Lock 接口的对象。
  • 它不是本地锁!它是集群范围内的分布式锁
  • 当你在 Node A 上调用 lock.lock(),Node B 和 Node C 上试图对同一个 key 加锁的线程都会阻塞等待,直到 Node A 释放锁。

2️⃣ 使用方式:try-finally 确保释放

lock.lock();  // 阻塞直到获取锁
try {
    // 安全地操作共享资源
    cache.put("Hello", 11);
    cache.put("World", 22);
} finally {
    lock.unlock(); // 必须释放,否则死锁!
}

⚠️ 注意:必须放在 finally 块中释放,防止异常导致锁未释放,造成死锁或资源饥饿


3️⃣ lockAll(keys) —— 批量加锁

Collection<String> keys = Arrays.asList("key1", "key2", "key3");
Lock lock = cache.lockAll(keys);
lock.lock();
try {
    // 同时锁定多个 key
    cache.put("key1", 1);
    cache.put("key2", 2);
    cache.put("key3", 3);
} finally {
    lock.unlock();
}
  • 适用于需要原子性地操作多个 key 的场景。
  • 所有 key 的锁会一起获取、一起释放
  • 避免因部分加锁成功而导致的数据不一致问题。

🔐 三、为什么需要分布式锁?

在分布式系统中,多个节点可能同时访问同一份数据。例如:

场景 问题 解决方案
多个节点同时更新用户余额 超卖、余额错乱 userId 加分布式锁
多个节点争抢执行定时任务 重复执行 "task-refresh" 加锁
缓存双写一致性 缓存和数据库不一致 更新时对 key 加锁

👉 没有锁 → 数据竞争(Race Condition) → 数据错误!


⚙️ 四、Atomicity Mode:必须是 TRANSACTIONAL

CacheConfiguration cfg = new CacheConfiguration("myCache");
cfg.setAtomicityMode(CacheAtomicityMode.TRANSACTIONAL); // 必须设置
  • Ignite 支持两种原子性模式:
    • ATOMIC:高性能,无事务支持,不能使用显式锁。
    • TRANSACTIONAL:支持事务和显式分布式锁

❌ 如果你在 ATOMIC 模式下调用 cache.lock(),会抛出异常!

✅ 所以:要用分布式锁,缓存必须配置为 TRANSACTIONAL 模式。


🔄 五、Locks vs Transactions:锁与事务的关系

这是最容易混淆的部分,原文说得很清楚:

“Explicit locks are not transactional and cannot be used from within transactions.”

我们来拆解这句话:

✅ 情况 1:显式锁 ≠ 事务锁

类型 显式锁 (cache.lock()) 事务中的锁
是否可嵌套在事务中 ❌ 不可以 ✅ 可以
是否自动提交/回滚 ❌ 不支持回滚 ✅ 支持
如何获取 手动 lock.lock() 自动由事务管理器获取
使用场景 非事务性临界区 事务性数据操作

🔴 错误写法(会抛异常):

IgniteTransactions txs = ignite.transactions();
try (Transaction tx = txs.txStart()) {
    Lock lock = cache.lock("key");
    lock.lock(); // ❌ 抛异常!不能在事务中使用显式锁
    cache.put("key", 1);
    tx.commit();
}

✅ 情况 2:想要“事务中的显式锁”?用 PESSIMISTIC 事务

如果你希望在事务中也能“显式控制锁”的行为(比如立即失败而不是等待),应该使用:

try (Transaction tx = ignite.transactions().txStart(
        TransactionConcurrency.PESSIMISTIC,  // 悲观并发控制
        TransactionIsolation.REPEATABLE_READ)) {

    // 第一次读/写就会自动加锁
    Integer val = cache.get("key");
    cache.put("key", val + 1);

    tx.commit(); // 提交时释放锁
}
悲观事务(PESSIMISTIC)的特点:
  • get()put()立即尝试获取分布式锁
  • 如果锁被占用,可以选择超时失败(避免无限等待)。
  • 行为类似于“显式锁 + 事务”的组合效果。

🧪 六、完整示例:银行转账(防止并发超支)

IgniteCache<String, Integer> cache = ignite.cache("accounts");

// 模拟两个账户
String from = "account-A";
String to = "account-B";

// 对两个账户加锁(避免死锁:按字母顺序加锁)
List<String> sortedKeys = Arrays.asList(from, to).stream().sorted().collect(Collectors.toList());
Lock lock = cache.lockAll(sortedKeys);

lock.lock();
try {
    Integer balanceA = cache.get(from);
    Integer balanceB = cache.get(to);

    if (balanceA >= 100) {
        cache.put(from, balanceA - 100);
        cache.put(to, balanceB + 100);
        System.out.println("转账成功");
    } else {
        System.out.println("余额不足");
    }
} finally {
    lock.unlock(); // 释放所有锁
}

✅ 保证了即使多个节点同时发起转账,也不会出现“超卖”。


⚠️ 七、注意事项 & 最佳实践

项目 建议
🔒 锁粒度 尽量小(比如按用户 ID 锁),避免锁整个缓存
⏱️ 锁持有时间 越短越好,不要在锁内做耗时操作(如网络请求)
💥 异常处理 一定要 finally unlock(),建议用 try-with-resources(如果自定义封装)
🪢 死锁风险 多 key 加锁时,按固定顺序加锁(如排序)
📈 性能影响 分布式锁涉及网络通信,频繁使用会影响性能
🔄 替代方案 考虑使用 EntryProcessorinvoke())进行原子更新,避免手动加锁

✅ 总结:一句话掌握精髓

Ignite 的 cache.lock(key) 提供了一种简单、直观的跨节点互斥机制,让你像使用本地 ReentrantLock 一样保护分布式共享资源,但前提是缓存必须是 TRANSACTIONAL 模式,并且不能与事务混用。


🔄 对比总结表

功能 cache.lock() 显式锁 悲观事务(PESSIMISTIC) EntryProcessor(invoke)
是否跨节点 ✅ 是 ✅ 是 ✅ 是
是否支持事务 ❌ 否 ✅ 是 ✅ 是(单 key)
是否自动加锁 ✅ 手动 ✅ 自动 ✅ 自动
适用场景 非事务临界区 多 key 事务操作 单 key 原子更新
性能 中等 中等 高(推荐)

如果你想实现高并发下的安全更新,优先考虑 EntryProcessor;如果逻辑复杂必须加锁,再用 lock() 或 悲观事务。

如有具体业务场景(如库存扣减、计数器、任务调度),欢迎继续提问,我可以给出更具体的代码建议!


网站公告

今日签到

点亮在社区的每一天
去签到