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,在<dependencies></dependencies>标签之间追加Zookeeper的依赖项:

<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>4.0.0</version>
</dependency>

<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.0.0</version>
</dependency>

<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-client</artifactId>
<version>4.0.0</version>
</dependency>

<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.10</version>
</dependency>

<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>17.0</version>
</dependency>

<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,546评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,224评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,911评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,737评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,753评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,598评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,338评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,249评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,696评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,888评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,013评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,731评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,348评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,929评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,048评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,203评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,960评论 2 355

推荐阅读更多精彩内容