先了解一下java里面的锁,
Java中的锁可以简单的理解为多线程情况下访问临界资源的一种线程同步机制。
在单个应用节点的情况下,对某一个共享变量进行多线程访问的时候,可以完美运行。
后来业务发现了,发展成多节点运行,一个应用需要部署到好几台机器上做负载均衡。
在多节点的情况下:
这个时候,该变量就会存在于多个JVM的内存中,不加任何控制的话。
多个请求进来,负载均衡分配请求到了不同的节点上进行处理,多个节点
之间,变量不存在共享,也不具有可见性,这种情况,不符合预期,出来的
结果也可能是有问题的。
虽然在传统的单机应用下,可以使用java并发处理的API进行互斥控制(如ReentrantLock或Synchronized),但是,
java的api无法对这种多节点的应用,进行加锁处理;
为了解决这个问题,就需要一种跨越jvm的互斥机制来控制共享资源的方案,这就是分布式锁所要解决的。
分布式锁应该具备以下几种特性:
- 在分布式系统环境下,一个方法在同一时间,只能被一个机器的一个线程执行
- 高可用的获取锁以及释放锁
- 高性能的获取锁以及释放锁
- 具备可重入特性
- 具备锁失效机制,防止死锁
- 具备非阻塞锁特性,在没有获取到锁的时候,直接返回获取锁失败
分布式锁的三种实现方式:
分布式的CAP理论:
一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance)
任意一个分布式系统,都无法同事满足3个理论,只能在这3者之间进行取舍,
在绝大多数的领域,都在可接受的范围内,强调最终一致性
为了达到数据的最终一致性,我们需要一些技术方案来支持,比如说,分布式锁,分布式事务等
有时候,我们需要保证系统中里面的一个方法,在同一个时间节点内,只能被同一个线程执行。
- 基于数据库实现分布式锁
- 基于缓存实现分布式锁
- 基于Zookeeper实现分布式锁
可以从这3个方案中,选择最合适的方案进行。
基于数据库实现分布式锁:
核心思想:在数据库中建一个表,使用方法名作为唯一的索引,需要执行某个方法的时候,就在该表插入一条记录,成功插入之后,则获取到锁,当方法执行结束之后,删除记录,即删除锁。
基于缓存(Redis)实现分布式锁:
redis性能非常好,命令对于此的支持比较好,使用起来很方便。
需要使用到的命令如下:
SETNX:
setnx key val 仅当key不存在的时候,set一个key为val的字符串,返回1;若key存在,则什么都不做,返回0
EXPIRE:
expire key timeout 为key设置一个过期时间,单位为second,超过这个时间会自动释放
DELETE:
delete kay 删除key
核心思想:获取锁的时候,使用setnx加锁,并且使用expire给锁设置一个过期时间,超过时间自动释放,锁的value值是一个随机数,在释放锁的时候,可以使用该值进行判断。
释放锁的时候,使用value进行判断是否是当前线程的锁,若是,则进行delete释放。
基于Zookeeper实现分布式锁:
ZooKeeper是一个为分布式应用提供一致性服务的开源组件,它内部是一个分层的文件系统目录树结构,规定同一个目录下只能有一个唯一文件名。基于ZooKeeper实现分布式锁的步骤如下:
创建一个目录mylock;
线程A想获取锁就在mylock目录下创建临时顺序节点;
获取mylock目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获得锁;
线程B获取所有节点,判断自己不是最小节点,设置监听比自己次小的节点;
线程A处理完,删除自己的节点,线程B监听到变更事件,判断自己是不是最小的节点,如果是则获得锁。
这里推荐一个Apache的开源库Curator,它是一个ZooKeeper客户端,Curator提供的InterProcessMutex是分布式锁的实现,acquire方法用于获取锁,release方法用于释放锁。
优点:具备高可用、可重入、阻塞锁特性,可解决失效死锁问题。
缺点:因为需要频繁的创建和删除节点,性能上不如Redis方式。