使用缓存项版本控制进行锁定(乐观锁定)
而 悲观锁 是一种非常有用的方法,但使用它有一个限制,除非对某个项目完全执行至少一个操作,否则无法使用该项目。 这意味着该项目将保持锁定状态,直到对该项目完全执行一项任务为止。 如果某个项目长时间保持锁定状态,这可能会导致线程饥饿。
备注
此功能也可用于 NCache Professional.
这就是乐观锁派上用场的地方 NCache 使用缓存项版本控制。 CacheItemVersion
是与每个缓存项关联的属性。 它是一个数值,表示缓存项目的版本,每次更新项目时都会将其自身增加 XNUMX。 此属性允许您跟踪项目中是否发生任何更改。 当您从缓存中获取项目时,您还会在缓存中获取其当前版本。
对于读密集型应用程序,乐观锁优于悲观锁。
何时使用乐观锁定
在前面的示例中,我们有一个银行帐户由两个用户同时使用。 假设其中一个用户因在银行帐户上执行存款交易而获得了锁。 User2正在等待User1释放锁以进行提现交易。 假设 User1 由于网络连接问题而进入不稳定状态,而没有设置释放锁定。 User2 在不知道任何连接问题的情况下一直等待他释放锁,因此他进入饥饿状态,直到第一个用户释放锁。
为了避免此类问题,乐观锁是一个有用的解决方案。 使用这种锁定,如果用户1想要更新帐户,他可以在不锁定帐户的情况下更新帐户,并且项目版本也会相应更新。 现在,当 User2 想要更新数据时,他将根据项目版本获取更新版本,这将确保不会发生数据完整性问题。 如果任何用户对旧项目版本的数据进行操作,则操作将失败,因为该数据的项目版本已过时。
CacheItemVersion
使用以下方式为应用程序的开发添加了额外的维度 NCache。 乐观并发可以通过以下方式在应用程序中实现 NCache 项目版本控制。
当任何项目添加到缓存时,缓存项目版本将返回到缓存客户端。 该值表示对特定数据执行的更新次数。 每次更新时,项目版本的值都会增加。
先决条件
检索项目并使用项目版本更新项目
An 地址 操作返回 CacheItemVersion
。 如果第一次添加某个项目,则会返回一个包含其创建时间戳的长值。 以后对该键进行操作时,该版本将加“1”。
乐观锁定确保用户始终从缓存中获取项目的最新副本。 如果用户继续在过时的版本上执行功能, NCache 抛出异常,以便用户从缓存中获取更新的项目。
在下面的示例中,多个应用程序使用缓存。 缓存包含产品的数据。 一种 CacheItem
被添加到缓存中。 这两个应用程序都会获取当前版本(比如说版本)的项目。 应用程序1修改 产品名称 然后将项目重新插入缓存中,这会将项目的项目版本更新为新版本。 应用程序 2 仍然具有该版本的项目。 如果 Application2 更新库存中商品的单位并将该商品重新插入缓存中,则插入将会失败。 Application2 必须获取更新版本才能执行操作 CacheItem
.
备注
您可以使用两者在缓存中添加项目 地址 or 插页 方法。
-
Add
方法将新项目添加到缓存并首次保存项目版本。
-
Insert
方法在缓存中添加一个项目(如果它不存在),而它会覆盖现有时间的值并更新项目版本。
以下代码部分解释了应用程序执行的操作。
// Precondition: Cache is already connected
// An item is added in the cache with itemVersion
// Specify the key of the cacheItem
string key = "Product:1001";
// Initialize the cacheItemVersion
CacheItemVersion version = null;
// Get the cacheItem previously added in the cache with the version
CacheItem cacheItem = cache.GetCacheItem(key, ref version);
// If result is not null
if (cacheitem != null)
{
// CacheItem is retrieved successfully with the version
// If result is Product type
var prod = new Product();
prod = cacheItem.GetValue<Product>();
prod.UnitsInStock++;
// Create a new cacheItem with updated value
var updateItem = new CacheItem(prod);
//Set the itemversion. This version will be used to compare the
// item version of cached item
updateItem.Version = version;
cache.Insert(key, updateItem);
// If it matches, the insert will be successful, otherwise it will fail
}
else
{
// Item could not be retrieved due to outdated CacheItemVersion
}
// Precondition: Cache is already connected
// An item is added in the cache with itemVersion
// Specify the key of the cacheItem
String key = "Product:1001";
// Initialize the cacheItemVersion
CacheItemVersion version = new CacheItemVersion();
// readThruOptions set to null
CacheItem cacheItem = cache.getCacheItem(key, version, null);
// If result is not null
if (cacheItem != null)
{
var prod = new Product();
prod = cacheItem.getValue(Product.Class);
prod.unitsInStock++;
// Update the retrieved cacheitem according to requirement
// Create a new cacheItem with updated value
CacheItem updatedItem = new CacheItem(prod);
// Set the itemversion. This version will be used to compare the
// item version of cached item
updatedItem.setCacheItemVersion(version);
cache.insert(key, updatedItem);
// If it matches, the insert will be successful, otherwise it will fail
}
// This is an async method
// Precondition: Cache is already connected
// An item is added in the cache with itemVersion
// Specify the key of the cacheItem
var key = "Product:1001";
// Initialize the cacheItemVersion
var version = new ncache.CacheItemVersion();
// readThruOptions set to null
var cacheItem = await this.cache.getCacheItem(key, null, version);
// If result is not null
if (cacheItem != null)
{
var prod = new Product();
prod = cacheItem.getValue(ncache.JsonDataType.Object);
prod.unitsInStock++;
// Update the retrieved cacheitem according to requirement
// Create a new cacheItem with updated value
// You also need to specify the FQN(Fully Qualified Name) of the class
var updatedItem = new ncache.CacheItem(prod,"FQN.Product");
// Set the itemversion. This version will be used to compare the
// item version of cached item
updatedItem.setCacheItemVersion(version);
await this.cache.insert(key, updatedItem);
// If it matches, the insert will be successful, otherwise it will fail
}
# Precondition: Cache is already connected
# An item is added in the cache with ItemVersion
# Specify the key of the cacheItem
key = "Product:1001"
# Initialize the cacheItemVersion
version = ncache.CacheItemVersion(0)
cache_item = cache.get_cacheitem(key, cacheitemversion=version)
# If result is not None
if cache_item is not None:
prod = cache_item.get_value(Product)
prod.set_units_in_stock(20)
# Update the retrieved cacheitem according to requirement
# Create a new CacheItem with updated value
updated_item = ncache.CacheItem(prod)
# Set the item_version. This version will be used to compare the
# item version of cached item
updated_item.set_cache_item_version(version)
cache.insert(key, updated_item)
# If it matches, the insert will be successful, otherwise it will fail
备注
为确保操作是故障安全的,建议处理应用程序中的任何潜在异常,如中所述 处理故障.
如果缓存中存在较新版本,则检索该项目
GetIfNewer
如果缓存中有更新的版本,则可以使用方法来获取现有项目。 通过指定当前版本作为方法调用的参数,缓存将返回适当的结果。
如果指定的版本小于缓存中的版本,则该方法才会返回一个新的 Item else 空 将被退回。
下面的示例使用键在缓存中添加一个项目 Product:1001
和项目版本,然后检索它(如果有任何更新的版本可用) GetIfNewer
使用缓存项目版本获取项目的方法。
// Get updated product from database against given product ID
Product product = FetchProductByProductID(1001);
// Generate a unique key for this item
string key = $"Product:{product.ProductID}";
// Create a new CacheItem
var item = new CacheItem(product);
// Add CacheItem to cache with new itemversion
CacheItemVersion version = cache.Insert(key, item);
// Get object from cache
var result = cache.GetIfNewer<Product>(key, ref version);
// Check if updated item is available
if (result != null)
{
// An item with newer version is available
if (result is Product)
{
// Perform operations according to business logic
}
}
else
{
// No new itemVersion is available
}
// Pre-condition: Cache is already connected
// Get updated product from database against given product ID
Product product = fetchProductByProductID(1001);
// Generate a unique key for this item
String key = "Product:" + product.getProductID();
// Create a new CacheItem
CacheItem item = new CacheItem(product);
// Add CacheItem to cache with new itemversion
CacheItemVersion version = cache.insert(key, item);
// Get object from cache
Object result = cache.getIfNewer(key,version,Object.class);
// Check if updated item is available
if (result != null)
{
// Perform operations according to business logic
}
else
{
// No new itemVersion is available
}
// This is an async method
// Pre-condition: Cache is already connected
// Get updated product from database against given product ID
var product = this.fetchProductByProductID(1001);
// Generate a unique key for this item
var key = "Product:" + product.getProductID();
// Create a new CacheItem
// You also need to specifyt the FQN(Fully Qualified Name) of the class
var item = new ncache.CacheItem(product,"FQN.Product");
// Add CacheItem to cache with new itemversion
var version = await this.cache.insert(key, item);
// Get object from cache
var result = await this.cache.getIfNewer(key, ncache.JsonDataType.Object, version);
// Check if updated item is available
if (result != null)
{
// Perform operations according to business logic
}
else
{
// No new itemVersion is available
}
# Pre-condition: Cache is already connected
# Get updated product from database against given product ID
product = fetch_product_from_db(1001)
# Generate a unique key for this item
key = "Product:" + product.get_product_id()
# Create a new CacheItem
item = ncache.CacheItem(product)
# Add CacheItem to cache with new itemversion
version = cache.insert(key, item)
# Get object from cache
result = cache.get_if_newer(key, cacheitemversion=version, objtype=Product)
# Check if updated item is available
if result is not None:
# Perform operations according to business logic
print("New version is available")
else:
# No new ItemVersion is available
print("New version is not available")
删除带有项目版本的项目
可以使用以下重载从缓存中删除项目 删除, 基于项目版本。 但是,如果项目版本与缓存中的版本不同,您会得到一个异常说明。
以下示例显示如何通过使用 删除 方法。
// Get updated product from database against given product ID
Product product = FetchProductByProductID(1001);
// Cache key remains the same for this product
string key = $"Product:{product.ProductID}";
// Create a new CacheItem
var item = new CacheItem(product);
// Insert CacheItem to cache with new itemversion
CacheItemVersion version = cache.Insert(key, item);
// Remove the item from the cache using the itemVersion
cache.Remove(key, null, version);
// Pre-condition: Cache is already connected
// Get updated product from database against given product ID
Product product = fetchProductByProductID(1001);
// Cache key remains the same for this product
String key = "Product:" + product.getProductID();
// Create a new CacheItem
CacheItem item = new CacheItem(product);
// Add CacheItem to cache with new itemversion
CacheItemVersion version = cache.insert(key, item);
// Remove the item from the cache using the itemVersion
//lockHandle and writeThruOptions set to null
cache.remove(key,null,version,null,Object.class);
Object value = cache.get(key,Object.class);
// Checking if item still exists in cache or not
if(value == null)
{
// Item has been removed from the cache
}
else
{
// Item is still in Cache
}
// Pre-condition: Cache is already connected
// Get updated product from database against given product ID
var product = this.fetchProductByProductID(1001);
// Cache key remains the same for this product
var key = "Product:" + product.getProductID();
// Create a new CacheItem
// You also need to specify the FQN(Fully Qualified Name) of the class
var item = new ncache.CacheItem(product,"FQN.Product");
// Add CacheItem to cache with new itemversion
var version = await this.cache.insert(key, item);
// Remove the item from the cache using the itemVersion
// lockHandle and writeThruOptions set to null
await this.cache.remove(key, ncache.JsonDataType.Object, null, version, null);
var value = await this.cache.get(key, ncache.JsonDataType.Object);
if (value == null)
{
// Item has been removed from cache
}
else
{
// Item is still in cache
}
# Pre-condition: Cache is already connected
# Get updated product from database against given product ID
product = fetch_product_from_db(1001)
# Cache key remains the same for this product
key = "Product:" + product.get_product_id()
# Create a new CacheItem
item = ncache.CacheItem(product)
# Add CacheItem to cache with new itemversion
version = cache.insert(key, item)
# Remove the item from the cache using the ItemVersion
cache.remove(key, version=version, objtype=Product)
value = cache.get(key, Product)
if value is None:
# Item has been removed from cache
print("Remove successful")
else:
# Item is still in cache
print("Remove failed")
拓扑明智的行为
在 镜子 拓扑中,当添加或更新某个项目时,会在主动节点生成其版本,然后将相同版本与该项目一起复制到被动节点,以便当主动节点变为被动节点时,项目版本保持不变。
在 已复制 拓扑中,客户端连接到一个节点,项目版本在接收客户端更新/添加操作的节点处生成,然后相同的项目版本与项目一起复制到所有其他节点以确保数据一致性。
在 分区的 拓扑中,项目版本生成并存在于包含该项目的同一节点上,并且在状态传输期间,版本也会与该项目一起传输,以防该项目移动到另一个节点。
在 分区副本 拓扑中,版本在包含项目的活动节点上生成,然后将相同版本与项目一起复制到其副本以实现数据一致性,并且在状态传输期间,版本也会与项目一起传输,以防项目移动到另一个项目节点。
在 客户端缓存,所有与版本相关的信息都保存在集群缓存中,每当调用与版本相关的 API 时,用户都会从集群缓存中获取其版本。
更多资讯
NCache 提供了项目锁定的示例应用程序 GitHub上.
参见
.NET: Alachisoft.NCache.运行时.缓存 命名空间。
Java的: COM。alachisoft.ncache.runtime.caching 命名空间。
节点.js: 缓存 类。
Python: ncache.runtime.caching 类。