分布式锁的3种实现方式

 

提及分布式的概念,首当其冲就是CAP理论,即知足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance)。可是CAP理论告诉咱们,任何系统只能知足其中两个,因此都要求去作取舍。那么人们常说的通常都是,须要牺牲一致性来保证系统的高可用性,只要保证系统的最终一致性,而且容许的时间差值可以被接受就行。html

对于这个,本人的体会就是订单系统,对于订单系统来讲,用户端的一致性须要保证强一致性,可是对于后台或者商家来讲的话,这个订单的状态只要保证最终一致性就行,时间差值在可接受范围内就OK。redis

1.单机的状况下:数据库

单机状况要解决共享资源的访问很容易,Java的API提供了很丰富的解决方案,常见的诸如synchronize,lock,volatile,c.u.t包等等,不少,可是这是在单机状况下,由于只有一个JVM在运行咱们的代码。缓存

2.多机的状况下:服务器

这个时候就会出现一套代码出如今多个JVM中,请求落在哪个上面是随机的。这个时候上面提到的基于Java的API提供的一些解决机制就无法知足要求,它只能解决当前机器中能保证顺序访问共享资源,可是不能保证其余机器。框架

那么对于多机的状况怎么去解决这个问题呢,其实很简单,只要保证互斥就好了,原理和单机是同样的,找到一个互斥点就行。那么这个互斥点就必须在你们共有的一个环境中。分布式

那么我所了解到如今分布式锁有三种实现方案。post

1.基于数据库。性能

2.基于缓存环境,redis,memcache等。测试

3.基于zookeeper。

方案的实现注意点

1.首先保证在分布式的环境中,同一个方法只能被同一个服务器上的一个线程执行。

2.锁要可重入,严重一点的场景不能获取锁以后若是须要再次获取时发现不能获取了,形成死锁。

3.锁要可阻塞。这通常只要保证有个超时时间就行。

4.高可用的加锁和释放锁功能。

5.加锁和释放锁的性能要好。

1、基于数据库的实现方式

1.1 基于数据库表获取

此时这张表相似一个公共资源池,每一个线程都要来这边获取条件,看能不能获取到当前方法的锁。

1.1.1 获取锁时,只要执行insert语句insert into lock_table("method_name","time");

1.1.2 释放锁时,执行对应的delete语句就行。

一个简单的分布式锁就实现了,可是里面会存在不少问题,由于这只是一个初步方案,须要不断改进。

可能出现的问题

1.2.1 这个表中没有设计失效时间,一旦出现加锁成功可是解锁失败的状况,会出现其余线程没法获取到锁。

1.2.2 这把锁不是可重入的,同一个线程在没有释放以前没法再insert。

1.2.3 这把锁不是阻塞的,这边阻塞的意思就是有加锁时间限制,在这个时间内不断去尝试,相似Java里面的自旋。超过期间就失败。出现这个问题的缘由和1.2.2一致。

1.2.4 最后一点也是要考虑的,它的可用性怎么样?并很差,一旦数据库挂了,就不能使用了。

针对的解决方案

1.2.1 --> 

    1.2.1.1能够存在一个定时任务,可是要注意断定失效的时间点的把握,既不能过短也不能太长。

    1.2.1.2 代码中在加锁时能够先判断当前记录是否是已经超过最大容许时间,超过了说明已经失效了,先手动释放锁,再加锁

1.2.2 -->

    重入的需求能够加入一个字段记录当前JVM的机器标识和线程标识,再次获取时判断一下就行。

1.2.3 -->

    阻塞的问题很简单了,代码里执行while循环,设置一个容许最大时间,超过了,直接失败就是了。

1.2.4 -->

    单机的问题更好解决了,上两台,互为准备,随时备份,搞定。

1.2 基于排他锁的实现

排它锁测试

通常就能够理解为加上写锁,致使其余事务不能加写锁,只能读而已。就是人们常说的select * for update。

 

public boolean lock(){
connection.setAutoCommit(false)
while(true){
try{
result = select * from methodLock where method_name=xxx for update;
if(result==null){
return true;
}
}catch(Exception e){

}
sleep(1000);
}
return false;
}

 


public void unlock(){
connection.commit();
}

 

可解决问题:

1.2.1 它是阻塞的吗?是的,由于经过排它锁测试这一篇张,能够看见,当你使用了select * for update时,其余想要获取锁的事务读不出数据,一直阻塞在那儿。

1.2.2 宕机?宕机以后就自动释放了。

无法解决的问题:

1.2.3 单点问题

1.2.4 可重入问题。

2、基于缓存实现(redis,memchache等常见缓存框架)

redis锁的实现

基于redis的实现能够参考我以前的一篇文章

主要存在的问题:

1. 重入的问题没有解决。其实在笔者项目中,不存在须要现场重入的场景,基本都是在方法外面用redis的加锁包住,finally后释放。

2.redis中如何保证锁的容错性。须要注意加锁成功,可是设置失效时间时宕机的场景,保证不出现死锁。文章里有解决方案。

1.3 基于zk的实现

zk锁分析

zk锁的问题和实现

zk锁的实现

相比较稳定性而言,zk锁无疑是最好的实现方式,可是zk锁的实现依靠一个zk平台,它的理解程度也比较复杂,包括它是怎么保证多节点数据的一致性,怎么对外提供稳定的服务等等。复杂程度也最高,可是最稳定。

想比较而言,采用redis实现分布式锁仍是比较好的。我的意见。

相关文章
相关标签/搜索