第 10 章 配置锁定和并发
Data Grid 使用多版本的并发控制(MVCC)来改进对共享数据的访问。
- 允许并发读取器和写入器
- 读取器和写入者不会阻断另一个内容
- 可以检测到并处理写 skews
- 内部锁定可以是条带的
10.1. 锁定和并发
多版本的并发控制(MVCC)是一个与关系数据库和其他数据存储流行的并发方案。MVCC 与粗粒度 Java 同步(甚至 JDK Locks)提供了许多优点,以访问共享数据。
Data Grid 的 MVCC 实现利用最小的锁和同步,更倾向于采用无锁定技术,如 比较 和交换以及无锁定数据结构,这有助于针对多 CPU 和多核环境进行优化。
特别是,Data Grid 的 MVCC 实现会对读者进行大量优化。读取器线程不会获取条目的显式锁定,而是直接读取问题中的条目。
另一方面,作者需要获取写锁。这样可确保每个条目只有一个并发写入器,从而导致并发写入器在线更改条目。
要允许并发读取,写器通过嵌套 MVCCEntry
中的条目来制作他们要修改的条目的副本。此副本隔离并发读取器来查看部分修改的状态。写入完成后,MVCCEntry.commit
() 将清空对数据容器的更改,后续的读取器将看到写入的更改。
10.1.1. 集群缓存和锁定
在 Data Grid 集群中,主所有者节点负责锁定密钥。
对于非事务缓存,Data Grid 将写操作转发到密钥的主所有者,以便它能够锁定它。然后,Data Grid 将写入操作转发到其他所有者,或者在无法锁定密钥时抛出异常。
如果操作为条件并在主所有者上失败,则 Data Grid 不会将它转发到其他所有者。
对于事务缓存,主所有者可以使用最佳锁定模式锁定密钥。Data Grid 还支持不同的隔离级别,以控制事务之间的并发读取。
10.1.2. LockManager
LockManager
是一个组件,它负责锁定写条目。LockManager
使用 LockContainer
找到/hold/create 锁定。LockContainers
有两个广泛的 flavours,它支持锁定条带,并支持每个条目一个锁定。
10.1.3. 锁定条带
锁定条带要求使用固定大小、整个缓存的锁定共享集合,锁定会根据条目的哈希代码分配给条目。与 JDK 的 ConcurrentHashMap
分配锁定的方式类似,这允许在交换过程中具有高度可扩展的固定位锁定机制,以便同一锁定阻止了与相关的条目。
另一种方法是禁用锁定条带 - 这意味着 每个条目创建一个新的 锁定。这种方法 可能会 为您提供更高的并发吞吐量,但它将是额外的内存用量、垃圾收集时间等。
默认情况下禁用锁定条带,因为当不同键锁定在同一锁定条带中时可能会出现潜在的死锁。
可以使用 < locking
/> 配置元素的 concurrencyLevel
属性调整锁定条带使用的共享锁定集合的大小。
配置示例:
<locking striping="false|true"/>
或者
new ConfigurationBuilder().locking().useLockStriping(false|true);
10.1.4. 并发级别
除了确定条带锁定容器的大小外,这个并发级别还用于调整任何基于 JDK ConcurrentHashMap
的集合。有关并发级别的详细讨论,请参阅 JDK
ConcurrentHashMap
Javadocs,因为此参数在 Data Grid 中完全相同。
配置示例:
<locking concurrency-level="32"/>
或者
new ConfigurationBuilder().locking().concurrencyLevel(32);
10.1.5. 锁定超时
锁定超时指定等待内容锁定的时间长度(以毫秒为单位)。
配置示例:
<locking acquire-timeout="10000"/>
或者
new ConfigurationBuilder().locking().lockAcquisitionTimeout(10000); //alternatively new ConfigurationBuilder().locking().lockAcquisitionTimeout(10, TimeUnit.SECONDS);
10.1.6. 一致性
单个所有者被锁定(而不是所有所有者被锁定)不会破坏以下一致性保证:如果密钥
对节点 K
{A、B}
和事务 TX1
进行哈希处理时,让我们在 A
上取得一个锁定。如果另一个事务 TX2
在 B
(或任何其他节点)上启动,TX2
会尝试锁定 K
,然后其将失败,因为锁定已持有 TX1
。这样做的原因是,无论事务源自的位置,K
的锁始终、确定地获取到集群的同一节点上。
10.1.7. 数据版本控制
Data Grid 支持两种形式的数据版本: simple 和 external。简单的版本控制用于写入偏移检查。
外部版本控制用于在 Data Grid 内封装一个外部的数据源,例如将 Data Grid 与 Hibernate 搭配使用时,后者又直接从数据库获取其数据版本信息。
在这个方案中,传递版本的机制变得有必要,而 put ()
和 putForExternalRead ()
的超载版本将在 AdvancedCache
中提供,采用外部数据版本。然后,这存储在 InvocationContext
中,并在提交时应用到条目。
无法编写 skew 检查,在外部数据版本控制时不会执行。