基于Zookeeper实现分布式锁

一 为什么使用分布式锁

我们在开发应用的时候,如果需要对某一个共享变量进行多线程同步访问的时候,我们往往采用synchronized或者Lock的方解决多线程的代码同步问题,这时多线程的运行是在同一个JVM之下是没有任何问题的。
但当我们的应用是分布式集群工作的情况下,属于多JVM下的工作环境,跨JVM之间已经无法通过多线程的锁解决同步问题。
那么就需要一种更加高级的锁机制,来处理跨机器的进程之间的数据同步问题——分布式锁。
在这里插入图片描述
分布锁的实现方式有三种:

基于数据库实现分布式锁
基于缓存(Redis等)实现分布式锁
基于Zookeeper实现分布式锁

今天我们讨论一下基于Zookeeper实现分布式锁的原理。

二 分布式锁应具备的条件


1、在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行;
2、高可用的获取锁与释放锁;
3、高性能的获取锁与释放锁;
4、具备可重入特性;
5、具备锁失效机制,防止死锁;
6、具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败。

三 实现过程原理

核心思想:当客户端要获取锁,则创建临时顺序节点,使用完锁,则删除该节点。
获取锁:
首先,在Zookeeper当中创建一个持久节点Lock。当第一个客户端想要获得锁时,需要在Lock这个节点下面创建一个临时顺序节点 Lock1
在这里插入图片描述
之后,Client1查找Lock下面所有的临时顺序节点并比较排序,判断自己所创建的节点Lock1是不是序号最小的哪一个。如果是,则成功获得锁。
在这里插入图片描述
这时候,如果再有一个客户端 Client2 前来获取锁,则在Lock下载再创建一个临时顺序节点Lock2。
在这里插入图片描述
Client2查找Lock下面所有的临时顺序节点并排序,判断自己所创建的节点Lock2是不是序号最小的那个,结果发现节点Lock2并不是最小的。

于是,Client2向排序仅比它靠前的节点Lock1注册Watcher事件监听器,监听删除事件,用于监听Lock1节点是否存在。这意味着Client2抢锁失败,进入了等待状态。
在这里插入图片描述
这时候,如果又有一个客户端Client3前来获取锁,则在Lock下载再创建一个临时顺序节点Lock3。

在这里插入图片描述
Client3查找Lock下面所有的临时顺序节点并排序,判断自己所创建的节点Lock3是不是序号最小的那个,结果同样发现节点Lock3并不是最小的。

于是,Client3向排序仅比它靠前的节点Lock2注册Watcher,用于监听Lock2节点是否存在。这意味着Client3同样抢锁失败,进入了等待状态。
在这里插入图片描述
这样一来,Client1得到了锁,Client2监听了Lock1,Client3监听了Lock2。这恰恰形成了一个等待队列,很像是Java当中ReentrantLock所依赖的。

释放锁

释放锁分为两种情况:

1.任务完成,客户端显示释放

当任务完成时,Client1会显示调用删除节点Lock1的指令。在这里插入图片描述

2.任务执行过程中,客户端崩溃

获得锁的Client1在任务执行过程中,如果Duang的一声崩溃,则会断开与Zookeeper服务端的链接。根据临时节点的特性,相关联的节点Lock1会随之自动删除。
在这里插入图片描述
由于Client2一直监听着Lock1的存在状态,当Lock1节点被删除,Client2会立刻收到通知。这时候Client2会再次查询Lock下面的所有节点,确认自己创建的节点Lock2是不是目前最小的节点。如果是最小,则Client2顺理成章获得了锁。
在这里插入图片描述
同理,如果Client2也因为任务完成或者节点崩溃而删除了节点Lock2,那么Client3就会接到通知。
在这里插入图片描述
最终,Client3成功得到了锁。
在这里插入图片描述

方案:

可以直接使用zookeeper第三方库Curator客户端,这个客户端中封装了一个可重入的锁服务
Curator提供的InterProcessMutex是分布式锁的实现。acquire方法用户获取锁,release方法用于释放锁。
提供了一个12306的一个抢票小Demo来了解zookeeper分布式锁的实现。

package cn.itcast.test;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.*;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.util.concurrent.TimeUnit;

/*
 需求:模拟车站售票
 */
public class Ticket12306 implements  Runnable {

    //票数
    private int num=50;

    //声明分布式锁
    private InterProcessMutex lock;


    public Ticket12306() {
        //一旦创建线程任务对象的时候就与zookeeper建立连接
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000,2);
        CuratorFramework client = CuratorFrameworkFactory.builder()
                .connectString("192.168.179.129:2181")
                .retryPolicy(retryPolicy)
                .build();
        //开启链接
        client.start();
        //得到了分布式锁
        lock = new InterProcessMutex(client,"/lock");

    }

    @Override
    public void run() {
        while(true){
            try {
                //获取锁
                lock.acquire(3, TimeUnit.SECONDS);//参数一:等待锁的最大超时时间, 参数二;第一个参数的时间单位
                if(num>0){
                    System.out.println(Thread.currentThread().getName()+"卖出了第"+num+"号票");
                    num--;
                }else{
                    System.out.println("售罄了..");
                    break;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                //释放锁
                try {
                    lock.release();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        //创建Runnable实现类对象
        Ticket12306 ticket = new Ticket12306();
        //模拟两个窗口售票就是两个线程
        Thread thread1 = new Thread(ticket,"售票员甲");
        Thread thread2 = new Thread(ticket,"售票员乙");
        //开启线程
        thread1.start();
        thread2.start();
    }
}

感谢观看~

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值