服务器之家:专注于服务器技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Java教程 - 分析ZooKeeper分布式锁的实现

分析ZooKeeper分布式锁的实现

2021-09-23 11:45IT王小二 Java教程

在分布式的情况下,sychornized 和 Lock 已经不能满足我们的要求了,那么就需要使用第三方的锁了,这里我们就使用 ZooKeeper 来实现一个分布式锁

一、分布式锁方案比较

方案 实现思路 优点 缺点
利用 mysql 的实现方案 利用数据库自身提供的锁机制实现,要求数据库支持行级锁 实现简单 性能差,无法适应高并发场景;容易出现死锁的情况;无法优雅的实现阻塞式锁
利用 redis 的实现方案 使用 setnx 和 lua 脚本机制实现,保证对缓存操作序列的原子性 性能好 实现相对复杂,有可能出现死锁;无法优雅的实现阻塞式锁
利用 zookeeper 的实现方案 基于 zookeeper 节点特性及 watch 机制实现 性能好,稳定可靠性高,能较好地实现阻塞式锁 实现相对复杂

二、zookeeper实现分布式锁

这里使用 zookeeper 来实现分布式锁,以50个并发请求来获取订单编号为例,描述两种方案,第一种为基础实现,第二种在第一种基础上进行了优化。

2.1、方案一

流程描述:

分析ZooKeeper分布式锁的实现

具体代码:

ordernumgenerator:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
 * @description 生成随机订单号
 */
public class ordernumgenerator {
 
    private static long count = 0;
 
    /**
     * 使用日期加数值拼接成订单号
     */
    public string getordernumber() throws exception {
        string date = datetimeformatter.ofpattern("yyyymmddhhmmss").format(localdatetime.now());
        string number = new decimalformat("000000").format(count++);
        return date + number;
    }
}

lock:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
 * @description 自定义锁接口
 */
public interface lock {
 
    /**
     * 获取锁
     */
    public void getlock();
 
    /**
     * 释放锁
     */
    public void unlock();
}

abstractlock:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
 * @description 定义一个模板,具体的方法由子类来实现
 */
public abstract class abstractlock implements lock {
 
    /**
     * 获取锁
     */
    @override
    public void getlock() {
 
        if (trylock()) {
            system.out.println("--------获取到了自定义lock锁的资源--------");
        } else {
            // 没拿到锁则阻塞,等待拿锁
            waitlock();
            getlock();
        }
 
    }
 
    /**
     * 尝试获取锁,如果拿到了锁返回true,没有拿到则返回false
     */
    public abstract boolean trylock();
 
    /**
     * 阻塞,等待获取锁
     */
    public abstract void waitlock();
}

zookeeperabstractlock:

?
1
2
3
4
5
6
7
8
9
10
11
/**
 * @description 定义需要的服务连接
 */
public abstract class zookeeperabstractlock extends abstractlock {
 
    private static final string server_addr = "192.168.182.130:2181,192.168.182.131:2181,192.168.182.132:2181";
 
    protected zkclient zkclient = new zkclient(server_addr);
 
    protected static final string path = "/lock";
}

zookeeperdistrbutelock:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
/**
 * @description 真正实现锁的细节
 */
public class zookeeperdistrbutelock extends zookeeperabstractlock {
    private countdownlatch countdownlatch = null;
 
    /**
     * 尝试拿锁
     */
    @override
    public boolean trylock() {
        try {
            // 创建临时节点
            zkclient.createephemeral(path);
            return true;
        } catch (exception e) {
            // 创建失败报异常
            return false;
        }
    }
 
    /**
     * 阻塞,等待获取锁
     */
    @override
    public void waitlock() {
        // 创建监听
        izkdatalistener izkdatalistener = new izkdatalistener() {
            @override
            public void handledatachange(string s, object o) throws exception {
 
            }
 
            @override
            public void handledatadeleted(string s) throws exception {
                // 释放锁,删除节点时唤醒等待的线程
                if (countdownlatch != null) {
                    countdownlatch.countdown();
                }
            }
        };
 
        // 注册监听
        zkclient.subscribedatachanges(path, izkdatalistener);
 
        // 节点存在时,等待节点删除唤醒
        if (zkclient.exists(path)) {
            countdownlatch = new countdownlatch(1);
            try {
                countdownlatch.await();
            } catch (interruptedexception e) {
                e.printstacktrace();
            }
        }
 
        // 删除监听
        zkclient.unsubscribedatachanges(path, izkdatalistener);
    }
 
    /**
     * 释放锁
     */
    @override
    public void unlock() {
        if (zkclient != null) {
            system.out.println("释放锁资源");
            zkclient.delete(path);
            zkclient.close();
        }
    }
}

测试效果:使用50个线程来并发测试zookeeper实现的分布式锁

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
 * @description 使用50个线程来并发测试zookeeper实现的分布式锁
 */
public class orderservice {
 
    private static class ordernumgeneratorservice implements runnable {
 
        private ordernumgenerator ordernumgenerator = new ordernumgenerator();;
        private lock lock = new zookeeperdistrbutelock();
 
        @override
        public void run() {
            lock.getlock();
            try {
                system.out.println(thread.currentthread().getname() + ", 生成订单编号:"  + ordernumgenerator.getordernumber());
            } catch (exception e) {
                e.printstacktrace();
            } finally {
                lock.unlock();
            }
        }
    }
 
    public static void main(string[] args) {
        system.out.println("----------生成唯一订单号----------");
        for (int i = 0; i < 50; i++) {
            new thread(new ordernumgeneratorservice()).start();
        }
    }
}

2.2、方案二

