• 分布式锁(Zookeeper实现)


    分布式锁

    分布式锁,这个主要得益于 ZooKeeper 为我们保证了数据的强一致性。锁服务可以分为两类,一个是 保持独占,另一个是 控制时序。

    1. 所谓保持独占,就是所有试图来获取这个锁的客户端,最终只有一个可以成功获得这把锁。通常的做法是把 zk 上的一个 znode 看作是一把锁,通过 create znode 的方式来实现。所有客户端都去创建 /distribute_lock 节点,最终成功创建的那个客户端也即拥有了这把锁。

    2. 控制时序,就是所有视图来获取这个锁的客户端,最终都是会被安排执行,只是有个全局时序了。做法和上面基本类似,只是这里 /distributelock 已经预先存在,客户端在它下面创建临时有序节点(这个可以通过节点的属性控制:CreateMode.EPHEMERALSEQUENTIAL 来指定)。Zk 的父节点(/distribute_lock)维持一份 sequence, 保证子节点创建的时序性,从而也形成了每个客户端的全局时序。

    分布式锁    单纯的Lock锁或者synchronize只能解决单个jvm线程安全问题

    分布式 Session 一致性问题

    分布式全局id(也可以使用分布式锁)

    分布式锁,产生的原因是 集群

    在单台服务器上 如何生成订单号(保证唯一),方案 UUid+时间戳方式, redis方式

    生成订单号, 秒杀抢购时候,

      首先预测100w订单号,生成放在redis。客户端下单,直接redis去获取即可。因为redis单线程的,多个线程去获取时候,安全呀。

      实际150w用户。当redis剩下50w订单号时候,继续生成补充之。 

    如果在集群情况,UUid+时间戳。不能保证唯一性!,原因:  

     如果单台:

    uuid+时间戳,生成的代码逻辑:

    package com.toov5.Lock;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    //生成订单号 时间戳
    public class OrderNumGenerator {
      //区分不同的订单号
        private static int count = 0;
    //单台服务器,多个线程 同事生成订单号
        public String getNumber(){
            try {
                Thread.sleep(300);
            } catch (Exception e) {
                // TODO: handle exception
            }
            SimpleDateFormat simpt = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");  
            return simpt.format(new Date()) + "-" + ++count;  //时间戳后面加了 count
    
        }
        
    }

    开启100个线程调用之:

    package com.toov5.Lock;
    
    public class OrderService implements  Runnable {
          
         private OrderNumGenerator    orderNumGenerator  = new OrderNumGenerator(); //定义成全局的
        
         public void run() {
            getNumber();     
        }
        
        public void getNumber(){
        String number =    orderNumGenerator.getNumber();
        System.out.println(Thread.currentThread().getName()+"num"+number);
        }
        
        public static void main(String[] args) {
            OrderService orderService = new OrderService();
            for (int i = 0; i <100; i++) {  //开启100个线程
                new Thread(orderService).start();
            }    
        }
        
    }

    结果:

     多个线程共享区同一个全局变量,线程安全问题!

     

     解决方案就是加锁嘛!

    或者使用 lock锁也可以

    public class OrderService implements Runnable {
        private OrderNumGenerator orderNumGenerator = new OrderNumGenerator();
        // 使用lock锁
        private java.util.concurrent.locks.Lock lock = new ReentrantLock();
    
        public void run() {
            getNumber();
        }
    
        public void getNumber() {
            try {
                // synchronized (this) {
                lock.lock();
                String number = orderNumGenerator.getNumber();
                System.out.println(Thread.currentThread().getName() + ",生成订单ID:" + number);
                // }
    
            } catch (Exception e) {
    
            } finally {
                lock.unlock();
            }
        }
    
        public static void main(String[] args) {
            System.out.println("####生成唯一订单号###");
            OrderService orderService = new OrderService();
            for (int i = 0; i < 100; i++) {
                new Thread(orderService).start();
            }
    
        }
    }

    如果是集群环境下:

      

         每台jvm都有一个 count   都有 自增的代码 操作这个 count  三个不同的jvm 独立的  用户请求 过来 映射到哪个 就操作哪个 

       这时候就产生分布式锁的问题

    这时候需要分布式锁:共享一个count

    jvm1 操作时候 其他的jvm2 和 jvm3 不可以操作他!

    分布式锁  保证分布式领域中共享数据安全问题

    1、数据库实现(效率低,不推荐)

    2、redis实现(使用redission实现,但是需要考虑思索,释放问题。繁琐一些)

    3、Zookeeper实现   (使用临时节点,效率高,失效时间可以控制)

     4、Spring Cloud 实现全局锁(内置的)

    业务场景

    在分布式情况,生成全局订单号ID

    产生问题

    在分布式(集群)环境下,每台JVM不能实现同步,在分布式场景下使用时间戳生成订单号可能会重复

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

    1. 使用分布式锁
    2. 提前生成好,订单号,存放在redis取。获取订单号,直接从redis中取。

    使用分布式锁生成订单号技术

    1.使用数据库实现分布式锁

    缺点:性能差、线程出现异常时,容易出现死锁

    2.使用redis实现分布式锁

    缺点:锁的失效时间难控制、容易产生死锁、非阻塞式、不可重入

    3.使用zookeeper实现分布式锁

    实现相对简单、可靠性强、使用临时节点,失效时间容易控制

    什么是分布式锁

    分布式锁一般用在分布式系统或者多个应用中,用来控制同一任务是否执行或者任务的执行顺序。在项目中,部署了多个tomcat应用,在执行定时任务时就会遇到同一任务可能执行多次的情况,我们可以借助分布式锁,保证在同一时间只有一个tomcat应用执行了定时任务

    使用Zookeeper实现分布式锁

    Zookeeper实现分布式锁原理

    使用zookeeper创建临时序列节点来实现分布式锁,适用于顺序执行的程序,大体思路就是创建临时序列节点,找出最小的序列节点,获取分布式锁,程序执行完成之后此序列节点消失,通过watch来监控节点的变化,从剩下的节点的找到最小的序列节点,获取分布式锁,执行相应处理,依次类推……

     如何使用zk实现分布式锁?

        临时节点

        持久节点

    分布式锁使用 临时节点,实现:

    实现步骤:

    多个Jvm同时在Zookeeper上创建同一个相同的节点( /Lock)

    zk节点唯一的! 不能重复!节点类型为临时节点, jvm1创建成功时候,jvm2和jvm3创建节点时候会报错,该节点已经存在。这时候 jvm2和jvm3进行等待。

                                                                                     jvm1的程序现在执行完毕,执行释放锁。关闭当前会话。临时节点不复存在了并且事件通知Watcher,jvm2和jvm3继续创建。

                          

     ps:zk强制关闭时候,通知会有延迟。但是close()方法关闭时候,延迟小

      如果程序一直不处理完,可能导致思索(其他的一直等待)。设置有效期~  直接close()掉 其实连接也是有有效期设置的 大家可以找下相关资料看下哦

        上代码!

           引入Jar包:

               

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      <modelVersion>4.0.0</modelVersion>
      <groupId>com.toov5.FbsLock</groupId>
      <artifactId>FbsLock</artifactId>
      <version>0.0.1-SNAPSHOT</version>
      <dependencies>
    		<dependency>
    			<groupId>com.101tec</groupId>
    			<artifactId>zkclient</artifactId>
    			<version>0.10</version>
    		</dependency>
    	</dependencies>
      
    </project>
    

     创建锁的接口

    package com.toov5.Lock;
    
    public interface ExtLock {
       
        //ExtLock基于zk实现分布式锁
        public void  getLock();
        
        //释放锁
        public void unLock();
        
    }

    模板方法模式

    package com.toov5.Lock;
    
    import org.I0Itec.zkclient.ZkClient;
    
    //将重复代码抽象到子类中(模板方法设计模式)
    public abstract class ZookeeperAbstractLock implements ExtLock {
        private static final String CONNECTION="192.168.91.5:2181";
        protected ZkClient zkClient = new ZkClient(CONNECTION);
        private String lockPath="/lockPath";
        
         //获取锁
          public void getLock() { 
              //1、连接zkClient 创建一个/lock的临时节点
              // 2、 如果节点创建成果,直接执行业务逻辑,如果节点创建失败,进行等待 
              if (tryLock()) {
                System.out.println("#####成功获取锁######");
            }else {
                //进行等待
                waitLock();
            }
         
            //3、使用事件通知监听该节点是否被删除    ,如果是,重新进入获取锁的资源  
            
        }
          
       //创建失败 进行等待
        abstract void waitLock();
    
    
        abstract boolean tryLock();
         
         
        //释放锁
          public void unLock() {
            //执行完毕 直接连接
              if (zkClient != null) {
                zkClient.close();
                System.out.println("######释放锁完毕######");
            }
            
        }
          
    }

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

    package com.toov5.Lock;
    
    import java.util.concurrent.CountDownLatch;
    
    import org.I0Itec.zkclient.IZkDataListener;
    public class ZookeeperDistrbuteLock extends ZookeeperAbstractLock {
    
        @Override
        boolean tryLock() {
            try {
                zkClient.createEphemeral(lockPath);
    //            System.out.println("#########获取锁######");
                return true;
            } catch (Exception e) {
                // 如果失败 直接catch
                return false;
            }
        }
    
        @Override
        void waitLock() {
    
            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)) {  //如果 检查出 已经被创建了 就new 然后进行等待
                countDownLatch = new CountDownLatch(1);
                try {
                    countDownLatch.wait(); //等待时候 就不往下走了   当为0 时候 后面的继续执行
                } catch (Exception e) {
                    // TODO: handle exception
                }
            }
            //后面代码继续执行
            //为了不影响程序的执行 建议删除该事件监听 监听完了就删除掉
            zkClient.unsubscribeDataChanges(lockPath, iZkDataListener);
    
        }
    
    }

    生产订单号:

    package com.toov5.Lock;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    //生成订单号 时间戳
    public class OrderNumGenerator {
      //区分不同的订单号
        private static int count = 0;
    //单台服务器,多个线程 同事生成订单号
        public String getNumber(){
            try {
                Thread.sleep(500);
            } catch (Exception e) {
                
            }
            SimpleDateFormat simpt = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");  
            return simpt.format(new Date()) + "-" + ++count;  //时间戳后面加了 count
    
        }
        
    }

    运行方法:

    package com.toov5.Lock;
    
    public class OrderService implements Runnable {
    
        private OrderNumGenerator orderNumGenerator = new OrderNumGenerator(); // 定义成全局的
        private ExtLock lock = new ZookeeperDistrbuteLock();
    
        public void run() {
            getNumber();
        }
    
        public synchronized void getNumber() { // 加锁 保证线程安全问题 让一个线程操作
            try {
                lock.getLock();
                String number = orderNumGenerator.getNumber();
                System.out.println(Thread.currentThread().getName() + ",number" + number);
    
            } catch (Exception e) {
    
            } finally {
                lock.unLock();
            }
        }
    
        public static void main(String[] args) {
    //        OrderService orderService = new OrderService();
            for (int i = 0; i < 100; i++) { // 开启100个线程
                //模拟分布式锁的场景
                new Thread(new OrderService()).start();
            }
        }
    
    }

    运行结果:

    代码欣赏:

  • 相关阅读:
    日志命令
    QPS、TPS、PV、UV、GMV、IP、RPS
    Tmux实践
    1-2+3-4+5-6....-100 除了88以外其他数字的和 python 实现
    MCNN: 多列卷积神经网络的单图像人群计数
    facenet-pytorch库的简单使用
    SENet笔记
    目标检测 anchor的生成
    matplotlib动图绘制
    感知机算法及其对偶形式
  • 原文地址:https://www.cnblogs.com/toov5/p/9899489.html
Copyright © 2020-2023  润新知