6 zookeeper实现分布式锁

zookeeper实现分布式锁

仓库地址:https://gitee.com/J_look/ssm-zookeeper/blob/master/README.md

  • 锁:我们在多线程中接触过,作用就是让当前的资源不会被其他线程访问!
    我的日记本,不可以被别人看到。所以要锁在保险柜中
    当我打开锁,将日记本拿走了,别人才能使用这个保险柜
  • 在zookeeper中使用传统的锁引发的 “羊群效应” :1000个人创建节点,只有一个人能成功,999
    人需要等待!
  • 羊群是一种很散乱的组织,平时在一起也是盲目地左冲右撞,但一旦有一只头羊动起来,其他的羊
    也会不假思索地一哄而上,全然不顾旁边可能有的狼和不远处更好的草。羊群效应就是比喻人都有
    一种从众心理,从众心理很容易导致盲从,而盲从往往会陷入骗局或遭到失败。

实现分布式锁大致流程
6 zookeeper实现分布式锁

整体思路
6 zookeeper实现分布式锁

  1. 所有请求进来,在/lock下创建 临时顺序节点 ,放心,zookeeper会帮你编号排序

  2. 判断自己是不是/lock下最小的节点

  3. 是,获得锁(创建节点)

  4. 否,对前面小我一级的节点进行监听

  5. 获得锁请求,处理完业务逻辑,释放锁(删除节点),后一个节点得到通知(比你年轻的死了,你
    成为最嫩的了)

  6. 重复步骤2

安装nginx

安装nginx运行所需的库

//一键安装上面四个依赖 yum -y install gcc zlib zlib-devel pcre-devel openssl openssl-devel 

下载nginx

在那个目录下执行这个命令 就会下载到哪个目录下

//下载tar包 wget http://nginx.org/download/nginx-1.13.7.tar.gz 

解压

注意哦 解压出来的文件 我们还需要安装哦

下面所有的命令 都是在nginx-1.13.7文件夹里面进行哦

tar -zxvf  nginx-1.13.7.tar.gz 
  • 查看解压出来的文件

    ll ./nginx-1.13.7 

    6 zookeeper实现分布式锁

安装

创建一个文件夹,也就是nginx需要安装到的位置

mkdir /usr/local/nginx 

执行命令 考虑到后续安装ssl证书 添加两个模块

./configure --with-http_stub_status_module --with-http_ssl_module 

执行make install命令

make install 
  • 我们可以来到nginx安装到的目录下查看
  • 你们没有我这么多目录 conf 配置 sbin 启动nginx
  • 博主技术有限,还没有深入去学习nginx的 大致这样介绍吧
  • 6 zookeeper实现分布式锁

启动nginx服务

我这个是在/ 目录底下执行的 你们可以根据 自己所在的目录去执行

 /usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf 

访问nginx

nginx的默认端口是80
6 zookeeper实现分布式锁

配置nginx

我们所做的配置大概就是

  • 当有请求去访问我们服务器,
  • 然后负载到我们处理请求的服务器 我这里是两台
  • 为了方便 处理请求的这两台服务器 是在我Windows上

打开配置文件

# 打开配置文件 vim /usr/local/nginx/conf/nginx.conf 
  • 图中 红框的位置 是需要添加的内容
  • 配置含义: 我们的nginx监听的是服务器的80端口 当有请求访问时 会负载到 look代理里面 server是处理请求的两台服务器
  • 查看本机ip Windows ==>ipconfig linux ==> ip a(ip address)

6 zookeeper实现分布式锁

