Spark从入门到精通6:使用Zookeeper实现分布式锁服务

在高并发访问资源的情况下,比如春运抢票、微信抢红包等,往往需要采取一定的策略来保证数据的一致性,其中最常见的一种实现方式就是使用锁服务。本节就来介绍一种基于Zookeeper的分布式锁服务的原理和实现。

1.基于Zookeeper的锁服务的原理

image

如图所示,使用Zookeeper实现分布式锁服务,就是当多个用户(客户端)同时竞争访问某个资源,并且会对该资源执行一些操作(主要是写操作)时,需要按照如下的步骤进行:

  1. 用户A要访问共享资源,需要向Zookeeper申请锁;
  2. 如果没有其他用户 占用该锁,用户A就获得锁并访问共享资源,此时申请该锁的其他用户将等待;否则用户A将一直重试,直到该锁被释放或者超时;
  3. 用户A访问资源结束后,释放锁;
  4. 用户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工程如下所示:

image

(4)编辑Maven的配置文件pom.xml,在标签之间追加Zookeeper的依赖项:


org.apache.curator
curator-framework
4.0.0


org.apache.curator
curator-recipes
4.0.0


org.apache.curator
curator-client
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上给出了:

image

(5)按Ctrl+S保存pom.xml文件,Maven会自动添加依赖的jar包:这个过程会花费一段时间,请耐心等待……

image

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实现分布式锁服务就介绍完了,祝你玩的愉快!

你可能感兴趣的:(Spark从入门到精通6:使用Zookeeper实现分布式锁服务)