方案二在方案一的基础上进行优化,避免产生“羊群效应”,方案一一旦临时节点删除,释放锁,那么其他在监听这个节点变化的线程,就会去竞争锁,同时访问 zookeeper,那么怎么更好的避免各线程的竞争现象呢,就是使用临时顺序节点,临时顺序节点排序,每个临时顺序节点只监听它本身的前一个节点变化。

流程描述:

分析ZooKeeper分布式锁的实现

具体代码

具体只需要将方案一中的 zookeeperdistrbutelock 改变,增加一个 zookeeperdistrbutelock2,测试代码中使用 zookeeperdistrbutelock2 即可测试,其他代码都不需要改变。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
/**
 * @description 真正实现锁的细节
 */
public class zookeeperdistrbutelock2 extends zookeeperabstractlock {
 
    private countdownlatch countdownlatch = null;
    /**
     * 当前请求节点的前一个节点
     */
    private string beforepath;
    /**
     * 当前请求的节点
     */
    private string currentpath;
 
    public zookeeperdistrbutelock2() {
        if (!zkclient.exists(path)) {
            // 创建持久节点,保存临时顺序节点
            zkclient.createpersistent(path);
        }
    }
 
    @override
    public boolean trylock() {
        // 如果currentpath为空则为第一次尝试拿锁,第一次拿锁赋值currentpath
        if (currentpath == null || currentpath.length() == 0) {
            // 在指定的持久节点下创建临时顺序节点
            currentpath = zkclient.createephemeralsequential(path + "/", "lock");
        }
        // 获取所有临时节点并排序,例如:000044
        list<string> childrenlist = zkclient.getchildren(path);
        collections.sort(childrenlist);
 
        if (currentpath.equals(path + "/" + childrenlist.get(0))) {
            // 如果当前节点在所有节点中排名第一则获取锁成功
            return true;
        } else {
            int wz = collections.binarysearch(childrenlist, currentpath.substring(6));
            beforepath = path + "/" + childrenlist.get(wz - 1);
        }
        return false;
    }
 
    @override
    public void waitlock() {
        // 创建监听
        izkdatalistener izkdatalistener = new izkdatalistener() {
            @override
            public void handledatachange(string s, object o) throws exception {
 
            }
 
            @override
            public void handledatadeleted(string s) throws exception {
                // 释放锁,删除节点时唤醒等待的线程
                if (countdownlatch != null) {
                    countdownlatch.countdown();
                }
            }
        };
 
        // 注册监听,这里是给排在当前节点前面的节点增加(删除数据的)监听,本质是启动另外一个线程去监听前置节点
        zkclient.subscribedatachanges(beforepath, izkdatalistener);
 
        // 前置节点存在时,等待前置节点删除唤醒
        if (zkclient.exists(beforepath)) {
            countdownlatch = new countdownlatch(1);
            try {
                countdownlatch.await();
            } catch (interruptedexception e) {
                e.printstacktrace();
            }
        }
 
        // 删除对前置节点的监听
        zkclient.unsubscribedatachanges(beforepath, izkdatalistener);
    }
 
    /**
     * 释放锁
     */
    @override
    public void unlock() {
        if (zkclient != null) {
            system.out.println("释放锁资源");
            zkclient.delete(currentpath);
            zkclient.close();
        }
    }
}

以上就是分析zookeeper分布式锁的实现的详细内容,更多关于zookeeper分布式锁的资料请关注服务器之家其它相关文章!

原文链接:https://www.cnblogs.com/itwxe/p/14948383.html

延伸 · 阅读

精彩推荐
  • Java教程xml与Java对象的转换详解

    xml与Java对象的转换详解

    这篇文章主要介绍了xml与Java对象的转换详解的相关资料,需要的朋友可以参考下...

    Java教程网2942020-09-17
  • Java教程Java BufferWriter写文件写不进去或缺失数据的解决

    Java BufferWriter写文件写不进去或缺失数据的解决

    这篇文章主要介绍了Java BufferWriter写文件写不进去或缺失数据的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望...

    spcoder14552021-10-18
  • Java教程小米推送Java代码

    小米推送Java代码

    今天小编就为大家分享一篇关于小米推送Java代码,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧...

    富贵稳中求8032021-07-12
  • Java教程Java8中Stream使用的一个注意事项

    Java8中Stream使用的一个注意事项

    最近在工作中发现了对于集合操作转换的神器,java8新特性 stream,但在使用中遇到了一个非常重要的注意点,所以这篇文章主要给大家介绍了关于Java8中S...

    阿杜7472021-02-04
  • Java教程升级IDEA后Lombok不能使用的解决方法

    升级IDEA后Lombok不能使用的解决方法

    最近看到提示IDEA提示升级,寻思已经有好久没有升过级了。升级完毕重启之后,突然发现好多错误,本文就来介绍一下如何解决,感兴趣的可以了解一下...

    程序猿DD9332021-10-08
  • Java教程20个非常实用的Java程序代码片段

    20个非常实用的Java程序代码片段

    这篇文章主要为大家分享了20个非常实用的Java程序片段,对java开发项目有所帮助,感兴趣的小伙伴们可以参考一下 ...

    lijiao5352020-04-06
  • Java教程Java实现抢红包功能

    Java实现抢红包功能

    这篇文章主要为大家详细介绍了Java实现抢红包功能,采用多线程模拟多人同时抢红包,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙...

    littleschemer13532021-05-16
  • Java教程Java使用SAX解析xml的示例

    Java使用SAX解析xml的示例

    这篇文章主要介绍了Java使用SAX解析xml的示例,帮助大家更好的理解和学习使用Java,感兴趣的朋友可以了解下...

    大行者10067412021-08-30