ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

阶段七模块一 分布式锁实现商品秒杀

2021-04-08 10:03:27  阅读:111  来源: 互联网

标签:product int spring public -- 秒杀 模块 id 分布式


内容输出来源:拉钩教育Java就业训练营

锁:我们在多线程中接触过,作用就是让当前的资源不会被其他线程访问!

在zookeeper中使用传统的锁引发的 “羊群效应” :1000个人创建节点,只有一个人能成功,999人需要等待!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tu2wtPme-1617846797778)(分布式锁实现商品秒杀.assets/zookeeper详解.jpg)]

避免“羊群效应”,zookeeper采用分布式锁

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zzA1EjZU-1617846797780)(分布式锁实现商品秒杀.assets/zookeeper详解-1617780648149.jpg)]

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

实现步骤

1 初始化数据库

-- 商品表
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 -- 用户编号
)

2 搭建工程

工程构造

实体类Order和Product自行创建

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fqgTpRT3-1617846797781)(分布式锁实现商品秒杀.assets/微信截图_20210407161310.png)]

<packaging>war</packaging>
    <properties>
        <spring.version>5.2.7.RELEASE</spring.version>
    </properties>

    <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.5</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.5</version>
        </dependency>
        <!-- 连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.10</version>
        </dependency>
        <!-- 数据库 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.20</version>
        </dependency>
        <!-- junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!-- maven内嵌的tomcat插件 -->
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <!-- 目前apache只提供了tomcat6和tomcat7两个插件 -->
                <artifactId>tomcat7-maven-plugin</artifactId>
                <configuration>
                    <port>8001</port>
                    <path>/</path>
                </configuration>
                <executions>
                    <execution>
                        <!-- 打包完成后,运行服务 -->
                        <phase>package</phase>
                        <goals>
                            <goal>run</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

mybatis配置文件

<?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 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://192.168.204.131:3306/zkproduct?
serverTimezone=GMT" />
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <property name="username" value="root" />
        <property name="password" value="123123" />
        <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-config.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>

webxml

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

OrderMapper

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

ProducttionMapper

@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);
}

ProductService

public interface ProductService {
    //减库存
    void reduceStock(int id) throws Exception;
}

ProductServiceImpl

@Service
public class ProductServiceImpl implements ProductService {

    @Autowired
    ProductMapper productMapper;
    @Autowired
    OrderMapper orderMapper;
    public void reduceStock(int id) throws Exception {
        // 获取库存(根据商品id查询商品)
        Product product = productMapper.getProduct(id);
        // 模拟网络延迟
        Thread.sleep(1000);
        if(product.getStock() <= 0)
            throw new RuntimeException("已抢光!");
        // 2.减库存
        int i = productMapper.reduceStock(id);
        if(i == 1){
            Order order = new Order();
            order.setId(UUID.randomUUID().toString());
            order.setPid(id);
            order.setUserId(101);
            orderMapper.insert(order);
        }else
            throw new RuntimeException("减库存失败,请重试!");
    }
}

controller

@Controller
public class ProductAction {

    @Autowired
    private ProductService productService;

    @GetMapping("/prodeuct/reduce")
    @ResponseBody
    public Object reduce(int id) throws Exception {
        productService.reduceStock(id);
        return "ok";
    }
}

3 启动测试

  1. 启动两次工程,端口号分别8001和8002

  2. 使用nginx做负载均衡

    upstream sga{
        server 192.168.163.128:8001;
        server 192.168.163.128:8002;
    }
    server {
        listen 80;
        server_name localhost;
        #charset koi8-r;
        #access_log logs/host.access.log main;
        location / {
            proxy_pass http://sga;
            root html;
            index index.html index.htm;
    }
    
  3. 使用 JMeter 模拟1秒内发出10个http请求

4. apahce提供的zookeeper客户端

基于zookeeper原生态的客户端类实现分布式是非常麻烦的,我们使用apahce提供了一个zookeeper客户端来实现

<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>4.2.0</version> 
</dependency>

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

@Controller
public class ProductAction {
    @Autowired
    private ProductService productService;
    private static String connectString =
    "192.168.204.141:2181,192.168.204.142:2181,192.168.204.143:2181";
    @GetMapping("/product/reduce")
    @ResponseBody
    public Object reduce( int 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";
    }
}

k = 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”;
}
}


标签:product,int,spring,public,--,秒杀,模块,id,分布式
来源: https://blog.csdn.net/wangxu123445678/article/details/115506175

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有