为什么要用锁?
大型站点在高并发的情况下,为了保持数据最终一致性就需要用到技术方案来支持。比如:分布式锁、分布式事务。有时候我们在为了保证某一个方法每次只能被一个调用者使用的时候,这时候我们也可以锁来实现。
基于本地缓存实现锁
为什么还要写基于本地缓存实现的锁呢,因为有些项目项目可能还是单机部署的,当随着业务量增长的时候就会变成多机部署,从单机到多机的切换过程中,我们也需要把原先业务相关的锁改成分布式锁,来保持数据的最终一致性。当然项目是使用ioc的那就更好了,切换注册时的实现类就完成了切换,非常方便。
实现思路:
用户需要用一个key和一个唯一的值(知道当前这个key的使用者是谁)来获取一个锁,获取到锁之后,执行完对应的操作然后释放掉。在释放锁的时候 4我们需要判断下当前这个锁的使用者对应的值与想要释放传递过来的值是不是相等,如果相等则可以释放,不相等则不释放。
实现代码:
public sealed class LocalLock : ILock { private static ConcurrentDictionary<string, object> _LockCache = new ConcurrentDictionary<string, object>(); private static ConcurrentDictionary<string, string> _LockUserCache = new ConcurrentDictionary<string, string>(); /// <summary> /// 获取一个锁(需要自己释放) /// </summary> /// <param name="key">锁的键</param> /// <param name="value">当前占用值</param> /// <param name="span">耗时时间</param> /// <returns>成功返回true</returns> public bool LockTake(string key, string value, TimeSpan span) { EnsureUtil.NotNullAndNotEmpty(key, "Lockkey"); EnsureUtil.NotNullAndNotEmpty(value, "Lockvalue"); var obj = _LockCache.GetValue(key, () => { return new object(); }); if (Monitor.TryEnter(obj, span)) { _LockUserCache[key] = value; return true; } return false; } /// <summary> /// 异步获取一个锁(需要自己释放) /// </summary> /// <param name="key">锁的键</param> /// <param name="value">当前占用值</param> /// <param name="span">耗时时间</param> /// <returns>成功返回true</returns> public Task<bool> LockTakeAsync(string key, string value, TimeSpan span) { return Task.FromResult(LockTake(key, value, span)); } /// <summary> /// 释放一个锁 /// </summary> /// <param name="key">锁的键</param> /// <param name="value">当前占用值</param> /// <returns>成功返回true</returns> public bool LockRelease(string key, string value) { EnsureUtil.NotNullAndNotEmpty(key, "Lockkey"); EnsureUtil.NotNullAndNotEmpty(value, "Lockvalue"); _LockCache.TryGetValue(key, out object obj); if (obj != null) { if (_LockUserCache[key] == value) { Monitor.Exit(obj); return true; } return false; } return true; } /// <summary> /// 异步释放一个锁 /// </summary> /// <param name="key">锁的键</param> /// <param name="value">当前占用值</param> /// <returns>成功返回true</returns> public Task<bool> LockReleaseAsync(string key, string value) { return Task.FromResult(LockRelease(key, value)); } /// <summary> /// 使用锁执行一个方法 /// </summary> /// <param name="key">锁的键</param> /// <param name="value">当前占用值</param> /// <param name="span">耗时时间</param> /// <param name="executeAction">要执行的方法</param> public void ExecuteWithLock(string key, string value, TimeSpan span, Action executeAction) { if (executeAction == null) return; if (LockTake(key, value, span)) { try { executeAction(); } finally { LockRelease(key, value); } } } /// <summary> /// 使用锁执行一个方法 /// </summary> /// <typeparam name="T">返回值类型</typeparam> /// <param name="key">锁的键</param> /// <param name="value">当前占用值</param> /// <param name="span">耗时时间</param> /// <param name="executeAction">要执行的方法</param> /// <param name="defaultValue">默认返回</param> /// <returns></returns> public T ExecuteWithLock<T>(string key, string value, TimeSpan span, Func<T> executeAction, T defaultValue = default(T)) { if (executeAction == null) return defaultValue; if (LockTake(key, value, span)) { try { return executeAction(); } finally { LockRelease(key, value); } } return defaultValue; } /// <summary> /// 使用锁执行一个异步方法 /// </summary> /// <param name="key">锁的键</param> /// <param name="value">当前占用值</param> /// <param name="span">耗时时间</param> /// <param name="executeAction">要执行的方法</param> public async Task ExecuteWithLockAsync(string key, string value, TimeSpan span, Func<Task> executeAction) { if (executeAction == null) return; if (await LockTakeAsync(key, value, span)) { try { await executeAction(); } catch { throw; } finally { LockRelease(key, value); } } } /// <summary> /// 使用锁执行一个异步方法 /// </summary> /// <typeparam name="T">返回值类型</typeparam> /// <param name="key">锁的键</param> /// <param name="value">当前占用值</param> /// <param name="span">耗时时间</param> /// <param name="executeAction">要执行的方法</param> /// <param name="defaultValue">默认返回</param> /// <returns></returns> public async Task<T> ExecuteWithLockAsync<T>(string key, string value, TimeSpan span, Func<Task<T>> executeAction, T defaultValue = default(T)) { if (executeAction == null) return defaultValue; if (await LockTakeAsync(key, value, span)) { try { return await executeAction(); } catch { throw; } finally { LockRelease(key, value); } } return defaultValue; } }
class Program { static void Main(string[] args) { ILock localLock = new LocalLock(); int excuteCount = 0; Parallel.For(0, 10000, i => { localLock.ExecuteWithLock("test", Guid.NewGuid().ToString(), TimeSpan.FromSeconds(5), () => { Console.WriteLine("获取锁成功"); Interlocked.Increment(ref excuteCount); }); }); Console.WriteLine("成功次数:" + excuteCount.ToString()); Console.WriteLine("执行完成"); Console.ReadLine(); } }
基于zk实现的分布式锁
实现思路:
在获取锁的时候在固定节点下创建一个自增的临时节点,然后获取节点列表,按照增量排序,假如当前创建的节点是排在第一个的,那就表明这个节点是得到了执行的权限,假如在它前面还有其它节点,那么就对它的上一个节点进行监听,等到上一个节点被删除了,那么该节点就得到了执行的权限了。
由于代码片段太多,待会再github自行查看实现过程。zk获取锁的速度比较慢,导致有几个可能是失败的。
基于redis的实现
实现思路:
利用redis的setnx(key, value):“set if not exits”,若该key-value不存在,则成功加入缓存并且返回1,否则返回0。在有效时间内如果设置成功则获取执行限权,没有那就获取权限失败。
对比下会发现redis的执行效率会比zk的快一点。
项目下载地址:https://github.com/ProjectSharing/Lock