net core 使用redis 分布式锁
电脑版发表于:2024/3/20 13:40
在电商项目中,必定会与订单打交道。订单中必定会涉及到扣库存,但是在高并发项目中,库存余量不能及时刷新,
导致库存扣除得不正确,1000个并发请求下来,扣除的库存只有几十个。如何解决此问题,那就得加锁了。
还有其他解决办法也能处理此问题,本文只分享一下redis 分布式锁解决此问题。
第一步添加一个redis的获取锁,与释放锁的方法。在获取锁时,如果未获取到锁,那么就会尝试重复获取,
直到获取倒锁后,才返回。当然在这步你可以加一个超时时间,例如最多尝试获取30秒,避免释放锁异常在此一直死循环。
释放锁时只有锁的拥有者才能释放锁,避免锁被其他线程释放了,这个分布式锁就没有意义了。
public class RedisDistributedLock { private readonly ConnectionMultiplexer _redis; private readonly IDatabase _database; public RedisDistributedLock(ConnectionMultiplexer redis) { _redis = redis; _database = redis.GetDatabase(); } public async Task<bool> AcquireLockAsync(string lockKey, string value, TimeSpan lockTimeout) { var acquired = await _database.StringSetAsync(lockKey, value, lockTimeout, When.NotExists); if (!acquired) { // 循环重试直到成功或超时 var startTime = DateTime.UtcNow; while (true) { Thread.Sleep(10); // 等待一段时间后再重试 if (await _database.StringSetAsync(lockKey, value, lockTimeout, When.NotExists)) { acquired = true; // 成功获取到锁 break; } } } return acquired; } public async Task ReleaseLockAsync(string lockKey, string value) { //验证锁的拥有者才能释放锁 string currentValue = await _database.StringGetAsync(lockKey); if (currentValue == value) { await _database.KeyDeleteAsync(lockKey); } } }
订单扣库存场景
private readonly ConnectionMultiplexer _connectionMultiplexer; //创建一个redis锁 private readonly RedisDistributedLock _lockHelper; private readonly string _lockKey = "inventory_lock:OrderSubmit"; // 锁的唯一标识 public OrdersService(IFreeSql freeSql, ICacheService cacheService, ConnectionMultiplexer connectionMultiplexer) { _freeSql = freeSql; _cacheService = cacheService; _connectionMultiplexer = connectionMultiplexer; _lockHelper = new RedisDistributedLock(_connectionMultiplexer); } public async Task<CreateOrdersResult> OrderSubmitAsync(CreateOrderModel arg) { var orderStatus = OrderStatus.Fail; var message = string.Empty; var lockValue = $"lock_{CodeHelper.CreateGuid()}"; const int maxRetries = 5; TimeSpan retryDelay = TimeSpan.FromMilliseconds(100); if (await _lockHelper.AcquireLockAsync(_lockKey, lockValue, TimeSpan.FromSeconds(10))) { try { var oldInventory = await _freeSql.Select<Commodity>().Where(a => a.Code == arg.CommodityCode).FirstAsync(); if (oldInventory.Inventory >= arg.OrderQuantity && oldInventory.Inventory > 0) { var repo = _freeSql.GetRepository<Commodity>(); oldInventory.Inventory = oldInventory.Inventory - arg.OrderQuantity; await repo.UpdateAsync(oldInventory); orderStatus = OrderStatus.Success; message = "下单成功"; } else { message = "下单失败,库存不足"; } } catch (Exception e) { message = "下单失败," + e.Message; } finally { // 无论成功还是失败,最后都要释放锁 await _lockHelper.ReleaseLockAsync(_lockKey, lockValue); } } var ordersRepo = _freeSql.GetRepository<Orders>(); await ordersRepo.InsertAsync(new Orders() { Code = CodeHelper.CreateOrdersCode(), UserCode = arg.UserCode, CommodityCode = arg.CommodityCode, OrderTime = DateTime.Now, OrderAmount = arg.OrderAmount, OrderQuantity = arg.OrderQuantity, OrderStatus = orderStatus }); return new CreateOrdersResult() { Status = true, Message = message }; }