分布式锁学习一(zookeeper实现)

摘要:
Synchronized+lock只能在一台机器上锁定一个jvm。分布式锁定有三种方案:1。数据库2的乐观锁定实现。Redis,Redison(框架)3。Zookeeper设计思想对于单个进程的并发场景,我们可以使用synchronized关键字和Reentrantlock类。在多个jvm服务器之间创建一个锁,同时在zookeeper上创建同一个临时节点,因为临时节点路径保证是唯一的。也就是说,同一目录中的文件名不能重复。ZooKeeper也是如此。

1.在高并发的情况下,如何高效的只允许一个线程修改一条记录

2.分布式情况下,怎么解决订单号生成不重复

为了保证一个方法或属性在高并发情况下的同一时间只能被同一个线程执行,在传统单体应用单机部署的情况下,可以使用synchronized+lock实现加锁,解决高并发问题。synchronized+lock只能锁定单机对单个jvm加锁。

 分布式锁学习一(zookeeper实现)第1张

成员变量 A 存在 JVM1、JVM2、JVM3 三个 JVM 内存中

成员变量 A 同时都会在 JVM 分配一块内存,三个请求发过来同时对这个变量操作,显然结果是不对的

不是同时发过来,三个请求分别操作三个不同 JVM 内存区域的数据,变量 A 之间不存在共享,也不具有可见性,处理的结果也是不对的

现在分布式集群系统后,由于分布式系统多线程、多进程并且分布在不同机器上,要保证一个方法在同一时间只能被一个机器的一个线程执行的话,就要对多个jvm加锁。为了防止分布式系统中的多个进程之间相互干扰,我们需要一种分布式协调技术来对这些进程进行调度。而这个分布式协调技术的核心就是来实现这个分布式锁。

分布式锁应该具备哪些条件

在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行

高可用的获取锁与释放锁

高性能的获取锁与释放锁

具备可重入特性(可理解为重新进入,由多于一个任务并发使用,而不必担心数据错误)

具备锁失效机制,防止死锁

具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败

目前几乎很多大型网站及应用都是分布式部署的,分布式场景中的数据一致性问题一直是一个比较重要的话题。分布式的CAP理论告诉我们“任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足两项。”所以,很多系统在设计之初就要对这三者做出取舍。在互联网领域的绝大多数的场景中,都需要牺牲强一致性来换取系统的高可用性,系统往往只需要保证“最终一致性”,只要这个最终时间是在用户可以接受的范围内即可。

 
在很多场景中,我们为了保证数据的最终一致性,需要很多的技术方案来支持,比如分布式事务、分布式锁等。有的时候,我们需要保证一个方法在同一时间内只能被同一个线程执行。

分布式锁有三种方案:

1.数据库的乐观锁实现(并发量不够,别用)

2.redis(setnx命令)、redisson(框架)

3.zookeeper(本次采用zookeeper)

设计思想

对于单进程的并发场景,我们可以使用synchronized关键字和Reentrantlock类等。

对于分布式场景,我们可以使用分布式锁。

创建锁

多个jvm服务器之间,同时在zookeeper上创建相同的一个临时节点,因为临时节点路径是保证唯一。

只要谁能够创建节点成功,谁就能够获取到锁。没有创建成功节点,只能注册个监听器监听这个锁并进行等待,当释放锁的时候,采用事件通知给其他客户端重新获取锁的资源。这时候客户端使用事件监听,如果该临时节点被删除的话,重新进入获取锁的步骤。

释放锁

Zookeeper使用直接关闭临时节点session会话连接,因为临时节点生命周期与session会话绑定在一块,如果session会话连接关闭的话,该临时节点也会被删除。这时候客户端使用事件监听,如果该临时节点被删除的话,重新进入到获取锁的步骤。

zookeeper

ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件。

那对于我们初次认识的人,可以理解成ZooKeeper就像是我们的电脑文件系统,我们可以在d盘中创建文件夹a,并且可以继续在文件夹a中创建 文件夹a1,a2。

那我们的文件系统有什么特点??那就是同一个目录下文件名称不能重复,同样ZooKeeper也是这样的。

在ZooKeeper所有的节点,也就是文件夹称作 Znode,而且这个Znode节点是可以存储数据的。

