当今的企业正在开发为数以万计的并发用户提供服务的高流量 ASP.NET Web 应用程序。 多个客户端可以访问集群环境中的缓存数据,其中应用程序服务器部署在负载平衡的环境中。 在这样的并行条件下,多个用户经常尝试访问和修改相同的数据并触发竞争条件。
竞争条件是两个或多个用户尝试同时访问和更改相同的共享数据,但最终以错误的顺序进行。 这种情况会导致丢失数据完整性和一致性的高风险。 随着内存中的出现,可扩展的缓存解决方案,如 NCache 提供分布式锁机制,企业可以实现数据一致性的显着增强。
NCache 更多信息 锁定和控制文档 NCache 配套文档
用于数据一致性的分布式锁定
NCache 提供分布式机制 锁定 在 .NET 中,它允许您在并发更新期间锁定特定的缓存项。 为了在这种情况下保持数据一致性, NCache 充当分布式锁管理器,并为您提供两种类型的锁定:
我们将在稍后的博客中详细讨论它们。 现在,考虑以下场景,以了解没有分布式锁定服务是如何违反数据完整性的。
两个用户同时访问同一个银行账户,余额为 30,000。 一位用户提取 15,000,而另一用户存入 5,000。 如果操作正确,最终余额应为 20,000。 相反,如果出现未处理的竞争条件,则银行余额将是 15,000 或 35,000,如上所示。
以下是这种竞争条件的发生方式:
- 时间t1: 用户 1 获取余额 = 30,000 的银行账户
- 时间t2: 用户 2 获取余额 = 30,000 的银行账户
- 时间t3: 用户 1 提取 15,000 并更新银行账户余额 = 15,000
- 时间t4: 用户 2 存入 5,000 并更新银行账户余额 = 35,000
在这两种情况下,不适合管理线程的代码块对银行来说可能是灾难性的。 那么,在接下来的部分中,让我们看看如何 NCache 提供锁定机制以确保您的应用程序逻辑是线程安全的。
乐观锁定(项目版本)
In 乐观锁, NCache 使用 缓存项版本控制. 在服务器端,每个缓存对象都有一个与之关联的版本号,该版本号会在每次缓存项更新时递增。 NCache 然后检查您是否正在使用最新版本。 如果不是,它会拒绝您的缓存更新。 这样,只有一个用户可以更新,其他用户更新失败。
- add 方法在缓存中添加一个新项目,并首次保存项目版本。
- insert 方法覆盖现有项目的值并更新其项目版本。
看看下面的代码示例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
// Pre-condition: 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(); prod.UnitsInStock++; // Create a new cacheItem with updated value var updateItem = new CacheItem(prod); CacheItemVersion 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 } |
在上面的示例中,两个不同的应用程序使用一个包含产品数据的缓存。 CacheItem 被添加到缓存中。 两个应用程序都使用当前版本获取项目,假设 版本. Application1 修改 产品名称 然后将项目重新插入缓存中,将其项目版本更新为 新版本. Application2 仍然有项目 版本. 如果应用程序 2 更新商品的库存单位并在缓存中重新插入商品,商品插入将失败。 因此,Application2 必须获取更新的版本才能对该 cacheItem 进行操作。
但是,如果您希望获取缓存中可能有更新版本的现有项目, NCache 提供了一个 获取如果更新 方法。 如果您将当前版本指定为方法调用的参数,则缓存会返回适当的结果。 如果特定版本小于缓存中的版本,则该方法返回一个新项目,否则将返回 null。
让我们看一下下面的代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// Get object from cache var result = cache.GetIfNewer(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 } |
上面的示例在缓存中添加了一个项目,其键为 Product:1001 和项目版本。 如果有任何更新版本的项目可用, 获取如果更新 方法使用缓存项目版本获取项目。
使用乐观锁定, NCache 确保对分布式缓存的每次写入都与每个应用程序持有的版本一致。 请参考我们的 官方 NCache 配套文档 有关广泛的代码示例。
悲观锁定(排他锁定)
确保数据一致性的另一种方法是获取缓存数据的排他锁。 这种机制称为 悲观锁 . 它使用锁定句柄锁定项目,阻止所有其他用户对该缓存项目执行任何写入操作。 一个 锁柄 是与锁定 API 返回的缓存中的每个锁定项相关联的句柄。
以下示例创建一个 LockHandle,然后使用密钥 Product:1001 锁定项目 10 秒的时间跨度,以便项目将在 10 秒后自动解锁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
// Pre-Requisite: Cache is already connected // Item is already added in the cache // Specify the key of the item string key = $"Product:1001"; //Create a new LockHandle LockHandle lockHandle = null; // Specify time span of 10 seconds for which the item remains locked TimeSpan lockSpan = TimeSpan.FromSeconds(10); // Lock the item for a time span of 10 seconds bool lockAcquired = cache.Lock(key, lockSpan, out lockHandle); // Verify if the item is locked successfully if (lockAcquired == true) { // Item has been successfully locked } else { // Key does not exist // Item is already locked with a different LockHandle } |
在获取项目时成功获取锁后,应用程序现在可以安全地执行操作,因为只要您拥有此锁,其他应用程序就无法获取或更新此项目。 我们将调用具有相同锁句柄的 Insert API 来更新数据并释放锁。 这样做将在一次调用中将数据插入缓存并释放锁,从而使缓存的数据可用于所有其他应用程序。
请记住,您应该在超时后获取所有锁。 默认情况下,如果没有指定超时时间, NCache 将无限期锁定该项目, 时间跨度.零. 如果应用程序在未释放锁定的情况下崩溃,则可能存在项目可以永远保持锁定的情况。 对于解决方法,您可以 强行释放 它,但这种做法是不明智的。 因此,将项目锁定为最小 TimeSpan 以避免死锁或线程饥饿。
分布式锁定中的故障转移支持
自 NCache 是内存中的, 分布式缓存,它还提供完整的故障转移支持,因此不会丢失数据并且具有高可用性。 如果发生服务器故障,您的客户端应用程序将继续无缝运行。 同样,您在分布式系统中的锁也由复制节点复制和维护。 如果在您的一个应用程序获取锁时任何节点发生故障,它将自动传播到具有其指定属性的新节点,例如, 锁定到期.
结论
那么,哪种锁定机制最适合您,乐观还是悲观? 好吧,这取决于您的用例和您想要实现的目标。 与悲观锁定相比,乐观锁定提供了更高的性能优势,尤其是当您的应用程序是读取密集型时。 然而,从数据一致性的角度来看,悲观锁定更安全。 因此,请仔细选择您的锁定机制。 更多详情,请前往 官网. 如有任何问题, 立即联系我们 让我们的专家帮助您!