1。分布式锁概述我们的系统是分布式部署的。在日常开发中,为了防止在秒下单、抢购等业务场景下库存超卖,我们需要使用分布式锁。分布式锁实际上是一种控制分布式系统中不同进程访问共享资源的锁的实现。如果不同的系统或者同一系统的不同主机共享某个关键资源,往往需要互斥来防止相互干扰,保证一致性。业界流行的分布式锁实现方式一般有以下三种:基于数据库实现的分布式锁。基于Redis实现的分布式锁。基于Zookeeper实现的分布式锁。2.基于数据库的分布式锁2.1数据库悲观锁实现的分布式锁可以使用select...forupdate来实现分布式锁。我们自己的项目,分布式定时任务,采用了类似的实现方案。让我给你看一个简单的版本!表结构如下:CREATETABLE`t_resource_lock`(`key_resource`varchar(45)COLLATEutf8_binNOTNULLDEFAULT'resourceprimarykey',`status`char(1)COLLATEutf8_binNOTNULLDEFAULT''COMMENT'S,F,P',`lock_flag`int(10)unsignedNOTNULLDEFAULT'0'COMMENT'1锁定,0解锁',`begin_time`datetimeDEFAULTNULLCOMMENT'开始时间',`end_time`datetimeDEFAULTNULLCOMMENT'结束time',`client_ip`varchar(45)COLLATEutf8_binNOTNULLDEFAULT'获得锁的IP',`time`int(10)unsignedNOTNULLDEFAULT'60'COMMENT'只允许一个节点获取一次锁在方法lifetime,unit:minute',PRIMARYKEY(`key_resource`)USINGBTREE)ENGINE=InnoDBDEFAULTCHARSET=utf8COLLATE=utf8_bin加锁方法的伪代码如下:@Transcational//mustaddtransactionpublicbooleanlock(StringkeyResource,inttime){resourceLock='select*fromt_resource_lockwherekey_resource='#{keySource}'forupdate';try{if(resourceLock==null){//插入锁数据resourceLock=newResourceLock();resourceLock.setTime(时间);资源ourceLock.setLockFlag(1);//锁定resourceLock.setStatus(P);//处理resourceLock.setBeginTime(newDate());intcount="插入资源锁";if(count==1){/}返回false;}}catch(Exceptionx){返回false;}//如果锁没有上锁且锁超时,则可以成功获取锁if(resourceLock.getLockFlag=='0'&&'S'.equals(resourceLock.getstatus)&&newDate()>=resourceLock.addDateTime(resourceLock.getBeginTime(,time)){resourceLock.setLockFlag(1);//锁定resourceLock.setStatus(P);//处理resourceLock.setBeginTime(newDate());//更新resourceLock;returntrue;}elseif(newDate()>=resourceLock.addDateTime(resourceLock.getBeginTime(,time)){//超时未正常完成,获取锁失败returnfalse;}else{returnfalse;}}伪代码解锁方法如下:publicvoidunlock(Stringv,status){resourceLock.setLockFlag(0);//解锁resourceLock.setStatus(status);S:表示成功,F表示失败//更新资源锁;return;}整体流程:try{if(lock(keyResource,time)){//lockstatus=process();//你的业务逻辑处理}}finally{unlock(keyResource,status);//释放锁}其实悲观锁实现的分布式锁的整体流程已经比较清楚了。就是select...forupdate锁定主键key_resource的记录。如果为空,则可以插入一条记录。如果存在记录,判断状态和时间,是否超时。这里需要注意,必须要添加事务。2.2数据库乐观锁实现的分布式锁除了悲观锁,还可以使用乐观锁来实现分布式锁。乐观锁,顾名思义,就是非常乐观。每次更新操作,感觉不会有并发冲突。只有更新失败后,才会重试。它是基于CAS的思想实现的。我之前的公司就是用这个方法扣余额的。做一个版本字段,每更新修改一次,就加一,然后更新余额的时候,把查到的版本号带条件更新。如果是上次的版本号,更新一下。如果没有,说明如果其他人并发修改了,继续重试。大致流程如下:查询版本号和余额。选择版本,账户余额whereuser_id='666';假设找到的版本号是oldVersion=1。判断余额的逻辑处理。if(余额<扣除金额){return;}left_balance=balance-扣除金额;扣除余额。更新账户设置balance=#{left_balance},version=version+1whereversion=#{oldVersion}andbalance>=#{left_balance}anduser_id='666';可以看看这个流程图:这种方法适用于并发量不高的场景,一般需要设置重试次数。3、基于Redis实现的分布式锁Redis分布式锁一般有以下几种实现方式:setnx+expire。setnx+value是过期时间。set的扩展命令(setexpxnx)。setexpxnx+校验唯一随机值,然后删除。雷迪森。雷迪森+红锁。3.1setnx+expire说到Redis分布式锁,很多朋友反手就是setnx+expire,如下:if(jedis.setnx(key,lock_value)==1){//setnxlockexpire(key,100);//设置过期时间try{dosomething//业务处理}catch(){}finally{jedis.del(key);//释放锁}}这段代码可以成功加锁,但是如果发现有问题,加锁操作和设置超时时间是分开的。假设执行完setnx锁后,即将执行expire设置过期时间时,进程崩溃或者需要重启维护,那么锁将是永生的,其他线程将永远无法获取到锁,所以分布式锁不能这样实现!3.2setnx+value为过期时间longexpires=System.currentTimeMillis()+expireTime;//系统时间+设置过期时间StringexpiresStr=String.valueOf(expires);//如果当前锁不存在,则返回Lock成功if(jedis.setnx(key,expiresStr)==1){returntrue;}//如果锁已经存在,则获取锁的过期时间StringcurrentValueStr=jedis.get(key);//如果获取到过期时间,小于系统当前时间,表示已经过期if(currentValueStr!=null&&Long.parseLong(currentValueStr)
