第 7 章 locking 和 Concurrency
Data Grid 利用多版本的并发控制(MVCC)- 一个与关系数据库和其他数据存储中流行的并发方案。MVCC 比粒度 Java 同步提供了许多优点,甚至 JDK Locks 用于访问共享数据,包括:
- 允许并发读取器和写卡器
- 读者和写者不会阻止另一个操作
- 可以检测和处理写入 skews
- 内部锁定可以是条带的
7.1. 锁定实施详情
Data Grid 的 MVCC 实施利用最小锁定和同步,大量地与无锁技术(如 比较和交换 和无锁定数据结构)中实现,这有助于对多 CPU 和多核环境进行优化。
特别是,数据中心 MVCC 实现主要针对读卡器进行了优化。读取器线程不会为条目获得显式锁定,而是直接读取问题中的条目。
另一方面,写者需要获取写入锁定。这样可确保每个条目只有一个并发写入器,从而导致并发写入程序队列更改条目。
要允许并发读取,写者通过将条目嵌套在 MVCCEntry
中来制作它们要修改的条目的副本。此副本隔离并发读取器与查看部分修改的状态。写入完成后,MV CCEntry.commit ()
将刷新对数据容器的更改,后续的读取器会看到写入的更改。
7.1.1. 集群缓存和锁定
在 Data Grid 集群中,主所有者节点负责锁定密钥。
对于非事务缓存,Data Grid 会将写入操作转发到密钥的主所有者,以便它可以尝试锁定它。然后,数据中心会将写入操作转发到其他所有者,如果无法锁定密钥,则抛出异常。
如果操作有条件且在主所有者上失败,Data Grid 不会将其转发到其他所有者。
对于事务缓存,主所有者可以使用选择和模拟锁定模式锁定密钥。数据中心还支持不同的隔离级别来控制事务之间的并发读取。
7.1.2. LockManager
LockManager
是一个负责锁定写入条目的组件。LockManager
使用 LockContainer
来查找/保留/创建锁定。LockContainers
分为两个广泛的 flavours,支持锁定条带,支持每个条目有一个锁定。
7.1.3. 锁定条带
锁定需要整个缓存的固定大小共享锁集合,以及根据条目的密钥哈希代码分配给条目的锁定。与 JDK 的 ConcurrentHashMap
分配锁的方式类似,这允许在交换中实现高度可扩展、固定头锁定机制,以交换处理可能被同一锁阻断的相关条目。
另一种方法是禁用锁定条带 - 这意味着 每个条目都会创建新的 锁定。这种方法 可能会 为您提供更大的并发吞吐量,但将以额外内存使用量、垃圾收集时间等成本为代价。
默认情况下,锁定条带被禁用,因为当不同密钥的锁在同一锁定条带中最终时可能会出现潜在的死锁。
可以使用 < locking
/> 配置元素的 concurrencyLevel
属性调整锁定条带使用的共享锁定集合的大小。
配置示例:
<locking striping="false|true"/>
或者
new ConfigurationBuilder().locking().useLockStriping(false|true);
7.1.4. 并发级别
除了确定条带锁定容器的大小外,此并发级别还用于调整任何基于 JDK ConcurrentHashMap
的集合(如内部到 DataContainer
s)。如需了解并发级别的详细讨论,请参阅 JDK ConcurrentHashMap
Javadocs,因为这个参数在 Data Grid 中完全相同。
配置示例:
<locking concurrency-level="32"/>
或者
new ConfigurationBuilder().locking().concurrencyLevel(32);
7.1.5. 锁定超时
锁定超时指定等待内容锁定的时间(以毫秒为单位)。
配置示例:
<locking acquire-timeout="10000"/>
或者
new ConfigurationBuilder().locking().lockAcquisitionTimeout(10000); //alternatively new ConfigurationBuilder().locking().lockAcquisitionTimeout(10, TimeUnit.SECONDS);
7.1.6. 一致性
单个所有者被锁住(而不是所有所有者被锁住)实际上不会破坏以下一致性保证:如果键 K
被哈希成节点 {A、B}
和事务 TX1
来获取 K
的锁定,请参阅 A
。如果另一个事务在 B
(或任何其他节点)上启动,
会尝试锁定 TX2
K
,那么它将失败并显示超时,因为已由 TX1
保留。这样做的原因是,无论事务的来源是什么,都会始终确定地获取密钥 K
的锁定。