ZooKeeper可以创建4种类型的节点,分别是:

  • 持久性节点

  • 持久性顺序节点

  • 临时性节点

  • 临时性顺序节点

持久性节点和临时性节点的区别:

持久性节点表示只要你创建了这个节点,那不管你ZooKeeper的客户端是否断开连接,ZooKeeper的服务端都会记录这个节点。

临时性节点刚好相反,一旦你ZooKeeper客户端断开了连接,那ZooKeeper服务端就不再保存这个节点。

顺序性节点:

在创建节点的时候,ZooKeeper会自动给节点编号比如0000001 ,0000002 这种的。

最后说下,zookeeper有一个监听机制,客户端注册监听它关心的目录节点,当目录节点发生变化(数据改变、被删除、子目录节点增加删除)等,zookeeper会通知客户端。

 代码实现

pom.xml导入zk客户端

        <dependency>
            <groupId>com.101tec</groupId>
            <artifactId>zkclient</artifactId>
            <version>0.10</version>
        </dependency>
生成订单号自加:
public class OrderNumGeneratorUtil {
    private static int num = 0;

    public String getNumber(){
        return "订单号为:" +(++num);
    }
}

创建锁的接口

public interface ZkLock {
    //加锁
    public void  lock();

    //释放锁
    public void unLock();
}

模板方法模式

 //将重复代码抽象到子类中(模板方法设计模式)
public abstract class ZkAbstractLock implements ZkLock {
    private static final String CONNECTION="127.0.0.1:2181";
    protected ZkClient zkClient = new ZkClient(CONNECTION);
    protected String lockPath="/zk_lock";
    protected CountDownLatch countDownLatch=null;

    //获取锁
    public void lock() {
        //如果节点创建成功,直接执行业务逻辑,如果节点创建失败,进行等待,递归调用lock
        if (tryLock()) {
            System.out.println("*************成功获取锁***************");
        }else {
            //进行等待
            waitLock();
            lock();
        }
    }

    abstract void waitLock();
    abstract boolean tryLock();

    //释放锁
    public void unLock() {
        if (zkClient != null) {
            zkClient.close();
            System.out.println("----------------释放锁完毕-------------");
            System.out.println();
        }
    }
}

 创建子类实现上面的 抽象方法 

/**  查看节点:ls /
 *   删除节点:delete /node
 *   节点粗分为临时节点和持久性节点
 *   创建临时节点: create  -e /node value
 *   创建持久性节点create  /node value
 */
