<返回更多

阿里3个小时手把手教你用zookeeper实现分布式锁

2022-07-20  博客园  Java后端架构猛猛
加入收藏

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

实现分布式锁的大致流程

阿里3个小时手把手教你用zookeeper实现分布式锁

 

整体思路

阿里3个小时手把手教你用zookeeper实现分布式锁

 

  1. 所有请求进来,在/lock下创建 临时顺序节点 ,放心,zookeeper会帮你编号排序
  2. 判断自己是不是/lock下最小的节点
  3. 是,获得锁(创建节点)
  4. 否则,对前面小我一级的节点进行监听
  5. 获得锁请求,处理完业务逻辑,释放锁(删除节点),后一个节点得到通知(比你年轻的死了,你
    成为最嫩的了)
  6. 重复步骤2

安装Nginx

安装nginx运行所需的库

bash

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

下载nginx

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

bash

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

解压

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

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

highlighter-

tar -zxvf  nginx-1.13.7.tar.gz

安装

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

bash

mkdir /usr/local/nginx

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

bash

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

执行make install命令

bash

make install

启动nginx服务

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

bash

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

访问nginx

nginx的默认端口是80

配置nginx

我们所做的配置大概就是

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

打开配置文件

bash

# 打开配置文件
vim /usr/local/nginx/conf/nginx.conf
阿里3个小时手把手教你用zookeeper实现分布式锁

 

xml

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的版本

创建数据库:

sql

-- 商品表
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)

sql

-- 订单表
create table `order`(
id varchar(100) primary key, -- 订单编号
pid int not null, -- 商品编号
userid int not null -- 用户编号
)

添加依赖

简单解释一下build

我们引入的是tomcat7的插件configuration 配置的是端口 和根目录注意哦 记得刷新pom文件 build里面会有爆红 不要紧张 不用管他 后面的配置他会自己消失

xml

<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

<?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

<?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

<?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>

实体类

Product

java

/**
 * @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

highlighter- php

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

持久层

ProductMapper

java

@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

java

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

service

ProductService

java

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

ProductServiceImpl

java

/**
 * @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

java

/**
 * @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";
    }
}

启动测试

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

在执行打包命令之前,先执行clean命令
执行package命令

测试

highlighter- Go

http://localhost:8001/product/reduce/1
阿里3个小时手把手教你用zookeeper实现分布式锁

 

访问流程

阿里3个小时手把手教你用zookeeper实现分布式锁

 

**注意**

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

启动jmeter测试

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

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

下载地址

解压双击jmeter.bat启动

阿里3个小时手把手教你用zookeeper实现分布式锁

 

创建线程组

阿里3个小时手把手教你用zookeeper实现分布式锁

 

这里的线程数量根据自己电脑去设置

创建请求

阿里3个小时手把手教你用zookeeper实现分布式锁

 

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

阿里3个小时手把手教你用zookeeper实现分布式锁

 

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

去数据库查看

阿里3个小时手把手教你用zookeeper实现分布式锁

 

解决超卖

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

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

xml

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

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

java

/**
 * @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去测试,会发现,请求就像排队一样,一个一个出现,数据库也没有超卖现象

springboot版本后续会退出

原文链接:
https://www.cnblogs.com/look-word/p/16488623.html

声明:本站部分内容来自互联网,如有版权侵犯或其他问题请与我们联系,我们将立即删除或处理。
▍相关推荐
更多资讯 >>>