接口代码
protected readonly StackExchange.Redis.IDatabase _redisDb;
protected readonly IAbpRedisCacheDatabaseProvider _abpRedisCacheDatabaseProvider;
_redisDb = _abpRedisCacheDatabaseProvider.GetDatabase();
public async Task Update(EntityDto<long> input)
{
//创建锁信息,如果需要提升性能,可以把这个Id分成多个key(分段锁)
var lockInfo = new LockInfo(ToDoItemConsts.LockKey + input.Id.ToString());
while (!await _redisDb.LockTakeAsync(lockInfo.Key, lockInfo.Value, TimeSpan.FromSeconds(lockInfo.Expiry)))
{
await Task.Delay(TimeSpan.FromMilliseconds(RandomHelper.GetRandom(10, 300)));
// TODO: 超过等待时间,则不再等待
// if ((DateTime.Now - begin).TotalSeconds >= waitLockSeconds) break;
}
//锁续约(1/3过期时间为周期,执行锁续约代码)
var timer = new Timer(new TimerCallback(DoExtend), lockInfo, TimeSpan.Zero, TimeSpan.FromSeconds(lockInfo.Expiry / 3));
//try 防止执行异常,没有释放锁
try
{
//锁续约测试
await Task.Delay(TimeSpan.FromSeconds(lockInfo.Expiry + 1));
var stock = 0;
var stockKey = ToDoItemConsts.Stock + input.Id.ToString();
var redisStock = await _redisDb.StringGetAsync(stockKey);
if (redisStock.IsNull)//redis不存在,就到数据库找
{
var toDoItem = await _dbContext.Set<ToDoItem>().FirstOrDefaultAsync(x => x.Id == input.Id);
stock = toDoItem.Count;
}
else
{
stock = (int)redisStock;
}
if (stock > 0)
{
stock--;
await _redisDb.StringSetAsync(stockKey, stock);
}
else
{
throw new UserFriendlyException("库存不足!");
}
}
catch (Exception)
{
throw;
}
finally
{
//只能释放自己加的锁,因为锁有过期时间,有可能在自己释放前,锁过期了,导致别人加了锁。(可以用锁续命防止锁过期 LockExtendAsync)
//确保锁释放是原子操作
await _redisDb.LockReleaseAsync(lockInfo.Key, lockInfo.Value);
timer.Dispose();
}
}
private void DoExtend(object stateInfo)
{
if (!(stateInfo is LockInfo))
throw new UserFriendlyException($"锁续约出错:{stateInfo}");
//TODO 如果大于循环时间
var arg = (LockInfo)stateInfo;
//将过期时间重置为arg.Expiry
_redisDb.LockExtend(arg.Key, arg.Value, TimeSpan.FromSeconds(arg.Expiry));
Console.WriteLine(DateTime.Now + arg.ToString());
}
外部类代码
public class LockInfo
{
public string Key { get; set; }
/// <summary>
/// 生成一个随机值guid用于释放锁的判断
/// </summary>
public string Value { get; set; } = Guid.NewGuid().ToString();
/// <summary>
/// second
/// </summary>
public int Expiry { get; set; } = 9;
public LockInfo(string key)
{
Key = key ?? throw new ArgumentNullException(nameof(key));
}
public override string ToString() => $" Key:{Key},Value:{Value},Expiry:{Expiry}";
}