分布式锁实现方式

Posted 大汗的技能

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了分布式锁实现方式相关的知识,希望对你有一定的参考价值。

分布式CAP帽子原理

  • 一致性(Consistency):同样数据在分布式系统的各个节点上都是一致的

  • 分区容忍性(Partition Tolerance) :如果出现了网络故障、一部分节点无法通信,但是系统仍能够工作

  • 可用性(Availability):所有在分布式系统活跃的节点都能够处理操作且能响应查询

最多只能同时满足两项,在互联网领域的绝大多数的场景中,都需要牺牲强一致性来换取系统的高可用性,系统往往只需要保证“最终一致性”,只要这个最终时间是在用户可以接受的范围内即可。 在很多场景中,为了保证最终一致性,需要很多技术方案来支持,比如分布式事务、分布式锁等。

分布式锁实现的方式

  • 基于数据库实现分布式锁

  • 基于缓存(redis,memcached,tair)实现分布式锁

  • 基于Zookeeper实现分布式锁

基于数据库实现分布式锁

基于数据库表,实现分布式锁,思路是创建一个表,当要锁住某个方法或者资源时候,该表中增加一条记录,想要释放锁的时候就删除这条记录。

 
   
   
 
  1. CREATE TABLE `methodLock` (

  2. `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',

  3. `method_name` varchar(64) NOT NULL DEFAULT '' COMMENT '锁定的方法名',

  4. `desc` varchar(1024) NOT NULL DEFAULT '备注信息',

  5. `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '保存数据时间,自动生成',

  6. PRIMARY KEY (`id`),

  7. UNIQUE KEY `uidx_method_name` (`method_name `) USING BTREE

  8. )

当我们想要锁住某个方法时,执行以下SQL

 
   
   
 
  1. insert into methodLock(method_name,desc) values (‘method_name',‘desc')

对method_name做了唯一性约束。 想要释放锁的话,需要执行以下SQL

 
   
   
 
  1. delete from methodLock where method_name ='method_name'

缺点

  • 强依赖数据库,数据库是单点,会导致业务系统不可用

  • 锁没有失效时间,一旦解锁操作失败,就会导致锁记录一直在数据库中,其他线程无法再获得到锁。

  • 这把锁只能是非阻塞的,因为数据的insert操作,一旦插入失败就会直接报错。没有获得锁的线程并不会进入排队队列,要想再次获得锁就要再次触发获得锁操作

  • 这把锁是非重入的,同一个线程在没有释放锁之前无法再次获得该锁。因为数据中数据已经存在了

基于数据库排它锁,上面的数据库表除了增删操作外,还可以使用数据库排它锁来实现分布式锁。基于mysql的InnoDB引擎,在查询语句后面增加for update,此方法有效的解决上面无法释放锁和阻塞锁。 使用排他锁来进行分布式锁,如果一个排他锁长时间不提交,就会占用数据库连接。一旦类似的连接变得多了,就可能把数据库连接池撑爆。 总结一下使用数据库来实现分布式锁的方式,这两种方式都是依赖数据库的一张表,一种是通过表中的记录的存在情况确定当前是否有锁存在,另外一种是通过数据库的排他锁来实现分布式锁。

基于缓存实现分布式锁

可以使用redis,memcached,tair,基于缓存来实现在性能方面会表现的更好一点。而且很多缓存是可以集群部署的,可以解决单点问题。 使用Redis的 SETNX 命令可以实现分布式锁 

setnx

 
   
   
 
  1. # 当且仅当key不存在时,set一个key为val的字符串,返回1;若key存在,则什么都不做,返回0

  2. $ setnx key val

expire

 
   
   
 
  1. # 为key设置一个超时时间,单位为second,超过这个时间锁会自动释放,避免死锁。

  2. $ expire key timeout

delete

 
   
   
 
  1. $ delete key

在使用Redis实现分布式锁的时候,主要就会使用到这三个命令。实现思路

  • 获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID,通过此在释放锁的时候进行判断。

  • 获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁

  • 释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放。

redis实现分布式锁:https://github.com/jinweida/java-learn-cde/blob/master/src/main/java/com/king/learn/redis/RedisDistributedLock.java

Zookeeper实现分布式锁

ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。 ZooKeeper的架构通过冗余服务实现高可用性。因此,如果第一次无应答,客户端就可以询问另一台ZooKeeper主机。ZooKeeper节点将它们的数据存储于一个分层的命名空间,非常类似于一个文件系统或一个前缀树结构。客户端可以在节点读写,从而以这种方式拥有一个共享的配置服务。更新是全序的。实现流程

  • 在zookeeper指定节点(locks)下创建临时顺序节点node_n

  • 获取locks下所有子节点children

  • 对子节点按节点自增序号从小到大排序

  • 判断本节点是不是第一个子节点,若是,则获取锁;若不是,则监听比该节点小的那个节点的删除事件

  • 若监听事件生效,则回到第二步重新进行判断,直到获取到锁

zookeeper实现分布式锁:https://github.com/jinweida/java-learn-cde/blob/master/src/main/java/com/king/learn/zookeeper/ZKDistributedLock.java


以上是关于分布式锁实现方式的主要内容,如果未能解决你的问题,请参考以下文章

分布式锁三种解决方案

分布式锁的实现方式和优缺点&Java代码实现

Redis分布式锁的实现方式

80% 人不知道的 Redis 分布式锁的正确实现方式(Java 版)

基于redis和zookeeper的分布式锁实现方式

详解锁,分布式锁的几种实现方式