Zookeeper实现分布式锁

1.分布式锁的由来:

在程序开发过程中不得不考虑的就是并发问题。在java中对于同一个jvm而言,jdk已经提供了lock和同步等。但是在分布式情况下,往往存在多个进程对一些资源产生竞争关系,而这些进程往往在不同的机器上,这个时候jdk中提供的已经不能满足。分布式锁顾明思议就是可以满足分布式情况下的并发锁。 

确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:
1.互斥性。在任意时刻,只有一个客户端能持有锁。
2.不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
3.具有容错性。只要大部分的节点正常运行,客户端就可以加锁和解锁。
4.解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。

下面我们讲解怎么利用zk实现分布式锁。

实现分布式锁方式四种:

    1:Zookeeper
    2:Redis
    3:memcached
    4:Mysql

2.实现思路:

2.1 zk简单介绍:

ZooKeeper是Apache软件基金会的一个软件项目,他为大型分布式计算提供开源的分布式配置服务、同步服务和命名注册。在 ZooKeeper 中,节点类型可以分为持久节点(PERSISTENT )、临时节点(EPHEMERAL),以及时序节点(SEQUENTIAL ),具体在节点创建过程中,一般是组合使用,可以生成 4 种节点类型:持久节点(PERSISTENT),持久顺序节点(PERSISTENT_SEQUENTIAL),临时节点(EPHEMERAL),临时顺序节点(EPHEMERAL_SEQUENTIAL);具体节点含义,谷歌之。

2.2 利用zk实现:

当很多进程需要访问共享资源时,我们可以通过zk来实现分布式锁。主要步骤是: 
1.建立一个节点,假如名为:lock 。节点类型为持久节点(PERSISTENT) 
2.每当进程需要访问共享资源时,会调用分布式锁的lock()或tryLock()方法获得锁,这个时候会在第一步创建的lock节点下建立相应的顺序子节点,节点类型为临时顺序节点(EPHEMERAL_SEQUENTIAL),通过组成特定的名字name。 
3.在建立子节点后,对lock下面的name子节点,判断刚刚建立的子节点是否存在,则获得该锁对资源进行访问。 
4.假如存在,并给该节点是否存在注册监听事件。同时在这里阻塞。等待监听事件的发生,获得锁控制权。 
5.当调用完共享资源后,调用unlock()方法,关闭zk,进而可以引发监听事件,释放该锁。 


2.3:重要方法

CuratorFramework提供的方法:

方法名描述
create()开始创建操作, 可以调用额外的方法(比如方式mode 或者后台执行background) 并在最后调用forPath()指定要操作的ZNode
delete()开始删除操作. 可以调用额外的方法(版本或者后台处理version or background)并在最后调用forPath()指定要操作的ZNode
checkExists()开始检查ZNode是否存在的操作. 可以调用额外的方法(监控或者后台处理)并在最后调用forPath()指定要操作的ZNode
getData()开始获得ZNode节点数据的操作. 可以调用额外的方法(监控、后台处理或者获取状态watch, background or get stat) 并在最后调用forPath()指定要操作的ZNode
setData()开始设置ZNode节点数据的操作. 可以调用额外的方法(版本或者后台处理) 并在最后调用forPath()指定要操作的ZNode
getChildren()开始获得ZNode的子节点列表。 以调用额外的方法(监控、后台处理或者获取状态watch, background or get stat) 并在最后调用forPath()指定要操作的ZNode
inTransaction()开始是原子ZooKeeper事务. 可以复合create, setData, check, and/or delete 等操作然后调用commit()作为一个原子操作提交

后台操作的通知和监控可以通过ClientListener接口发布. 你可以在CuratorFramework实例上通过addListener()注册listener, Listener实现了下面的方法:


CuratorZookeeperClient  参数:
zookeeperFactory - 用于创建 ZooKeeper 实例的工厂 ensembleProvider  - 合奏提供者 sessionTimeoutMs  - 会话超时 connectionTimeoutMs  - 连接超时 watcher  - 默认观察者或null retryPolicy  - 要使用的重试策略 canBeReadOnly  - 如果为true,则允许ZooKeeper客户端在网络分区的情况下进入只读模式。详情  ZooKeeper(String, int, Watcher, long, byte[], boolean)  请见 connectionHandlingPolicy  - 连接处理策略 - 使用其中一个预定义的策略或编写自己的策略

3.代码实现:

下面将讲解使用java实现分布式锁:

@Configuration
@Order(1)
public class ZookeeperConfig {
    //间隔时间
    static final Integer INTERVALS = 5000;
    //重试次数
    static final Integer RETRYNUM = 10;

    static final String IP = "10.2.3.13:2181";
    //回话超时时间
    static final Integer SESSIONTIMEOUTMS = 20000;
    //连接创建超时时间
    static final Integer CONNECTIONTIMEOUTMS = 5000;

    /**
     * zk重连策略
     * @return
     */
    @Bean
    public RetryNTimes retryNTimes(){
        return new RetryNTimes(INTERVALS, RETRYNUM);
    }

    /**
     * zk客户端
     * @return
     */
    @Bean
    public CuratorFramework curatorFrameworkFactory(){
        CuratorFramework curatorFramework = CuratorFrameworkFactory.newClient(IP, SESSIONTIMEOUTMS, CONNECTIONTIMEOUTMS, retryNTimes());
        curatorFramework.start();
        return curatorFramework;
    }

