在高并发访问资源的情况下,比如春运抢票、微信抢红包等,往往需要采取一定的策略来保证数据的一致性,其中最常见的一种实现方式就是使用锁服务。本节就来介绍一种基于Zookeeper的分布式锁服务的原理和实现。
1.基于Zookeeper的锁服务的原理
如图所示,使用Zookeeper实现分布式锁服务,就是当多个用户(客户端)同时竞争访问某个资源,并且会对该资源执行一些操作(主要是写操作)时,需要按照如下的步骤进行:
- 用户A要访问共享资源,需要向Zookeeper申请锁;
- 如果没有其他用户 占用该锁,用户A就获得锁并访问共享资源,此时申请该锁的其他用户将等待;否则用户A将一直重试,直到该锁被释放或者超时;
- 用户A访问资源结束后,释放锁;
- 用户B也是一样。
上述例子中,只给出了资源的一把锁,同一时刻只允许一个用户拥有该锁,也就是说同一时刻只允许一个用户访问该共享资源。如果需要同一时刻为多个用户服务,则需要同时设置多把锁,以供用户竞争使用。例如,某银行的服务大厅有5个窗口,用户可以选择空闲的窗口办理银行业务,这里的银行就是一种共享资源,5个窗口就是5把锁,同一时刻最多为5个用户提供服务。
2.基于Zookeeper的锁服务的实现
Apache专门提供了一套解决方案:Curator来实现Zookeeper的分布式锁服务。Apache Curator的官方网址:curator.apache.org。Apache Curator是专门针对Apache Zookeeper提供的一套Java客户端类库,可以通过Maven/Artifacts工程快速实现Zookeeper的分布式锁服务。
2.1使用Eclipse搭建Maven工程
(1)打开Eclipse IDE,依次选择”File”–“New”–“Project…”
(2)在”Select a wizard”界面选择Maven文件夹下的”Maven Project”–Next–Next–Next
(3)在”Enter a group id for the artifact”界面输入如下信息:Group ID:ZK_Lock,Artifact ID:ZK_Lock,Package:demo.zk.lock,点击”Finish”。创建好的Maven工程如下所示:
(4)编辑Maven的配置文件pom.xml,在
org.apache.curator
curator-framework
4.0.0
org.apache.curator
curator-recipes
4.0.0
org.apache.zookeeper
zookeeper
3.4.10
com.google.guava
guava
17.0
log4j
log4j
1.2.17
注:这些Zookeeper的依赖项,在Apache Curator官网curator.apache.org上给出了:
(5)按Ctrl+S保存pom.xml文件,Maven会自动添加依赖的jar包:这个过程会花费一段时间,请耐心等待……
2.2编写测试用例
例子:10个线程并发访问变量NUMBER,每个线程访问完之后将NUMBER的值减一。
(1)Demo1:不使用Zookeeper的分布式锁之前,并发访问方法和变量
在src/main/java目录下新建一个类:TestDistributedLock.java,并生成主方法,代码如下:
package demo.zk.lock;
public class TestDistributedLock {
//定义共享资源
private static int NUMBER = 10;
private static void getNumber(){
//需要在高并发的情况下访问
System.out.println("NUMBER = " + NUMBER);
NUMBER--;
}
public static void main(String[] args) {
//创建10个线程
for(int i=0;i<10;i++){
new Thread(new Runnable(){
public void run() {
// 调用getNumber方法
getNumber();
}
}).start();
}
}
}
执行直接如下:
NUMBER = 10
NUMBER = 10
NUMBER = 10
NUMBER = 10
NUMBER = 10
NUMBER = 5
NUMBER = 4
NUMBER = 3
NUMBER = 3
NUMBER = 3
可以看到,这种情况下程序并没有按照我们的预期输出结果,并且这种线程竞争的结果是不确定的,每次执行程序的结果都不一样。
(2)Demo2:使用Zookeeper的分布式锁,并发访问方法和变量
在src/main/java目录下再新建一个类:TestDistributedLock2.java,并生成主方法,代码如下:
package demo.zk.lock;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;
public class TestDistributedLock2 {
//定义共享资源
private static int NUMBER = 10;
private static void getNumber(){
//需要在高并发的情况下访问
System.out.println("NUMBER = " + NUMBER);
NUMBER--;
try {
//为了观察Zookeeper上/mylock目录的变化,让程序睡3秒
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
//创建一个RetryPolicy,没有获得锁的时候,等待的时间
/*参数:
* 1000:等待时间毫秒
* 10:重试的次数
*/
RetryPolicy policy = new ExponentialBackoffRetry(1000,10);
//创建一个连接,指向Zookeeper
CuratorFramework cf = CuratorFrameworkFactory.builder()
.connectString("192.168.179.112:2181")//指定Zookeeper的地址
.retryPolicy(policy)
.build();
//启动连接
cf.start();
/*
* 生成锁
* cf:ZK连接
* "/mylock":ZK上保存锁的位置
*/
final InterProcessMutex lock = new InterProcessMutex(cf,"/mylock");
//创建10个线程
for(int i=0;i<10;i++){
new Thread(new Runnable(){
public void run() {
try {
//获取锁
lock.acquire();
// 调用getNumber方法
getNumber();
} catch (Exception e) {
e.printStackTrace();
} finally{
try {
//释放锁
lock.release();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}).start();;
}
}
}
启动Zookeeper服务器:
[root@master ~]# zkServer.sh start
ZooKeeper JMX enabled by default
Using config: /root/training/zookeeper-3.4.10/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED
[root@master ~]# jps
1586 QuorumPeerMain
1603 Jps
启动Zookeeper客户端,查看Zookeeper根目录下的内容:
[root@master ~]# zkCli.sh
[zk: localhost:2181(CONNECTED) 0] ls /
[zookeeper, spark]
执行TestDistributedLock2.java程序,结果如下:
NUMBER = 10
NUMBER = 9
NUMBER = 8
NUMBER = 7
NUMBER = 6
NUMBER = 5
NUMBER = 4
NUMBER = 3
NUMBER = 2
NUMBER = 1
并且在程序执行过程中,Zookeeper上会创建/mylock目录,并且下面会有锁信息产生,每个线程的锁都不一样,在线程开始会创建一把锁,线程结束会释放该锁,所以最终程序结束后/mylock目录为空:
[zk: localhost:2181(CONNECTED) 3] ls /
[mylock, zookeeper, spark]
[zk: localhost:2181(CONNECTED) 9] ls /mylock
[_c_7bd0d049-d9c3-4037-bcc0-bc462dda687a-lock-0000000019,
_c_eaec39e5-d6fc-46d8-ac5c-f5693bd46cda-lock-0000000010,
_c_e56690f9-9026-47ca-8b5c-2ddb4de3b2fb-lock-0000000016,
_c_5d4adebe-b433-44fc-88a1-ec8e701abad5-lock-0000000018,
_c_695d2f3b-1e52-4f9f-9732-3e8d5ddfbb93-lock-0000000013,
_c_6978707c-20d9-4880-977a-68d3556c26b7-lock-0000000012,
_c_688f9af0-6dc9-49a6-ac3c-5f2cb18055e0-lock-0000000015,
_c_ad409320-084c-4743-b28f-33464ef888f9-lock-0000000017,
_c_c522a6a5-47eb-418e-909a-ad3d3430e678-lock-0000000014,
_c_f4bdecc9-a022-426d-b287-455d64ac0175-lock-0000000011]
[zk: localhost:2181(CONNECTED) 10] ls /mylock
[_c_7bd0d049-d9c3-4037-bcc0-bc462dda687a-lock-0000000019,
_c_e56690f9-9026-47ca-8b5c-2ddb4de3b2fb-lock-0000000016,
_c_5d4adebe-b433-44fc-88a1-ec8e701abad5-lock-0000000018,
_c_695d2f3b-1e52-4f9f-9732-3e8d5ddfbb93-lock-0000000013,
_c_6978707c-20d9-4880-977a-68d3556c26b7-lock-0000000012,
_c_688f9af0-6dc9-49a6-ac3c-5f2cb18055e0-lock-0000000015,
_c_ad409320-084c-4743-b28f-33464ef888f9-lock-0000000017,
_c_c522a6a5-47eb-418e-909a-ad3d3430e678-lock-0000000014,
_c_f4bdecc9-a022-426d-b287-455d64ac0175-lock-0000000011]
……
[zk: localhost:2181(CONNECTED) 12] ls /mylock
[]
上述例子中,使用Zookeeper实现了分布式锁服务,解决了高并发访问共享资源的数据一致性问题,这非常有用。这里只给出了一把锁的演示,如果需要同一时刻可以有多个用户访问共享资源,可以设置多把锁以供用户抢用。至此,使用Zookeeper实现分布式锁服务就介绍完了,祝你玩的愉快!