java分布式锁

2019-08-08  本文已影响0人  夏与清风
1、什么是锁?

在单进程的系统中,当存在多个线程可以同时改变某个变量(可变共享变量)时,就需要对变量或代码块做同步,使其在修改这种变量时能够线性执行消除并发修改变量。
同步的本质是通过锁来实现的。为了实现多个线程在一个时刻同一个代码块只能有一个线程可执行,那么需要在某个地方做个标记,这个标记必须每个线程都能看到,当标记不存在时可以设置该标记,后续线程发现已有标记,则等待拥有标记的线程结束同步代码块取消标记后再去尝试设置标记。这个标记可以理解为锁。
不同地方实现锁的方式也不一样,只要满足所有线程都能看到标记即可。如synchronized是在对象头设置标记,Lock接口的实现类基本上都只是某一个 volitile修饰的int型变量,其保证每个线程都能拥有对该int的可见性和原子修改,linux内核中也是利用互斥量或信号量等内存数据做标记。
除了利用内存数据做锁,任何互斥的都能做锁,如流水表中流水号与时间结合做幂等校验可以看作是一个不会释放的锁,或使用某个文件是否存在作为锁等。只要满足在对标记进行修改能保证原子性和内存可见性即可。

2、什么是分布式?

分布式的CAP理论:
任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足两项。
基于CAP理论,很多系统在设计之初就要对这三者做出取舍。在互联网领域的绝大多数的场景中,都需要牺牲强一致性来换取系统的高可用性,系统往往只需要保证最终一致性。
分布式场景:
在许多的场景中,为了保证数据的最终一致性,需要很多的技术方案来支持,比如分布式事务、分布式锁等。很多时候我们需要保证一个方法在同一时间内只能被同一个线程执行。在单机环境中,可以通过Java提供的并发API解决,但在分布式环境下,就没有那么简单了。

3、什么是分布式锁?
4、我们需要怎样的分布式锁?
5、基于数据库做分布式锁

利用主键唯一的特性,如果有多个请求同时提交到数据库的话,数据库会保证只有一个操作可以成功,我们就可以认为操作成功的那个线程获得了该方法的锁,当方法执行完毕之后,想要释放锁的话,删除这条数据库记录即可。
缺点:

6、基于表字段版本号做分布式锁

这个策略源于mysql的mvcc机制,缺点是对数据表侵入较大,我们要为每个表设计一个版本号字段,然后写一条判断sql每次进行判断,增加了数据库操作的次数,在高并发的要求下,对数据库连接的开销较大。

7、基于数据库排他锁做分布式锁

在查询语句后面增加for update,数据库会在查询过程中给数据库表增加排他锁 (注意:InnoDB引擎在加锁的时候,只有通过索引进行检索时才会使用行级锁,否则会使用表级锁。希望使用行级锁,就要给要执行的方法字段名添加索引,这个索引一定要创建成唯一索引,否则会出现多个重载方法之间无法同时被访问的问题)。当某条记录被加上排他锁之后,其他线程无法在该行记录上增加排他锁。
可以认为获得排他锁的线程即可获得分布式锁,当获取到锁之后,可以执行方法的业务逻辑,执行完方法之后,通过connection.commit()操作来释放锁。
这种方法可以有效的解决无法释放锁和阻塞锁的问题,但无法直接解决数据库单点和可重入问题。

8、基于Redis做分布式锁
9、基于REDLOCK做分布式锁

Redlock基于N个完全独立的Redis节点(通常情况下N可以设置成5)。
算法的步骤如下:
1)客户端获取当前时间,以毫秒为单位。
2)客户端尝试获取N个节点的锁,(每个节点获取锁的方式和前面说的缓存锁一样),N个节点以相同的key和value获取锁。客户端需要设置接口访问超时,接口超时时间需要远小于锁超时时间,比如锁自动释放的时间是10s,那么接口超时大概设置5-50ms。这样可以在有redis节点宕机后,访问该节点时能尽快超时,而减小锁的正常使用。
3)客户端计算在获得锁的时候花费了多少时间,方法是用当前时间减去在步骤一获取的时间,只有客户端获得了超过3个节点的锁,而且获取锁的时间小于锁的超时时间,客户端才获得了分布式锁。
4)客户端获取的锁的时间为设置的锁超时时间减去步骤三计算出的获取锁花费时间。
5)如果客户端获取锁失败了,客户端会依次删除所有的锁。
使用Redlock算法,可以保证在挂掉最多2个节点时,分布式锁服务仍然能工作,相比之前的数据库锁和缓存锁大大提高了可用性,由于redis的高性能,分布式缓存锁性能并不比数据库锁差。

10、基于ZooKeeper做分布式锁

ZooKeeper 锁相关基础知识:
1)zk一般由多个节点构成(单数),采用zab一致性协议。因此可以将zk看成一个单点结构,对其修改数据其内部自动将所有节点数据进行修改而后才提供查询服务。
2)zk的数据以目录树的形式,每个目录称为znode, znode中可存储数据(一般不超过1M),还可以在其中增加子节点。
3)子节点有三种类型:序列化节点,每在该节点下增加一个节点自动给该节点的名称上自增。临时节点,一旦创建这个znode的客户端与服务器失去联系,这个znode也将自动删除。最后就是普通节点。
4)Watch机制,client可以监控每个节点的变化,当产生变化会给client产生一个事件。
ZK 基本锁:
原理:利用临时节点与watch机制。每个锁占用一个普通节点/lock,当需要获取锁时在/lock目录下创建一个临时节点,创建成功则表示获取锁成功,失败则 watch/lock节点,有删除操作后再去争锁。临时节点好处在于当进程挂掉后能自动上锁的节点自动删除即取消锁。
缺点:所有取锁失败的进程都监听父节点,很容易发生羊群效应,即当释放锁后所有等待进程一起来创建节点,并发量很大。
ZK 锁优化:
原理:上锁改为创建临时有序节点,每个上锁的节点均能创建节点成功,只是其序号不同。只有序号最小的可以拥有锁,如果这个节点序号不是最小的则watch序号比本身小的前一个节点 (公平锁)。
步骤:
1)在 /lock节点下创建一个有序临时节点 (EPHEMERAL_SEQUENTIAL)。
2)判断创建的节点序号是否最小,如果是最小则获取锁成功。不是则取锁失败,然后watch序号比本身小的前一个节点。
3)当取锁失败,设置watch后则等待watch事件到来后,再次判断是否序号最小。
4)取锁成功则执行代码,最后释放锁(删除该节点)。
优点:
有效的解决单点问题,不可重入问题,非阻塞问题以及锁无法释放的问题。实现起来较为简单。
缺点:
性能上可能并没有缓存服务那么高,因为每次在创建锁和释放锁的过程中,都要动态创建、销毁临时节点来实现锁功能。ZK中创建和删除节点只能通过Leader服务器来执行,然后将数据同步到所有的Follower机器上。

原文地址:https://www.cnblogs.com/seesun2012/p/9214653.html

上一篇下一篇

猜你喜欢

热点阅读