    @Bean
    public DistributedLock createDistributedLock(){
        DistributedLock distributedLock = new DistributedLock(curatorFrameworkFactory());
        distributedLock.init();
        return distributedLock;
    }
}

@Component
public class DistributedLock {

    final static Logger log = LoggerFactory.getLogger(DistributedLock.class);
    //zk客户端
    private CuratorFramework client = null;

    //用于挂起当前请求,并且等待上一个分布式锁释放
    private static CountDownLatch zkLockLatch = new CountDownLatch(1);

    //分布式锁的总结点名
    private static final String ZK_LOCK_PROJECT = "all_locks";
    //分布式锁子节点
    private static final String DISTRIBUTED_LOCK = "distributed_lock";

    public DistributedLock(CuratorFramework client) {
        this.client = client;
    }

    /**
     * 初始化锁
     */
    public void init() {
        client = client.usingNamespace(null);

        /**
         * 创建zk锁的总节点,相当于idea工作空间下的项目
         *
         *    all_locks
         *       |
         *       __distributed_lock
         */
        try {
            if (client.checkExists().forPath("/" + ZK_LOCK_PROJECT) == null) {
                client.create()
                        .creatingParentsIfNeeded()
                        .withMode(CreateMode.PERSISTENT)
                        .withACL(Ids.OPEN_ACL_UNSAFE)
                        .forPath("/" + ZK_LOCK_PROJECT);
            }
            //针对zk的分布式锁节点,创建相应的watcher事件监听
            addWatcherToLock("/" + ZK_LOCK_PROJECT);
        } catch (Exception e) {
            log.error("客户端连接zookeeper服务器错误。。。请重试。。。");
            e.printStackTrace();
        }
    }

    /**
     * 获取分布式锁
     */
    public void getLock() {
        //使用死循环,当且仅当上一个锁释放且当前请求获得锁成功后才会跳出
        while (true) {
            try {
                client.create()
                        .creatingParentsIfNeeded()
                        .withMode(CreateMode.EPHEMERAL)
                        .withACL(Ids.OPEN_ACL_UNSAFE)
                        .forPath("/" + ZK_LOCK_PROJECT + "/" + DISTRIBUTED_LOCK);
                log.info("获得分布式锁成功");
                return;        //如果锁的节点被创建成功,则锁没有被占用
            } catch (Exception e) {
                log.info("获得分布式锁失败");
                try {
                    //如果没有获取到锁,需要重新设置同步资源值
                    if (zkLockLatch.getCount() <= 0) {
                        zkLockLatch = new CountDownLatch(1);
                    }
                    //阻塞线程
                    zkLockLatch.await();
                } catch (InterruptedException e1) {
                    // TODO Auto-generated catch block
                    e1.printStackTrace();
                }
            }
        }
    }

    /**
     * 释放分布式锁
     */
    public boolean releaseLock() {
        try {
            if (client.checkExists().forPath("/" + ZK_LOCK_PROJECT + "/" + DISTRIBUTED_LOCK) != null) {
                client.delete().forPath("/" + ZK_LOCK_PROJECT + "/" + DISTRIBUTED_LOCK);
            }
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        log.info("分布式锁释放完毕");
        return true;
    }

    /**
     * 创建watcher监听
     *
     * @throws Exception
     */
    public void addWatcherToLock(String path) throws Exception {

        final PathChildrenCache cache = new PathChildrenCache(client, path, true);
        //发布最初的事件
        cache.start(StartMode.POST_INITIALIZED_EVENT);

        cache.getListenable().addListener(new PathChildrenCacheListener() {

            @Override
            public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {
                if (event.getType().equals(PathChildrenCacheEvent.Type.CHILD_REMOVED)) {
                    String path = event.getData().getPath();
                    log.info("上一个会话已释放锁或已断开,节点路径为:" + path);
                    if (path.contains(DISTRIBUTED_LOCK)) {
                        log.info("释放计数器,让当前请求来获得分布式锁");
                        zkLockLatch.countDown();
                    }
                }

            }
        });

    }

}
获取分布式锁--->distributedLock.getLock();
释放分布式锁--->distributedLock.releaseLock();

4.linux:



完成!!!!!!!!!!!!!!!!!!!!!!!!


分布式锁之间的对比

数据库分布式锁实现

缺点:1.db操作性能较差,并且有锁表的风险 
2.非阻塞操作失败后,需要轮询,占用cpu资源; 
3.长时间不commit或者长时间轮询,可能会占用较多连接资源

Redis(缓存)分布式锁实现

缺点:1.锁删除失败 过期时间不好控制 
2.非阻塞,操作失败后,需要轮询,占用cpu资源;

ZK分布式锁实现

缺点:性能不如redis实现,主要原因是写操作(获取锁释放锁)都需要在Leader上执行,然后同步到follower。

总之:ZooKeeper有较好的性能和可靠性。

从理解的难易程度角度(从低到高)数据库 > 缓存 > Zookeeper 
从实现的复杂性角度(从低到高)Zookeeper >= 缓存 > 数据库 
从性能角度(从高到低)缓存 > Zookeeper >= 数据库 
从可靠性角度(从高到低)Zookeeper > 缓存 > 数据库

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值