1 在开发中的锁是什么
在计算机科学中,锁是在执行多线程时用于强行限制资源访问的同步机制,即用于在并发控制中保证对互斥要求的满足。
在java中我们有两种资源控制方式Synchronized与AQS
1.2 基于Synchronized实现的锁控制
Synchronized是java提供的一种内置的锁机制,Synchronized的获取和释放锁由JVM实现,用户不需要显示的释放锁,非常方便。
在实际中我们使用的两种形式
- 同步方法使用synchronized修饰方法,在调用该方法前,需要获得内置锁(java每个对象都有一个内置锁),否则就处于阻塞状态
public synchronized void save(){//内容}
- 同步代码块使用synchronized(object){}进行修饰,在调用该代码块时,需要获得内置锁,否则就处于阻塞状态
synchronized(object){
}
1.3 基于AQS实现的锁控制
AQS,是用来构建锁或者其他同步组件的基础框架,它使用了一个int成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作。同步器的主要使用方式是继承,子类通过继承同步器并实现它的抽象方法来管理同步状态,在抽象方法的实现过程中免不了要对同步状态进行更改,ReentrantLock就是我们常用的AQS实现类
{
ReentrantLock lock=new ReentrantLock();
lock.lock();
//内容
lock.unlock();
}
1.3.1 公平性
ReentrantLock提供了两种公平性选择:
- 公平性锁
在公平的锁,线程将会按照他们发出请求的顺序来获得锁。
- 非公平性锁
在非公平的锁上,当线程申请锁时,同时锁的状态变为可用,那么线程有机会可以跳过所有等待线程并获得这个锁。
2 分布式下的挑战与分布式锁
分布式锁,是控制分布式系统之间同步访问共享资源的一种方式。在分布式系统中,常常需要协调他们的动作。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁。
显然单个机器的线程锁无法管控多个机器对同一资源的访问,这时使用分布式锁,就可以把整个集群当作一个应用一样去处理
2.1 分布式锁需要怎么样的特性
- (必须)分布式锁必须在多机器下保证资源,同时只有一个线程进行使用
- (多数情况下)分布式必须保证高可用
- (多数情况下)分布式锁获取锁和释放锁性能要好
- (多数情况下)具备锁失效机制,在分布式锁执行者失效依然被动解锁
- (最好)根据业务要求可以实现公平锁
3 分布式锁的实现
目前主流的分布式锁主要有3种:
- 基于数据库实现分布式锁,这里的数据库指的是关系型数据库;
- 基于 ZooKeeper 实现分布式锁;
- 基于缓存实现分布式锁。
3.1 基于数据库实现分布式锁
基于数据库实现分布式锁的原理非常简单,当多个资源请求进行数据添加的时候,通过对数据库插入表内容,同时资源表的唯一约束性,会保证只有1个表数据添加成功。从而进行后续操作。数据库实现的分布式锁本身无法实现公平锁。
3.2 基于 ZooKeeper 实现分布式锁
基于ZooKeeper实现分布式锁,首先要了解ZooKeeper的节点类型,ZooKeeper支持6种数据结构,PERSISTENT、PERSISTENT_SEQUENTIAL、EPHEMERAL、EPHEMERAL_SEQUENTIAL、 CONTAINER、PERSISTENT_WITH_TTL、PERSISTENT_SEQUENTIAL_WITH_TTL,而我们用到的会是以下三种:
- PERSISTENT
持久化目录节点,客户端与zookeeper断开连接后,该节点依旧存在
- EPHEMERAL
临时节点,生命周期和客户端会话绑定。也就是说,如果客户端会话失效了,那么这个节点就会自动被清除掉
- EPHEMERAL_SEQUENTIAL
临时顺序节点,生命周期和客户端会话绑定。也就是说,如果客户端会话失效了,那么这个节点就会自动被清除掉,并且在生成时会带有顺序编号。
基于ZooKeeper实现分布式锁,以资源命名持久化目录节点,通过判断是否有该持久化目录节点且在该线程生成的节点是否顺序编号最少,如果为true则可以获取资源,最终实现公平锁。而非公平锁,我们只需要以资源命名临时节点,如果临时节点不存在则可以获取资源,实现非公平锁。
3.3 基于缓存实现分布式锁
基于缓存实现的分布式锁,现在一般是指使用Redis实现的分布式锁。而Redis通常使用其setnx函数来的特性来实现锁:setnx 函数的返回值有 1 和 0
- 返回 1,说明该服务器获得锁。
- 返回 0,说明其他服务器已经获得了锁,进程不能进入临界区。
程序通过判断setnx的返回值判断是否取到锁。从而进行后续操作。Redis实现的分布式锁本身无法实现公平锁。
4 三种分布式锁实现方式对比
- 理解容易程度 数据库
缓存
Zookeeper
数据库及缓存实现的方式比较直观查看返回结果即可,而基于Zookeeper实现的分布式锁首先要理解其运作方式,结合程序才能实现。
- 实现复杂度 Zookeeper
缓存
数据库
Zookeeper实现复杂性体现在对其特性的理解,Redis实现复杂性体现在保持可用性的情况下如何保持正确性.
- 性能 缓存
Zookeeper
数据库
存储媒介及系统本身确定。
- 可靠性 Zookeeper
缓存
数据库
可靠性上,Zookeeper和缓存基本都能很好解决可靠性上的问题,但是准确率上Zookeeper会比缓存更好。
理解容易程度(从难到易) | 数据库 |
实现复杂度(从难到易) | Zookeeper |
性能(从高到低) | 缓存 |
可靠性(从高到低) | 可靠性 Zookeeper |
5 后记
分布式锁在分布式服务中是很常用的,在文中提到的特性也尤其重要,后续我会通过多篇博文来逐一介绍,并且深入介绍她们的实现
参考: 锁 (计算机科学)
书籍:Java并发编程实战