• Zookeeper系列——6Zookeeper的分布式锁及Leader选举原理分析


    学习目标

    1. Zookeeper分布式锁原理分析

    2. Zookeeper用作Leader选举的原理分析

    第1章 分布式锁简介

    1.1 理解分布式锁

    我们先来看一个问题,如下图所示,两个用户同时去抢购秒杀商品,当秒杀服务同时收到秒杀请求时,都去进行库存扣减,此时在没有做任何处理的情况下,就会导致库存数量变成负数从而导致超卖现象。

    这种情况下我们一般会选择加锁的方式来避免并发的问题。但是在分布式场景中,采用传统的锁并不能解决跨进程并发的问题,所以需要引入一个分布式锁,来解决多个节点之间的访问控制。

    1.2 如何解决分布式锁

    我们可以基于Zookeeper的两种特性来实现分布式锁:

    1、使用唯一节点特性实现分布式锁

    就是基于唯一节点特性,如下图所示。多个应用程序去抢占锁资源时,只需要在指定节点上创建一个 /Lock 节点,由于Zookeeper中节点的唯一性特性,使得只会有一个用户成功创建 /Lock 节点,剩下没有创建成功的用户表示竞争锁失败。

     这种方法能达到目的,但是会有一个问题,如下图所示,假设有非常多的节点需要等待获得锁,那么等待的方式自然是使用Watcher机制来监听/lock节点的删除事件,一旦发现该节点被删除说明之前获得锁的节点已经释放了锁,此时剩下的B、C、D。节点同时会收到删除事件从而去竞争锁,这个过程会产生惊群效应。

     

    “惊群效应”,简单来说就是如果存在许多的客户端在等待获取锁,当成功获取到锁的进程释放该节点后,所有处于等待状态的客户端都会被唤醒,这个时候zookeeper在短时间内发送大量子节点变更事件给所有待获取锁的客户端,然后实际情况是只会有一个客户端获得锁。如果在集群规模比较大的情况下,会对zookeeper服务器的性能产生比较大的影响。

    2、使用有序节点实现分布式锁

    因此为了解决这个问题,我们可以采用Zookeeper的有序节点特性来实现分布式锁。

    如下图所示,每个客户端都往指定的节点下注册一个临时有序节点,越早创建的节点,节点的顺序编号就越小,那么我们可以判断子节点中最小的节点设置为获得锁。如果自己的节点不是所有子节点中最小的,意味着还没有获得锁。这个的实现和前面单节点实现的差异性在于,每个节点只需要监听比自己小的节点,当比自己小的节点删除以后,客户端会收到watcher事件,此时再次判断自己的节点是不是所有子节点中最小的,如果是则获得锁,否则就不断重复这个过程,这样就不会导致羊群效应,因为每个客户端只需要监控一个节点。

     如下图所示,表示有序节点实现分布式锁的流程。

     

    第2章 分布式锁实现

    在本文中我们使用Curator来实现分布式锁。为了实现分布式锁,我们先演示一个存在并发异常的场景。

    2.1 场景模拟

    项目见zk-demo中的scene包下面

    1、sql脚本

    1. DROP TABLE IF EXISTS `goods_stock`;
    2. CREATE TABLE `goods_stock` (
    3. `id` int unsigned NOT NULL AUTO_INCREMENT,
    4. `goods_no` int NOT NULL COMMENT '商品编号',
    5. `stock` int DEFAULT NULL COMMENT '库存',
    6. `isActive` smallint DEFAULT NULL COMMENT '是否上架(1上,0不是)',
    7. PRIMARY KEY (`id`)
    8. ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

    2、采用mybatis-plus搭建项目,项目结构如图所示

     3、POM文件

    1. "1.0" encoding="UTF-8"?>
    2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    4. <modelVersion>4.0.0modelVersion>
    5. <groupId>com.examplegroupId>
    6. <artifactId>zk-demoartifactId>
    7. <version>0.0.1-SNAPSHOTversion>
    8. <name>zk-demoname>
    9. <description>zookeeper应用description>
    10. <properties>
    11. <java.version>1.8java.version>
    12. <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
    13. <project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
    14. <spring-boot.version>2.3.2.RELEASEspring-boot.version>
    15. properties>
    16. <dependencies>
    17. <dependency>
    18. <groupId>org.springframework.bootgroupId>
    19. <artifactId>spring-boot-starter-webartifactId>
    20. dependency>
    21. <dependency>
    22. <groupId>org.springframework.bootgroupId>
    23. <artifactId>spring-boot-starter-testartifactId>
    24. dependency>
    25. <dependency>
    26. <groupId>org.apache.zookeepergroupId>
    27. <artifactId>zookeeperartifactId>
    28. <version>3.4.13version>
    29. dependency>
    30. <dependency>
    31. <groupId>org.apache.curatorgroupId>
    32. <artifactId>curator-frameworkartifactId>
    33. <version>4.0.1version>
    34. <exclusions>
    35. <exclusion>
    36. <groupId>org.apache.zookeepergroupId>
    37. <artifactId>zookeeperartifactId>
    38. exclusion>
    39. exclusions>
    40. dependency>
    41. <dependency>
    42. <groupId>org.apache.curatorgroupId>
    43. <artifactId>curator-recipesartifactId>
    44. <version>4.0.1version>
    45. <exclusions>
    46. <exclusion>
    47. <groupId>org.apache.zookeepergroupId>
    48. <artifactId>zookeeperartifactId>
    49. exclusion>
    50. exclusions>
    51. dependency>
    52. <dependency>
    53. <groupId>mysqlgroupId>
    54. <artifactId>mysql-connector-javaartifactId>
    55. <scope>runtimescope>
    56. dependency>
    57. <dependency>
    58. <groupId>com.baomidougroupId>
    59. <artifactId>mybatis-plus-boot-starterartifactId>
    60. <version>3.4.2version>
    61. dependency>
    62. <dependency>
    63. <groupId>com.baomidougroupId>
    64. <artifactId>mybatis-plus-generatorartifactId>
    65. <version>3.4.0version>
    66. dependency>
    67. <dependency>
    68. <groupId>com.alibabagroupId>
    69. <artifactId>druid-spring-boot-starterartifactId>
    70. <version>1.2.9version>
    71. dependency>
    72. <dependency>
    73. <groupId>org.apache.commonsgroupId>
    74. <artifactId>commons-lang3artifactId>
    75. dependency>
    76. <dependency>
    77. <groupId>org.projectlombokgroupId>
    78. <artifactId>lombokartifactId>
    79. dependency>
    80. <dependency>
    81. <groupId>org.apache.commonsgroupId>
    82. <artifactId>commons-pool2artifactId>
    83. dependency>
    84. <dependency>
    85. <groupId>org.freemarkergroupId>
    86. <artifactId>freemarkerartifactId>
    87. dependency>
    88. dependencies>
    89. <dependencyManagement>
    90. <dependencies>
    91. <dependency>
    92. <groupId>org.springframework.bootgroupId>
    93. <artifactId>spring-boot-dependenciesartifactId>
    94. <version>${spring-boot.v
  • 相关阅读:
    【MySQL面试复习】了解过索引吗?(索引的底层原理)/B 树和B+树的区别是什么?
    Java学习【String类详解】
    微服务保护-热点参数限流
    计算机毕业设计之java+javaweb的超市库存管理系统
    基于FPGA的正弦PWM产生系统verilog实现
    Shell编程循环语句(for、while、until)
    品牌方发行NFT时,应如何考量实用性?
    【Vue】内置指令
    uniapp通过ip获取其地址:
    网络安全原来有这么多大厂,码住!
  • 原文地址:https://blog.csdn.net/Eclipse_2019/article/details/126419126