在电商项目中,必定会与订单打交道。订单中必定会涉及到扣库存,但是在高并发项目中,库存余量不能及时刷新,导致库存扣除得不正确,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
};
}