各大网站大全,南昌优易科 网站建设,网站建设的基本条件,小程序模板多少钱目录 1. 分布式锁的概念
2.基于数据库做分布式锁
2.1 基于表主键唯一做分布式锁
2.2 基于表字段版本号做分布式锁
2.3 基于数据库排他锁做分布式锁
3.使用Redis做分布式锁
3.1 redis实现分布式锁的基本原理
3.2 问题一#xff1a;增加超时机制#xff0c;防止长期持有…目录 1. 分布式锁的概念
2.基于数据库做分布式锁
2.1 基于表主键唯一做分布式锁
2.2 基于表字段版本号做分布式锁
2.3 基于数据库排他锁做分布式锁
3.使用Redis做分布式锁
3.1 redis实现分布式锁的基本原理
3.2 问题一增加超时机制防止长期持有的情况
3.3 问题2重入的问题
3.4 问题三优化轮询加锁的问题
3.4 总结 1. 分布式锁的概念
从现在开始我们来研究分布式锁相关的问题。在单机环境下我们可以使用JDK提供的大量工具来完成线程安全与高并发相关的任务这其中最重要的一个就是锁。
在单进程的系统中当存在多个线程可以同时改变某个变量可变共享变量时就需要对变量或代码块做同步使其在修改这种变量时能够线性执行消除并发修改变量。而同步的本质是通过锁来实现的。为了实现多个线程在一个时刻同一个代码块只能有一个线程可执行那么需要在某个地方做个标记这个标记必须每个线程都能看到当标记不存在时可以设置该标记其余后续线程发现已经有标记了则等待拥有标记的线程结束同步代码块取消标记后再去尝试设置标记。这个标记可以理解为锁。
不同地方实现锁的方式也不一样只要能满足所有线程都能看得到标记即可。如 Java 中 synchronize 是在对象头设置标记JUC包里的Lock 接口的实现类基本上都只是某一个 volitile 修饰的 int 型变量其保证每个线程都能拥有对该 int 的可见性和原子修改linux 内核中也是利用互斥量或信号量等内存数据做标记。
除了利用内存数据做锁其实任何互斥的都能做锁只考虑互斥情况如流水表中流水号与时间结合做幂等校验可以看作是一个不会释放的锁或者使用某个文件是否存在作为锁等。只需要满足在对标记进行修改能保证原子性和内存可见性即可。
如果我们思考一下如果是在分布式场景下又该如何实现共享变量的安全访问呢很多时候我们需要保证一个方法在同一时间内只能被同一个线程执行。在单机环境中通过 Java 提供的并发 API 我们可以解决但是在分布式环境下就没有那么简单啦。其中最典型的问题是
分布式与单机情况下最大的不同在于其不是多线程而是多进程。多线程由于可以共享堆内存因此可以简单的采取内存作为标记存储位置。而进程之间甚至可能都不在同一台物理机上因此需要将标记存储在一个所有进程都能看到的地方。
为此我们就需要来设计分布式环境下可以保证资源安全访问的锁。具体来说应该满足如下几个要求
在分布式模型下数据只有一份或有限制此时需要利用锁的技术控制某一时刻修改数据的进程数。与单机模式下的锁不仅需要保证进程可见还需要考虑进程与锁之间的网络问题。分布式情况下之所以问题变得复杂主要就是需要考虑到网络的延时和不可靠。分布式锁还是可以将标记存在内存只是该内存不是某个进程分配的内存而是公共内存如 Redis、Memcache。至于利用数据库、文件等做锁与单机的实现是一样的只要保证标记能互斥就行。
该如何实现分布式锁呢在Java领域主要有三个组件可以帮助我们实现Mysql数据库、redis和Zookeeper。我们分别来看如何使用这三个组件实现分布式锁。
2.基于数据库做分布式锁
我们知道即使在复杂的微服务环境下我们最终要访问的数据只会在一个地方例如某个学生某个时间学习某课的记录一定是在一个表里的(不考虑主备和异地保活的因素)而不会将这个数据存放在好几个地方因此不管有几个请求只要最后在访问这条记录的时候我们实现安全访问 其他的请求等待或者拒绝掉就可以做到分布式锁的效果。
这就是数据库可以做分布式锁的核心思想具体来说根据数据库表的情况我们又有多种方案。
2.1 基于表主键唯一做分布式锁
利用主键唯一的特性如果有多个请求同时提交到数据库的话数据库会保证只有一个操作可以成功那么我们就可以认为操作成功的那个线程获得了该方法的锁当方法执行完毕之后想要释放锁的话删除这条数据库记录即可。
上面这种简单的实现有以下几个问题
这把锁强依赖数据库的可用性数据库是一个单点一旦数据库挂掉会导致业务系统不可用。这把锁没有失效时间一旦解锁操作失败就会导致锁记录一直在数据库中其他线程无法再获得到锁。这把锁只能是非阻塞的因为数据的 insert 操作一旦插入失败就会直接报错。没有获得锁的线程并不会进入排队队列要想再次获得锁就要再次触发获得锁操作。这把锁是非重入的同一个线程在没有释放锁之前无法再次获得该锁。因为数据中数据已经存在了。这把锁是非公平锁所有等待锁的线程凭运气去争夺锁。在 MySQL 数据库中采用主键冲突防重在大并发情况下有可能会造成锁表现象。
当然我们也可以有其他方式解决上面的问题。
数据库是单点搞两个数据库数据之前双向同步一旦挂掉快速切换到备库上。没有失效时间只要做一个定时任务每隔一定时间把数据库中的超时数据清理一遍。非阻塞的搞一个 while 循环直到 insert 成功再返回成功。非重入的在数据库表中加个字段记录当前获得锁的机器的主机信息和线程信息那么下次再获取锁的时候先查询数据库如果当前机器的主机信息和线程信息在数据库可以查到的话直接把锁分配给他就可以了。非公平的再建一张中间表将等待锁的线程全记录下来并根据创建时间排序只有最先创建的允许获取锁。比较好的办法是在程序中生产主键进行防重。
不过整体来看在复杂场景下这个分布式锁会非常脆弱很容易出问题。
2.2 基于表字段版本号做分布式锁
这个策略源于 mysql 的 mvcc 机制使用这个策略其实本身没有什么问题唯一的问题就是对数据表侵入较大我们要为每个表设计一个版本号字段然后写一条判断 sql 每次进行判断增加了数据库操作的次数但是在高并发的要求下这会严重影响性能对数据库连接的开销也是无法忍受的。
2.3 基于数据库排他锁做分布式锁
在查询语句后面增加for update数据库会在查询过程中给数据库表增加排他锁 (注意 InnoDB 引擎在加锁的时候只有通过索引进行检索的时候才会使用行级锁否则会使用表级锁。这里我们希望使用行级锁就要给要执行的方法字段名添加索引值得注意的是这个索引一定要创建成唯一索引否则会出现多个重载方法之间无法同时被访问的问题。重载方法的话建议把参数类型也加上。当某条记录被加上排他锁之后其他线程无法再在该行记录上增加排他锁。
我们可以认为获得排他锁的线程即可获得分布式锁当获取到锁之后可以执行方法的业务逻辑执行完方法之后通过connection.commit()操作来释放锁。
这种方法可以有效的解决上面提到的无法释放锁和阻塞锁的问题。
这种锁是阻塞锁for update语句会在执行成功后立即返回在执行失败时一直处于阻塞状态直到成功。这种锁定之后服务宕机是否会出现无法释放的问题呢使用这种方式服务宕机之后数据库会自己把锁释放掉。
所以这种方式比较简单在实际工程里是很多应用的。例如在转账的时候我们需要先从A扣减10然后再给B加10此时就可以使用for update保证这个操作要么一定完成要么都不执行。
这里可能有一个问题就是我们要使用排他锁来进行分布式锁的 lock那么一个排他锁长时间不提交就会占用数据库连接。一旦类似的连接变得多了就可能把数据库连接池撑爆。
而且这种方式还是无法直接解决数据库单点和可重入问题适合处理比较简单的分布式锁问题。
优点简单易于理解
缺点会有各种各样的问题操作数据库需要一定的开销使用数据库的行级锁并不一定靠谱性能不靠谱
3.使用Redis做分布式锁
这个是我们后面要重点学习方式做分布式锁是redis的一大强项。
这篇文章写的不错我们直接参考啦
3.1 redis实现分布式锁的基本原理
我们主要使用redis的setnx命令来实现。setnx是SET if not exists(如果不存在则 SET)的简写。
先看一个例子 127.0.0.1:6379 setnx lock value1 #在键lock不存在的情况下将键key的值设置为value1 (integer) 1 127.0.0.1:6379 setnx lock value2 #试图覆盖lock的值返回0表示失败 (integer) 0 127.0.0.1:6379 get lock #获取lock的值验证没有被覆盖 value1 127.0.0.1:6379 del lock #删除lock的值删除成功 (integer) 1 127.0.0.1:6379 setnx lock value2 #再使用setnx命令设置返回0表示成功 (integer) 1 127.0.0.1:6379 get lock #获取lock的值验证设置成功 value2 上面这几个命令就是最基本的用来完成分布锁的命令。
加锁使用setnx key value命令如果key不存在设置value(加锁成功)。如果已经存在lock(也就是有客户端持有锁了)则设置失败(加锁失败)。
解锁使用del命令通过删除键值释放锁。释放锁之后其他客户端可以通过setnx命令进行加锁。
key的值可以根据业务设置比如是用户中心使用的可以命令为USER_REDIS_LOCKvalue可以使用uuid保证唯一用于标识加锁的客户端。保证加锁和解锁都是同一个客户端。
那么接下来就可以写一段很简单的加锁代码
private static Jedis jedis new Jedis(127.0.0.1);private static final Long SUCCESS 1L;/*** 加锁*/
public boolean tryLock(String key, String requestId) {//使用setnx命令。//不存在则保存返回1加锁成功。如果已经存在则返回0加锁失败。return SUCCESS.equals(jedis.setnx(key, requestId));
}//删除key的lua脚本先比较requestId是否相等相等则删除
private static final String DEL_SCRIPT if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end;/*** 解锁*/
public boolean unLock(String key, String requestId) {//删除成功表示解锁成功Long result (Long) jedis.eval(DEL_SCRIPT, Collections.singletonList(key), Collections.singletonList(requestId));return SUCCESS.equals(result);
}
上面代码加锁和解锁的基本原理是 3.2 问题一增加超时机制防止长期持有的情况
上面过程其实仍然是有问题的上面的过程仅仅满足上述的第一个条件和第三个条件保证上锁和解锁都是同一个客户端也保证只有一个客户端持有锁。
但是第二点没法保证因为如果一个客户端持有锁的期间突然崩溃了就会导致无法解锁最后导致出现死锁的现象。 所以要有个超时的机制在设置key的值时需要加上有效时间如果有效时间过期了就会自动失效就不会出现死锁。然后加锁的代码就会变成这样。
public boolean tryLock(String key, String requestId, int expireTime) {//使用jedis的api保证原子性//NX 不存在则操作 EX 设置有效期单位是秒String result jedis.set(key, requestId, NX, EX, expireTime);//返回OK则表示加锁成功return OK.equals(result);
}3.3 问题2重入的问题
但是聪明的同学肯定会问有效时间设置多长假如我的业务操作比有效时间长我的业务代码还没执行完就自动给我解锁了不就完蛋了吗。
这个问题就有点棘手了在网上也有很多讨论第一种解决方法就是靠程序员自己去把握预估一下业务代码需要执行的时间然后设置有效期时间比执行时间长一些保证不会因为自动解锁影响到客户端业务代码的执行。
但是这并不是万全之策比如网络抖动这种情况是无法预测的也有可能导致业务代码执行的时间变长所以并不安全。
有一种方法比较靠谱一点就是给锁续期。在Redisson框架实现分布式锁的思路就使用watchDog机制实现锁的续期。当加锁成功后同时开启守护线程默认有效期是30秒每隔10秒就会给锁续期到30秒只要持有锁的客户端没有宕机就能保证一直持有锁直到业务代码执行完毕由客户端自己解锁如果宕机了自然就在有效期失效后自动解锁。 但是聪明的同学可能又会问你这个锁只能加一次不可重入。可重入锁意思是在外层使用锁之后内层仍然可以使用那么可重入锁的实现思路又是怎么样的呢
在Redisson实现可重入锁的思路使用Redis的哈希表存储可重入次数当加锁成功后使用hset命令value(重入次数)则是1。
if (redis.call(exists, KEYS[1]) 0) then
redis.call(hset, KEYS[1], ARGV[2], 1);
redis.call(pexpire, KEYS[1], ARGV[1]);
return nil;
end; 如果同一个客户端再次加锁成功则使用hincrby自增加一。
if (redis.call(hexists, KEYS[1], ARGV[2]) 1) then
redis.call(hincrby, KEYS[1], ARGV[2], 1);
redis.call(pexpire, KEYS[1], ARGV[1]);
return nil;
end;
return redis.call(pttl, KEYS[1]); 解锁时先判断可重复次数是否大于0大于0则减一否则删除键值释放锁资源。
protected RFutureBoolean unlockInnerAsync(long threadId) {return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
if (redis.call(hexists, KEYS[1], ARGV[3]) 0) then
return nil;
end;
local counter redis.call(hincrby, KEYS[1], ARGV[3], -1);
if (counter 0) then
redis.call(pexpire, KEYS[1], ARGV[2]);
return 0;
else
redis.call(del, KEYS[1]);
redis.call(publish, KEYS[2], ARGV[1]);
return 1;
end;
return nil;,
Arrays.ObjectasList(getName(), getChannelName()), LockPubSub.UNLOCK_MESSAGE, internalLockLeaseTime, getLockName(threadId));
} 3.4 问题三优化轮询加锁的问题
上面的加锁方法是加锁后立即返回加锁结果如果加锁失败的情况下总不可能一直轮询尝试加锁直到加锁成功为止这样太过耗费性能。所以需要利用发布订阅的机制进行优化。
步骤如下
当加锁失败后订阅锁释放的消息自身进入阻塞状态。当持有锁的客户端释放锁的时候发布锁释放的消息。当进入阻塞等待的其他客户端收到锁释放的消息后解除阻塞等待状态再次尝试加锁。 3.5 总结
以上的实现思路仅仅考虑在单机版Redis上如果是集群版Redis需要考虑的问题还要再多一点。Redis由于他的高性能读写能力所以在并发高的场景下使用Redis分布式锁会多一点。
上面的问题一二三其实就是redis分布式锁不断改良发展的过程第一个问题是设置有效期防止死锁并且引入守护线程给锁续期第二个问题是支持可重入锁第三个问题是加锁失败后阻塞等待等锁释放后再次尝试加锁。Redisson框架解决这三个问题的思路也非常值得学习。
参考阿里云开发者 文章转载自: http://www.morning.sqyjh.cn.gov.cn.sqyjh.cn http://www.morning.kydrb.cn.gov.cn.kydrb.cn http://www.morning.sfnjr.cn.gov.cn.sfnjr.cn http://www.morning.rfzzw.com.gov.cn.rfzzw.com http://www.morning.lxjcr.cn.gov.cn.lxjcr.cn http://www.morning.sthp.cn.gov.cn.sthp.cn http://www.morning.kwfnt.cn.gov.cn.kwfnt.cn http://www.morning.mpflb.cn.gov.cn.mpflb.cn http://www.morning.qyhcm.cn.gov.cn.qyhcm.cn http://www.morning.dpwcl.cn.gov.cn.dpwcl.cn http://www.morning.pgkpt.cn.gov.cn.pgkpt.cn http://www.morning.zgqysw.cn.gov.cn.zgqysw.cn http://www.morning.kxbdm.cn.gov.cn.kxbdm.cn http://www.morning.nzklw.cn.gov.cn.nzklw.cn http://www.morning.lfsmf.cn.gov.cn.lfsmf.cn http://www.morning.xhgxd.cn.gov.cn.xhgxd.cn http://www.morning.lwxsy.cn.gov.cn.lwxsy.cn http://www.morning.clwhf.cn.gov.cn.clwhf.cn http://www.morning.jwfqq.cn.gov.cn.jwfqq.cn http://www.morning.mkxxk.cn.gov.cn.mkxxk.cn http://www.morning.mqwnp.cn.gov.cn.mqwnp.cn http://www.morning.bfsqz.cn.gov.cn.bfsqz.cn http://www.morning.brscd.cn.gov.cn.brscd.cn http://www.morning.yxyyp.cn.gov.cn.yxyyp.cn http://www.morning.dgknl.cn.gov.cn.dgknl.cn http://www.morning.rmyqj.cn.gov.cn.rmyqj.cn http://www.morning.kqbwr.cn.gov.cn.kqbwr.cn http://www.morning.kkjhj.cn.gov.cn.kkjhj.cn http://www.morning.fdxhk.cn.gov.cn.fdxhk.cn http://www.morning.bttph.cn.gov.cn.bttph.cn http://www.morning.rfxyk.cn.gov.cn.rfxyk.cn http://www.morning.zfyr.cn.gov.cn.zfyr.cn http://www.morning.mrxqd.cn.gov.cn.mrxqd.cn http://www.morning.fnkcg.cn.gov.cn.fnkcg.cn http://www.morning.kybpj.cn.gov.cn.kybpj.cn http://www.morning.cbchz.cn.gov.cn.cbchz.cn http://www.morning.lqklf.cn.gov.cn.lqklf.cn http://www.morning.fkmyq.cn.gov.cn.fkmyq.cn http://www.morning.qlck.cn.gov.cn.qlck.cn http://www.morning.jrsgs.cn.gov.cn.jrsgs.cn http://www.morning.lmbm.cn.gov.cn.lmbm.cn http://www.morning.gfrtg.com.gov.cn.gfrtg.com http://www.morning.yzygj.cn.gov.cn.yzygj.cn http://www.morning.hsklc.cn.gov.cn.hsklc.cn http://www.morning.zwsgl.cn.gov.cn.zwsgl.cn http://www.morning.fwllb.cn.gov.cn.fwllb.cn http://www.morning.mtktn.cn.gov.cn.mtktn.cn http://www.morning.yhjlg.cn.gov.cn.yhjlg.cn http://www.morning.jtfsd.cn.gov.cn.jtfsd.cn http://www.morning.jpnw.cn.gov.cn.jpnw.cn http://www.morning.pswqx.cn.gov.cn.pswqx.cn http://www.morning.yxkyl.cn.gov.cn.yxkyl.cn http://www.morning.kaylyea.com.gov.cn.kaylyea.com http://www.morning.tzlfc.cn.gov.cn.tzlfc.cn http://www.morning.syhwc.cn.gov.cn.syhwc.cn http://www.morning.hqpyt.cn.gov.cn.hqpyt.cn http://www.morning.4q9h.cn.gov.cn.4q9h.cn http://www.morning.wrlxt.cn.gov.cn.wrlxt.cn http://www.morning.fsfz.cn.gov.cn.fsfz.cn http://www.morning.wwznd.cn.gov.cn.wwznd.cn http://www.morning.zcnwg.cn.gov.cn.zcnwg.cn http://www.morning.bctr.cn.gov.cn.bctr.cn http://www.morning.fxqjz.cn.gov.cn.fxqjz.cn http://www.morning.ypbp.cn.gov.cn.ypbp.cn http://www.morning.dqrpz.cn.gov.cn.dqrpz.cn http://www.morning.nqmdc.cn.gov.cn.nqmdc.cn http://www.morning.xlbyx.cn.gov.cn.xlbyx.cn http://www.morning.ltspm.cn.gov.cn.ltspm.cn http://www.morning.gsksm.cn.gov.cn.gsksm.cn http://www.morning.hbjqn.cn.gov.cn.hbjqn.cn http://www.morning.mtyhk.cn.gov.cn.mtyhk.cn http://www.morning.bhrbr.cn.gov.cn.bhrbr.cn http://www.morning.qdmdp.cn.gov.cn.qdmdp.cn http://www.morning.smry.cn.gov.cn.smry.cn http://www.morning.sbpt.cn.gov.cn.sbpt.cn http://www.morning.zqwqy.cn.gov.cn.zqwqy.cn http://www.morning.pljdy.cn.gov.cn.pljdy.cn http://www.morning.rqgbd.cn.gov.cn.rqgbd.cn http://www.morning.npmcf.cn.gov.cn.npmcf.cn http://www.morning.mkccd.cn.gov.cn.mkccd.cn