分布式锁是“线程同步”的延续。最近,“分布式锁”第一次被应用。现在想想,分布式锁并不是孤立的技能点。这实际上是跨主机线程同步。In-process,cross-process,cross-hostLock/Monitor,SemaphoreSlimMetux,Semaphoredistributedlock用户态线程安全内核态线程安全单机服务器可以通过共享一定的内存堆来标记锁定/解锁。线程同步最终是基于单机操作系统用户态/内核态对共享内存的访问控制。分布式服务器不在同一台机器上:跨主机,所以锁标签需要存放在所有机器进程都能看到的地方。锁在很多业务场景的开发中都有用到,比如库存控制,抽奖等,比如库存中只剩下一件商品,三个用户同时打算购买,谁先购买谁清零立即清点,另外两人无法成功采购。分布式锁解读我们常说的线程安全和线程同步方案,包括这个分布式锁,都是基于“多线程/多进程同时对特定资源进行更新操作”。基本考虑1.在分布式系统中,一把锁一次只能被一台服务器获取(这是分布式锁的基础)2.它有一个锁失效机制来防止死锁(防止一些意外,如果锁不释放,别人也拿不到锁)RedisSETresource-nameanystringNXEXmax-lock-time是最简单的分布式锁实现。SET命令支持多个参数:EXseconds--设置过期时间(s)NX--如果key不存在则设置...因为SET命令参数可以替代SETNX、SETEX、GETSET,这些命令可能用在未来被废弃了。上述命令返回OK(或重试后),客户端获取到锁;使用DEL命令解锁;当达到超时时,锁将自动释放。在解锁的时候,加入一些设计,让系统更加健壮:3.不要使用一个固定的String值作为locktag的值,而是使用一个不容易被猜到的随机值,业界称之为token。4.不要使用DEL命令解除锁定,而是发送脚本来移除密钥。第3点和第4点是解决:“锁提前过期,客户端A还没有执行完,然后客户端B获取了锁,这时候客户端A已经执行完了,会不会在删除锁的时候,删除B的锁”——4是技术上推荐的3实现。脚本如下:ifredis.call("get",KEYS1]==ARGV[1])thenreturnredis.call("DEL",KEYS[1])elsereturn0end下面使用StackExchange.Redis写的代码示例基于以上考虑://////Acquiresthelock./////////随机值//////非阻塞锁staticboolLock(stringkey,stringtoken,intexpireSecond=10,doublewaitLockSeconds=0){varwaitIntervalMs=50;boolisLock;DateTimebegin=DateTime.Now;do{isLock=Connection.GetDatabase().StringSet(key,token,TimeSpan.FromSeconds(expireSecond),When.NotExists);if(isLock)returntrue;//如果没有等待锁,返回if(waitLockSeconds==0)break;//如果超过等待时间,不再等待if((DateTime.Now-begin).TotalSeconds>=waitLockSeconds)break;Thread.Sleep(waitIntervalMs);}while(!isLock);returnfalse;}//////Releasesthelock.//////true</c>,iflockwasreleased,false否则.///Key.///valuestaticboolUnLock(stringkey,stringvalue){stringlua_script=@"if(redis.call('GET',KEYS[1])==ARGV[1])thenredis.call('DEL',KEYS[1])returntrueelsereturnfalseend";try{varres=Connection.GetDatabase().ScriptEvaluate(lua_script,newRedisKey[]{key},newRedisValue[]{value});return(bool)res;}catch(Exceptionex){Console.WriteLine($"ReleaseLocklockfail...{ex.Message}");returnfalse;}}privatestaticLazylazyConnection=newLazy(()=>{ConfigurationOptionsconfiguration=newConfigurationOptions{AbortOnConnectFail=false,ConnectTimeout=5000,};configuration.EndPoints.Add("10.100.219.9",6379);returnConnectionMultiplexer.Connect(configuration.ToString());});publicstaticConnectionMultiplexerConnection=>lazyConnection.Value;上面代码增加了第五点考虑:5.为了避免无限制的抢锁,增加了一个非阻塞锁:poll_s等待锁,没有等待就不再抢锁。用法:在减少库存的同时在任务下面打开三个并行锁:staticvoidMain(string[]args){//尝试并行执行3个任务Parallel.For(0,3,x=>{stringtoken=$"loki:{x}";boolisLocked=Lock("loki",token,5,10);if(isLocked){Console.WriteLine($"{token}beginreducestocks(withlock)at{DateTime.Now}。");Thread.Sleep(1000);Console.WriteLine($"{token}releaselock{UnLock("loki",token)}at{DateTime.Now}。");}else{Console.WriteLine($"{token}beginreducestocksat{DateTime.Now}.");}});}可以看到三个并行任务依次获取/释放锁输出。总结本文从基本的线程安全和线程同步认识到分布式锁是一种跨主机资源线程/进程同步的解决方案,并以循序渐进的方式演示了RedisSET。命令的分布式锁设计注意事项,好记性不如烂笔头