public class ZkDistrbuteLock extends ZkAbstractLock {
    boolean tryLock() {
        try {
            //创建临时节点
            zkClient.createEphemeral(lockPath);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    void waitLock() {
        //zk数据监听器
        IZkDataListener iZkDataListener = new IZkDataListener() {
            // 节点被删除
            public void handleDataDeleted(String arg0) throws Exception {
                if (countDownLatch != null) {
                    countDownLatch.countDown(); // 计数器为0的情况,await 后面的继续执行
                }
            }

            // 节点被修改
            public void handleDataChange(String arg0, Object arg1) throws Exception {

            }
        };

        // 加监听事件
        zkClient.subscribeDataChanges(lockPath, iZkDataListener);
        if (zkClient.exists(lockPath)) {
            countDownLatch = new CountDownLatch(1);
            try {
                countDownLatch.await(); //等待时候 就不往下走了   当为0 时候 后面的继续执行
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //解除监听事件
        zkClient.unsubscribeDataChanges(lockPath, iZkDataListener);
    }
}

   获取订单号业务层1(使用juc lock锁机制)  

public class OrderService1 {
    private OrderNumGeneratorUtil orderNumGeneratorUtil=new OrderNumGeneratorUtil();

    public void getNumber1(){
        String str= orderNumGeneratorUtil.getNumber();
        System.out.println(str);
    }

    //使用juc lock锁机制
    private Lock lock=new ReentrantLock();
    public void getNumber2(){
        lock.lock();
        try{
            String str= orderNumGeneratorUtil.getNumber();
            System.out.println(str);
        }finally {
            lock.unlock();
        }

    }
}

 获取订单号业务层2(使用zk分布锁机制)

/**
 * 使用zk分布锁机制
 */
public class OrderService2 {

    private OrderNumGeneratorUtil orderNumGeneratorUtil=new OrderNumGeneratorUtil();
    private ZkDistrbuteLock zkLock=new ZkDistrbuteLock();
    //获取订单号
    public void getNumber(){
        //zk加锁
        zkLock.lock();
        try{
            String str= orderNumGeneratorUtil.getNumber();
            System.out.println(str);
        }finally {
            //zk释放锁
            zkLock.unLock();
        }
    }
}

 测试方法

/**
 * 多线程下获取订单测试类
 */
public class ClientTest {

    //多线程未加锁生成订单号
    public static void test1(){
        OrderService1 orderService1=new OrderService1();
        for(int i=1;i<=500;i++){
            new Thread(()-> {
                orderService1.getNumber1();
            }).start();
        }
    }

    //lock加锁
    public static void test2(){
        OrderService1 orderService1=new OrderService1();
        for(int i=1;i<=500;i++){
            new Thread(()-> {
                orderService1.getNumber2();
            }).start();
        }
    }

    //多jvm下  lock加锁
    public static void test3(){
        for(int i=1;i<=500;i++){
            new Thread(()-> {
                new OrderService1().getNumber2();
            }).start();
        }
    }

    //多jvm下  zookeeper加锁
    public  static void test4(){
        for(int i=1;i<=500;i++){
            new Thread(()-> {
                new OrderService2().getNumber();
            }).start();
        }
    }

    public static void main(String[] args) {
        //test1();
        //test2();
       //test3();
      test4();
    }
}

  运行结果:

 分布式锁学习一(zookeeper实现)第2张

 

 

 

免责声明:文章转载自《分布式锁学习一(zookeeper实现)》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇正确显示textarea中输入的回车和空格如何分析和提高(C/C++)程序的编译速度?下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

分布式日志系统

FROM:http://go-on.iteye.com/blog/1789466 背景 Google、Facebook、Amazon等互联网巨头对于数据的创造性使用,创造出了很多辉煌的商业产品。如Amazon创造出的新的推荐模式:”查询此商品的顾客也查询了。。。。。”、“看过此商品的后的顾客买的其他商品有。。。。。。”、“购买了您最近浏览过的商品的顾客同时...

开源中间件大舞台

开源中间件大舞台 全文主要内容:一、中间件是什么?二、中间件的主要作用三、中间件的优越性四、中间件的应用领域与分类五、中间件的设计原则六、中间件的技术规范七、中间件的复杂性八、中间件的开发思路九、中间件的开源模式十、遵循J2EE的开源中间件十一、开源应用服务器比较最后:开源中间件将成为潮流   企业应用软件与桌面应用软件一样,是极为复杂的。企业应用软件具有...

.Net Core 商城微服务项目系列(十三):搭建Log4net+ELK+Kafka日志框架

之前是使用NLog直接将日志发送到了ELK,本篇将会使用Docker搭建ELK和kafka,同时替换NLog为Log4net。 一.搭建kafka 1.拉取镜像 //下载zookeeper docker pull wurstmeister/zookeeper //下载kafka docker pull wurstmeister/kafka:2.11-...

docker中zookeeper集群的安装与使用 以及zookeeper集群对mysq集群的管理

未完待续 在zookeeper容器中 配置文件的位置 /conf/zoo.cfg bash-4.4# cd /conf/bash-4.4# pwd /conf bash-4.4# ls configuration.xsl log4j.properties zoo.cfg zoo_sample.cfg bash-4.4# ls -...

linux运维、架构之路-分布式存储Ceph

一、Ceph介绍        Ceph是一个Linux PB级分布式文件系统,能够在维护POSIX兼容性的同时加入了复制和容错功能。Ceph号称高可用的分布式存储系统,通过多个MON节点(通常为3个)维护集群的状态及元数据信息,而真正存储数据的OSD节点通过向MON节点汇报状态,并通过CRUSH算法将数据副本布局到相应OSD的所在磁盘上,完成数据的持久化...

Apache Beam是什么?

  不多说,直接上干货! 以下是Apache Beam的官网 : https://beam.apache.org/ Apache Beam的前世今生       Apache Beam前身是Google Dataflow SDK,DataFlow是谷歌的提供大数据计算平台。在DataFlow之前,谷歌的批处理和流处理(流计算,实时处理)使用了不同系统,流...