百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

分布式锁(Zookeeper实现)(分布式锁实现方式及优缺点)

nanshan 2024-11-14 16:39 28 浏览 0 评论

toov5 | 作者

urlify.cn/iUNfyq | 来源

分布式锁

分布式锁,这个主要得益于 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();
        }
    }

}

运行结果:

代码欣赏:

相关推荐

使用nginx配置域名及禁止直接通过IP访问网站

前段时间刚搭建好这个网站,一直没有关注一个问题,那就是IP地址也可以访问我的网站,今天就专门研究了一下nginx配置问题,争取把这个问题研究透彻。1.nginx配置域名及禁止直接通过IP访问先来看n...

如何在 Linux 中使用 PID 号查找进程名称?

在Linux的复杂世界中,进程是系统运行的核心,每个进程都由一个唯一的「进程ID」(PID)标识。无论是系统管理员在排查失控进程,还是开发者在调试应用程序,知道如何将PID映射到对应的进程名称都是一项...

Linux服务器硬件信息查询与日常运维命令总结

1.服务器硬件信息查询1.1CPU信息查询命令功能描述示例lscpu显示CPU架构、核心数、线程数等lscpucat/proc/cpuinfo详细CPU信息(型号、缓存、频率)cat/proc/c...

Ubuntu 操作系统常用命令详解(ubuntu常用的50个命令)

UbuntuLinux是一款流行的开源操作系统,广泛应用于服务器、开发、学习等场景。命令行是Ubuntu的灵魂,也是高效、稳定管理系统的利器。本文按照各大常用领域,详细总结Ubuntu必学...

从 0 到 1:打造基于 Linux 的私有 API 网关平台

在当今微服务架构盛行的时代,API网关作为服务入口和安全屏障,其重要性日益凸显。你是否想过,不依赖商业方案,完全基于开源组件,在Linux上构建一个属于自己的私有API网关平台?今天就带你...

Nginx搭建简单直播服务器(nginx 直播服务器搭建)

前言使用Nginx+Nginx-rtmp-module在Ubuntu中搭建简单的rtmp推流直播服务器。服务器环境Ubuntu16.04相关概念RTMP:RTMP协议是RealTi...

Linux连不上网?远程卡?这篇网络管理指南你不能错过!

大家好!今天咱们聊个所有Linux用户都躲不开的“老大难”——网络管理。我猜你肯定遇到过这些崩溃时刻:新装的Linux系统连不上Wi-Fi,急得直拍桌子;远程服务器SSH连不上,提示“Connecti...

7天从0到上线!手把手教你用Python Flask打造爆款Web服务

一、为什么全网开发者都在疯学Flask?在当今Web开发的战场,Flask就像一把“瑞士军刀”——轻量级架构让新手3天速成,灵活扩展能力又能支撑百万级用户项目!对比Django的“重型装甲”,Flas...

nginx配置文件详解(nginx反向代理配置详解)

Nginx是一个强大的免费开源的HTTP服务器和反向代理服务器。在Web开发项目中,nginx常用作为静态文件服务器处理静态文件,并负责将动态请求转发至应用服务器(如Django,Flask,et...

30 分钟搞定 Docker 安装与 Nginx 部署,轻松搭建高效 Web 服务

在云计算时代,利用容器技术快速部署应用已成为开发者必备技能。本文将手把手教你在阿里云轻量应用服务器上,通过Docker高效部署Nginx并发布静态网站,全程可视化操作,新手也能轻松上手!一、准...

Nginx 配置实战:从摸鱼到部署,手把手教你搞定生产级配置

各位摸鱼搭子们!今天咱不聊代码里的NullPointerException,改聊点「摸鱼必备生存技能」——Nginx配置!先灵魂拷问一下:写了一堆接口却不会部署?服务器被恶意请求打崩过?静态资源加载...

如何使用 Daphne + Nginx + supervisor部署 Django

前言:从Django3.0开始支持ASGI应用程序运行,使Django完全具有异步功能。Django目前已经更新到5.0,对异步支持也越来越好。但是,异步功能将仅对在ASGI下运行的应用程序可用...

Docker命令最全详解(39个最常用命令)

Docker是云原生的核心,也是大厂的必备技能,下面我就全面来详解Docker核心命令@mikechen本文作者:陈睿|mikechen文章来源:mikechen.cc一、Docker基本命令doc...

ubuntu中如何查看是否已经安装了nginx

在Ubuntu系统中,可以通过以下几种方法检查是否已安装Nginx:方法1:使用dpkg命令(适用于Debian/Ubuntu)bashdpkg-l|grepnginx输出...

OVN 概念与实践(德育概念的泛化在理论和实践中有什么弊端?)

今天我们来讲解OVN的概念和基础实践,要理解本篇博客的内容,需要前置学习:Linux网络设备-Bridge&VethPairLinux网络设备-Bridge详解OVS+Fa...

取消回复欢迎 发表评论: