全国咨询热线:400-618-4000

redis令牌机制实现秒杀

更新时间:2019年04月18日23时11分 来源:传智播客

  一、前言1. 秒杀介绍

  秒杀是电商系统非常常见的功能模块,是商家进行相关促销推广的常用方式。主要特点是商品库存有限,抢购时间有限。那么在系统设计之初就应该考虑在数量和时间有限的情况下导致的一个高并发以及高并发所带来的库存超卖的问题。

  秒杀需要解决的问题:

  1) 库存超卖

  解决方案:

  1) 悲观锁:synchronize 、 Lock

  2) 乐观锁:数据库乐观锁版本号控制

  2) 高并发情况下系统压力以及用户体验

  解决方案: redis

  本教程采用:redis中list类型达到令牌机制完成秒杀。用户抢redis中的令牌,抢到令牌的用户才能进行支付,支付成功之后可以生成订单,如果一定时间之内没有支付那么就由定时任务来归还令牌

  2. 开发介绍

  1) 开发工具: IntelliJ IDEA2017.3.5

  2) JDK版本:1.7+

  3) 数据库: mysql5.7 、 Redis

  4) 技术:Spring、Spring Data Redis、mybatis

  二、环境搭建1. 数据库表创建

/*商品表 */
CREATE TABLE `goods` (
  `goods_id` int(11) NOT NULL AUTO_INCREMENT,
  `num` int(11) DEFAULT NULL,
  `goods_name` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`goods_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
insert  into `goods`(`goods_id`,`num`,`goods_name`) values (1,100,'iphone X');
 /*订单表 */
CREATE TABLE `orders` (
  `order_id` int(11) NOT NULL AUTO_INCREMENT,
  `good_id` int(11) DEFAULT NULL,
  `user` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`order_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1163 DEFAULT CHARSET=utf8;

 2. redis安装 ( 略 )

 3. 创建mavne项目,打包方式jar,pom.xml如下

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <junit.version>4.12</junit.version>
    <spring.version>4.2.4.RELEASE</spring.version>
    <pagehelper.version>4.0.0</pagehelper.version>
    <mybatis.version>3.2.8</mybatis.version>
    <mybatis.spring.version>1.2.2</mybatis.spring.version>
    <mybatis.paginator.version>1.2.15</mybatis.paginator.version>
    <mysql.version>5.1.32</mysql.version>
    <druid.version>1.0.9</druid.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>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jms</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-support</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.9</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.28</version>
    </dependency>
    <dependency>
        <groupId>javassist</groupId>
        <artifactId>javassist</artifactId>
        <version>3.11.0.GA</version>
    </dependency>
    <dependency>
        <groupId>commons-codec</groupId>
        <artifactId>commons-codec</artifactId>
        <version>1.10</version>
    </dependency>
    <dependency>
        <groupId>com.github.pagehelper</groupId>
        <artifactId>pagehelper</artifactId>
        <version>${pagehelper.version}</version>
    </dependency>
    <!-- Mybatis -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>${mybatis.version}</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>${mybatis.spring.version}</version>
    </dependency>
    <dependency>
        <groupId>com.github.miemiedev</groupId>
        <artifactId>mybatis-paginator</artifactId>
        <version>${mybatis.paginator.version}</version>
    </dependency>
    <!-- MySql -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>${mysql.version}</version>
    </dependency>
    <!-- 连接池 -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>${druid.version}</version>
    </dependency>
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>2.8.1</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-redis</artifactId>
        <version>1.7.2.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>dom4j</groupId>
        <artifactId>dom4j</artifactId>
        <version>1.6.1</version>
    </dependency>
    <dependency>
        <groupId>xml-apis</groupId>
        <artifactId>xml-apis</artifactId>
        <version>1.4.01</version>
    </dependency>
</dependencies>

  4. 数据访问层

  利用mybatis逆向工程生成POJO,以及mapper接口和mapper映射文件。该部分自行操作

  mybatis核心配置文件SqlMapConfig.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>
    <plugins>
        <!-- com.github.pagehelper 为 PageHelper 类所在包名 -->
        <plugin interceptor="com.github.pagehelper.PageHelper">
            <!-- 设置数据库类型 Oracle,Mysql,MariaDB,SQLite,Hsqldb,PostgreSQL 六种数据库-->
            <property name="dialect" value="mysql"/>
        </plugin>
    </plugins>
</configuration>
数据访问 db.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/miaosha?characterEncoding=utf-8
jdbc.username=root
jdbc.password=root

redis配置属性文件redis-config.propertiesproperties

# Redis settings
  # server IP
  redis.host=127.0.0.1
  # server port
  redis.port=6379
  # server pass
  redis.pass=
  # use dbIndex
  redis.database=0
  redis.maxIdle=1000
  redis.maxWait=3000

  5. spring配置文件

  applicationContext-dao.xml

<!-- 加载配置文件 -->
        <context:property-placeholder location="classpath*:properties/*.properties" />
        <!-- 数据库连接池 -->
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
              destroy-method="close">
            <property name="url" value="${jdbc.url}" />
            <property name="username" value="${jdbc.username}" />
            <property name="password" value="${jdbc.password}" />
            <property name="driverClassName" value="${jdbc.driver}" />
            <property name="maxActive" value="10" />
            <property name="minIdle" value="5" />
        </bean>
            <!-- 让spring管理sqlsessionfactory 使用mybatis和spring整合包中的 -->
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <!-- 数据库连接池 -->
            <property name="dataSource" ref="dataSource" />
            <!-- 加载mybatis的全局配置文件 -->
            <property name="configLocation" value="classpath:mybatis/SqlMapConfig.xml" />
        </bean>
        <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
            <property name="basePackage" value="com.miaosha.demo.mapper" />
        </bean>

  applicationContext-redis.xml

<context:property-placeholder location="classpath*:properties/*.properties" ignore-unresolvable="true" />  
  
   <!-- redis 相关配置 -->
   <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig"
     <property name="maxIdle" value="${redis.maxIdle}" />
       <property name="maxTotal" value="2000" />
       <property name="maxWaitMillis" value="${redis.maxWait}" />
     <property name="testOnBorrow" value="true" />
   </bean
  
   <bean id="JedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
       p:host-name="${redis.host}" p:port="${redis.port}" p:password="${redis.pass}" p:pool-config-ref="poolConfig"/>
   
   <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
       <!--<!–开启事务支持–>-->
       <!--<property name="enableTransactionSupport" value="true"/>-->
       <