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

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

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


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

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

✅ 类比:

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

🧩 二、核心概念解析

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

java 复制代码
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 确保释放

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

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


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

java 复制代码
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

java 复制代码
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() 自动由事务管理器获取
使用场景 非事务性临界区 事务性数据操作

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

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

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

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

java 复制代码
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()立即尝试获取分布式锁
  • 如果锁被占用,可以选择超时失败(避免无限等待)。
  • 行为类似于"显式锁 + 事务"的组合效果。

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

java 复制代码
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() 或 悲观事务。

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

相关推荐
lang201509284 小时前
Apache Ignite 的对等类加载(Peer Class Loading, P2P Class Loading)机制
apache·ignite
chen_note6 小时前
LAMP及其环境的部署搭建
linux·运维·mysql·php·apache·lamp·phpmyadmin
lang201509286 小时前
Apache Ignite 与 Spring Boot 集成
spring boot·后端·apache·ignite
lang201509287 小时前
如何使用 Apache Ignite 作为 Spring 框架的缓存(Spring Cache)后端
spring·缓存·apache·ignite
lang201509289 小时前
Apache Ignite 的监控与指标(Monitoring and Metrics)
apache·ignite
lang2015092815 小时前
Apache Ignite 集群状态(Cluster States)
apache·ignite
谈不譚网安19 小时前
Apache HTTP Server 2.4.50 路径穿越漏洞(CVE-2021-42013)
网络协议·http·apache
lang201509281 天前
关于 Apache Ignite 中 Job 调度(Job Scheduling)与冲突控制(Collision Control) 的机制说明
apache·ignite
lang201509282 天前
Apache Ignite 的分布式原子类型(Atomic Types)
分布式·apache·ignite