upstream look{      server 192.168.204.1:8001; //192.168.204.1是我本机的ip地址,8001是tomcat的端口号     server 192.168.204.1:8002; //8002是另外一个工程的tomcat端口号 } server {  	listen 80;  	server_name localhost;  	#charset koi8-r;  	#access_log logs/host.access.log main;  	location / { 		proxy_pass http://look;  		root html;  	index index.html index.htm;  } 

工程的搭建

搭建ssm框架 有时间推出springboot的版本

  • 创建一个maven项目(普通maven项目即可)

创建数据库:

-- 商品表 create table product( id int primary key auto_increment, -- 商品编号 product_name varchar(20) not null, -- 商品名称 stock int not null, -- 库存 version int not null -- 版本 ) insert into product (product_name,stock,version) values('锦鲤-清空购物车-大奖',5,0) 
-- 订单表 create table `order`( id varchar(100) primary key, -- 订单编号 pid int not null, -- 商品编号 userid int not null -- 用户编号 ) 
  • 项目目录结构
    6 zookeeper实现分布式锁

添加依赖

简单解释一下build

  • 我们引入的是tomcat7的插件
  • configuration 配置的是端口 和根目录
  • 注意哦 记得刷新pom文件 build里面会有爆红 不要紧张 不用管他 后面的配置他会自己消失
<properties>         <maven.compiler.source>8</maven.compiler.source>         <maven.compiler.target>8</maven.compiler.target>         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>         <spring.version>5.2.7.RELEASE</spring.version>     </properties>  <packaging>war</packaging>      <dependencies>         <!-- Spring -->         <dependency>             <groupId>org.springframework</groupId>             <artifactId>spring-context</artifactId>             <version>${spring.version}</version>         </dependency>         <dependency>             <groupId>org.springframework</groupId>             <artifactId>spring-beans</artifactId>             <version>${spring.version}</version>         </dependency>         <dependency>             <groupId>org.springframework</groupId>             <artifactId>spring-webmvc</artifactId>             <version>${spring.version}</version>         </dependency>         <dependency>             <groupId>org.springframework</groupId>             <artifactId>spring-jdbc</artifactId>             <version>${spring.version}</version>         </dependency>         <!-- Mybatis -->         <dependency>             <groupId>org.mybatis</groupId>             <artifactId>mybatis</artifactId>             <version>3.5.10</version>         </dependency>         <dependency>             <groupId>org.mybatis</groupId>             <artifactId>mybatis-spring</artifactId>             <version>2.0.7</version>         </dependency>         <!-- 连接池 -->         <dependency>             <groupId>com.alibaba</groupId>             <artifactId>druid</artifactId>             <version>1.2.11</version>         </dependency>         <!-- 数据库 -->         <dependency>             <groupId>mysql</groupId>             <artifactId>mysql-connector-java</artifactId>             <version>8.0.29</version>         </dependency>         <!-- junit -->         <dependency>             <groupId>junit</groupId>             <artifactId>junit</artifactId>             <version>4.13.2</version>             <scope>test</scope>         </dependency>         <dependency>             <groupId>org.projectlombok</groupId>             <artifactId>lombok</artifactId>             <version>1.18.24</version>         </dependency>     </dependencies>      <build>         <plugins>             <!-- maven内嵌的tomcat插件 -->             <plugin>                 <groupId>org.apache.tomcat.maven</groupId>                 <!-- 目前apache只提供了tomcat6和tomcat7两个插件 -->                 <artifactId>tomcat7-maven-plugin</artifactId>                 <configuration>                     <port>8002</port>                     <path>/</path>                 </configuration>                 <executions>                     <execution>                         <!-- 打包完成后,运行服务 -->                         <phase>package</phase>                         <goals>                             <goal>run</goal>                         </goals>                     </execution>                 </executions>             </plugin>         </plugins>     </build> 

mybatis.xml

注意哦 :仔细查看上面的项目结构 创建相应的文件夹

<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"         "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration>     <!-- 后台的日志输出  输出到控制台-->     <settings>         <setting name="logImpl" value="STDOUT_LOGGING"/>     </settings> </configuration> 

spring.xml

注意哦 :仔细查看上面的项目结构 创建相应的文件夹

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans"        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"        xmlns:mvc="http://www.springframework.org/schema/mvc"        xmlns:context="http://www.springframework.org/schema/context"        xmlns:tx="http://www.springframework.org/schema/tx"        xsi:schemaLocation="http://www.springframework.org/schema/beans         http://www.springframework.org/schema/beans/spring-beans.xsd         http://www.springframework.org/schema/context         http://www.springframework.org/schema/context/spring-context.xsd         http://www.springframework.org/schema/tx         http://www.springframework.org/schema/tx/spring-tx.xsd">      <!-- 1.扫描包下的注解 -->     <context:component-scan base-package="controller,service,mapper"/>     <!-- 2.创建数据连接池对象 -->     <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"           destroy-method="close">         <property name="url" value="jdbc:mysql://localhost:3306/2022_zkproduct?serverTimezone=GMT"/>         <property name="driverClassName" value="com.mysql.jdbc.Driver"/>         <property name="username" value="root"/>         <property name="password" value="317311"/>         <property name="maxActive" value="10"/>         <property name="minIdle" value="5"/>     </bean>      <!-- 3.创建SqlSessionFactory,并引入数据源对象 -->     <bean id="sqlSessionFactory"           class="org.mybatis.spring.SqlSessionFactoryBean">         <property name="dataSource" ref="dataSource"></property>         <property name="configLocation" value="classpath:mybatis/mybatis.xml"></property>     </bean>      <!-- 4.告诉spring容器,数据库语句代码在哪个文件中-->     <!-- mapper.xDao接口对应resources/mapper/xDao.xml-->     <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">         <property name="basePackage" value="mapper"></property>     </bean>      <!-- 5.将数据源关联到事务 -->     <bean id="transactionManager"           class="org.springframework.jdbc.datasource.DataSourceTransactionManager">         <property name="dataSource" ref="dataSource"></property>     </bean>      <!-- 6.开启事务 -->     <tx:annotation-driven/> </beans>  

web.xml

注意哦 :仔细查看上面的项目结构 创建相应的文件夹

这里也会出现爆红,后面会自己消失

<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"          xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"          version="3.1">     <servlet>         <servlet-name>springMVC</servlet-name>         <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>         <init-param>             <param-name>contextConfigLocation</param-name>             <param-value>classpath:spring/spring.xml</param-value>         </init-param>         <load-on-startup>1</load-on-startup>         <async-supported>true</async-supported>     </servlet>      <servlet-mapping>         <servlet-name>springMVC</servlet-name>         <url-pattern>/</url-pattern>     </servlet-mapping> </web-app> 

实体类

  • @Data 是lombok的注解

Product

/**  * @author : look-word  * 2022-07-17 10:12  **/ @Data public class Product implements Serializable {     private Integer id;     private String product_name;     private Integer stock;     private Integer version; } 

Order

/**  * @author : look-word  * 2022-07-17 10:12  **/ @Data public class Order implements Serializable {     private String id;     private Integer pid;     private Integer userid; } 

持久层

ProductMapper

@Mapper @Component public interface ProductMapper {     // 查询商品(目的查库存)     @Select("select * from product where id = #{id}")     Product getProduct(@Param("id") int id);      // 减库存     @Update("update product set stock = stock-1 where id = #{id}")     int reduceStock(@Param("id") int id); } 

OrderMapper

@Mapper @Component public interface OrderMapper {     // 生成订单     @Insert("insert into `order` (id,pid,userid) values (#{id},#{pid},#{userid})")     int insert(Order order); } 

service

ProductService

/**  * @author : look-word  * 2022-07-17 10:28  **/ public interface ProductService {     // 扣除库存     void reduceStock(Integer id) throws Exception; } 

ProductServiceImpl

/**  * @author : look-word  * 2022-07-17 10:29  **/ @Transactional @Service public class ProductServiceImpl implements ProductService {     @Resource     private ProductMapper productMapper;      @Resource     private OrderMapper orderMapper; 	     @Override     public void reduceStock(Integer id) throws Exception {         // 查询商品库存         Product product = productMapper.getProduct(id);         if (product.getStock() <= 0) {             throw new RuntimeException("库存不足");         }         // 减库存         int i = productMapper.reduceStock(id);                 if (i == 1) {             Order order = new Order();             order.setId(UUID.randomUUID().toString());             order.setUserid(1);             order.setPid(id);             Thread.sleep(500);             orderMapper.insert(order);         } else {             throw new RuntimeException("扣除库存失败");         }     } } 

controller

/**  * @author : look-word  * 2022-07-17 10:12  **/ @RestController public class ProductAction {      @Resource     private ProductService productService;          @GetMapping("product/reduce/{id}")     private Object reduce(@PathVariable Integer id) throws Exception {         productService.reduceStock(id);         return "ok";     } } 

启动测试

  • 点击右侧的maven
    6 zookeeper实现分布式锁

还记得我们在pom.xml配置的tomcat的插件吗,我们配置的意思是打包(package)之后会自动运行

  • 在执行打包命令之前,先执行clean命令
    6 zookeeper实现分布式锁
  • 执行package命令
    6 zookeeper实现分布式锁

测试

  • 浏览器访问
http://localhost:8001/product/reduce/1 

6 zookeeper实现分布式锁

访问流程
6 zookeeper实现分布式锁

**注意**

在使用jmeter测试的时候 需要启动两个服务

  • 在启动第一个之后 去修改pom里面的build里面tomcat插件的端口 8002
  • 记得要刷新pom文件,然后再打包启动即可

启动jmeter测试

简单阐述一下:我们会模拟高并发场景下对这个商品的库存进行扣减

  • 这也就会导致一个问题,会出现商品超卖(出现负的库存)
  • 出现的原因: 在同一时间,访问的请求很多。

下载地址

解压双击jmeter.bat启动
6 zookeeper实现分布式锁

创建线程组

6 zookeeper实现分布式锁

  • 这里的线程数量根据自己电脑去设置
    6 zookeeper实现分布式锁

创建请求

6 zookeeper实现分布式锁

我们填写红框的内容即可就是访问的地址

  • 我们还需要查看请求的结果 创建结果树 右击会出现

6 zookeeper实现分布式锁

配置好这些之后,点击菜单栏绿色启动标志

  • 会出现弹窗 第一个点yes 第二个点cancel(取消)

去数据库查看

  • 没有启动前数据库的库存
    6 zookeeper实现分布式锁

  • 可以看到 出现了 超卖

6 zookeeper实现分布式锁

解决超卖

需要用到 zookeeper集群,搭建的文章

zookeeper分布式锁不需要我们手写去实现,有封装好的依赖,引入即可

<dependency>     <groupId>org.apache.curator</groupId>     <artifactId>curator-recipes</artifactId>     <version>4.2.0</version> <!-- 网友投票最牛逼版本 --> </dependency> 

在控制层中加入分布式锁的逻辑代码

  • 添加了集群的ip
/**  * @author : look-word  * 2022-07-17 10:12  **/ @RestController public class ProductAction {      @Resource     private ProductService productService;     // 集群ip     private String connectString = "192.168.77.132,192.168.77.131,192.168.77.130";       @GetMapping("product/reduce/{id}")     private Object reduce(@PathVariable Integer id) throws Exception {         // 重试策略 (1000毫秒试1次,最多试3次)         RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);         //1.创建curator工具对象         CuratorFramework client = CuratorFrameworkFactory.newClient(connectString, retryPolicy);         client.start();         //2.根据工具对象创建“内部互斥锁”         InterProcessMutex lock = new InterProcessMutex(client, "/product_" + id);         try {             //3.加锁             lock.acquire();             productService.reduceStock(id);         } catch (Exception e) {             if (e instanceof RuntimeException) {                 throw e;             }         } finally {             //4.释放锁             lock.release();         }         return "ok";     } } 

启动jmeter去测试,会发现,请求就像排队一样,一个一个出现,数据库也没有超卖现象

  • 可以看到 只有前面的5课请求成功了,我们的库存只有5个
  • 说明我们的分布式锁,已经实现了
  • 6 zookeeper实现分布式锁

springboot版本后续会退出

发表评论

相关文章