SpringBoot+Redis实现实现Java高并发之秒杀系统

基于SpringBoot

  1. 实现Java高并发之秒杀系统

1、技术栈

后端: SpringBoot + Redis

前端: Bootstrap + Jquery

2、测试环境

IDEA + Maven+ Tomcat8.5 + JDK8

3、下载redis
Redis下载地址

4、基本流程图
SpringBoot+Redis实现实现Java高并发之秒杀系统_第1张图片
SpringBoot+Redis实现实现Java高并发之秒杀系统_第2张图片
SpringBoot+Redis实现实现Java高并发之秒杀系统_第3张图片
Spring的声明式事务通过:传播行为、隔离级别、只读提示、事务超时、回滚规则来进行定义。

SpringBoot+Redis实现实现Java高并发之秒杀系统_第4张图片
SpringBoot+Redis实现实现Java高并发之秒杀系统_第5张图片
配置pom.xml文件

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- alibaba的druid数据库连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.9</version>
        </dependency>

        <!-- redis客户端 小白用的是服务器上的-->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>

    </dependencies>

配置application.yml文件

server:
  port: 8080

spring:
  datasource:
    name: springboot
    type: com.alibaba.druid.pool.DruidDataSource
    #druid相关配置
    druid:
      #监控统计拦截的filters
      filter: stat
      #mysql驱动
      driver-class-name: com.mysql.jdbc.Driver
      #基本属性
      url: jdbc:mysql://localhost:3306/seckill?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&?zeroDateTimeBehavior=convertToNull
      username: root
      password: sasa
      #配置初始化大小/最小/最大
      initial-size: 1
      min-idle: 1
      max-active: 20
      #获取连接等待超时时间
      max-wait: 60000
      #间隔多久进行一次检测,检测需要关闭的空闲连接
      time-between-eviction-runs-millis: 60000

  thymeleaf:
    prefix: classpath:/templates/
    check-template-location: true
    suffix: .html
    encoding: UTF-8
    mode: LEGACYHTML5
    cache: false

  #文件上传相关设置
  servlet:
    multipart:
      max-file-size: 10Mb
      max-request-size: 100Mb

  #devtools插件
  devtools:
    livereload:
      enabled: true #是否支持livereload
      port: 35729
    restart:
      enabled: true #是否支持热部署

  #redis缓存
  redis:
    #redis数据库索引,默认是0
    database: 0
    #redis服务器地址
    host: 39.105.174.56
    # Redis服务器连接密码(默认为空)
    password:
    #redis服务器连接端口,默认是6379
    port: 6379
    # 连接超时时间(毫秒)
    timeout: 1000
    jedis:
      pool:
        # 连接池最大连接数(使用负值表示没有限制)
        max-active: 8
        # 连接池最大阻塞等待时间(使用负值表示没有限制
        max-wait: -1
        # 连接池中的最大空闲连接
        max-idle: 8
        # 连接池中的最小空闲连接
        min-idle: 0

#mybatis配置
mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: cn.tycoding.entity
  configuration:
    # 使用jdbc的getGeneratedKeys 可以获取数据库自增主键值
    use-generated-keys: true
    # 使用列别名替换列名,默认true。
    use-column-label: true
    # 开启驼峰命名转换
    map-underscore-to-camel-case: true

# 打印sql
logging:
  level:
    cn.tycoding.mapper: DEBUG

创建数据库seckill.sql文件

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for seckill
-- ----------------------------
DROP TABLE IF EXISTS `seckill`;
CREATE TABLE `seckill`  (
  `seckill_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '商品ID',
  `title` varchar(1000) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '商品标题',
  `image` varchar(1000) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '商品图片',
  `price` decimal(10, 2) NULL DEFAULT NULL COMMENT '商品原价格',
  `cost_price` decimal(10, 2) NULL DEFAULT NULL COMMENT '商品秒杀价格',
  `stock_count` bigint(20) NULL DEFAULT NULL COMMENT '剩余库存数量',
  `start_time` timestamp(0) NOT NULL DEFAULT '1970-02-01 00:00:01' COMMENT '秒杀开始时间',
  `end_time` timestamp(0) NOT NULL DEFAULT '1970-02-01 00:00:01' COMMENT '秒杀结束时间',
  `create_time` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`seckill_id`) USING BTREE,
  INDEX `idx_start_time`(`start_time`) USING BTREE,
  INDEX `idx_end_time`(`end_time`) USING BTREE,
  INDEX `idx_create_time`(`end_time`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '秒杀商品表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of seckill
-- ----------------------------
INSERT INTO `seckill` VALUES (1, 'Apple/苹果 iPhone 6s Plus 国行原装苹果6sp 5.5寸全网通4G手机', 'https://g-search3.alicdn.com/img/bao/uploaded/i4/i3/2249262840/O1CN011WqlHkrSuPEiHxd_!!2249262840.jpg_230x230.jpg', 2600.00, 1100.00, 9, '2019-12-22 16:30:00', '2019-12-22 23:30:00', '2019-12-22 21:12:46');
INSERT INTO `seckill` VALUES (2, 'ins新款连帽毛领棉袄宽松棉衣女冬外套学生棉服', 'https://gw.alicdn.com/bao/uploaded/i3/2007932029/TB1vdlyaVzqK1RjSZFzXXXjrpXa_!!0-item_pic.jpg_180x180xz.jpg', 200.00, 150.00, 10, '2019-12-22 16:30:00', '2019-12-22 23:30:00', '2019-12-22 21:12:46');
INSERT INTO `seckill` VALUES (3, '可爱超萌兔子毛绒玩具垂耳兔公仔布娃娃睡觉抱女孩玩偶大号女生 ', 'https://g-search3.alicdn.com/img/bao/uploaded/i4/i2/3828650009/TB22CvKkeOSBuNjy0FdXXbDnVXa_!!3828650009.jpg_230x230.jpg', 160.00, 130.00, 20, '2019-12-22 16:30:00', '2019-12-22 23:30:00', '2019-12-22 21:12:46');

-- ----------------------------
-- Table structure for seckill_order
-- ----------------------------
DROP TABLE IF EXISTS `seckill_order`;
CREATE TABLE `seckill_order`  (
  `seckill_id` bigint(20) NOT NULL COMMENT '秒杀商品ID',
  `money` decimal(10, 2) NULL DEFAULT NULL COMMENT '支付金额',
  `user_phone` bigint(20) NOT NULL COMMENT '用户手机号',
  `create_time` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '创建时间',
  `state` tinyint(4) NOT NULL DEFAULT -1 COMMENT '状态:-1无效 0成功 1已付款',
  PRIMARY KEY (`seckill_id`, `user_phone`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '秒杀订单表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of seckill_order
-- ----------------------------
INSERT INTO `seckill_order` VALUES (1, 1100.00, 15173117830, '2019-12-22 22:00:40', -1);

SET FOREIGN_KEY_CHECKS = 1;



整个项目结构
SpringBoot+Redis实现实现Java高并发之秒杀系统_第6张图片
隔离级别
声明式事务的第二个维度就是隔离级别。隔离级别定义了一个事务可能受其他并发事务影响的程度。多个事务并发运行,经常会操作相同的数据来完成各自的任务,但是可以回导致以下问题:

更新丢失:当多个事务选择同一行操作,并且都是基于最初的选定的值,由于每个事务都不知道其他事务的存在,就会发生更新覆盖的问题。
脏读:事务A读取了事务B已经修改但为提交的数据。若事务B回滚数据,事务A的数据存在不一致的问题。
不可重复读:书屋A第一次读取最初数据,第二次读取事务B已经提交的修改或删除的数据。导致两次数据读取不一致。不符合事务的隔离性。
幻读:事务A根据相同条件第二次查询到的事务B提交的新增数据,两次数据结果不一致,不符合事务的隔离性。
理想情况下,事务之间是完全隔离的,从而可以防止这些问题的发生。但是完全的隔离会导致性能问题,因为它通常会涉及锁定数据库中的记录。侵占性的锁定会阻碍并发性,要求事务互相等待以完成各自的工作。

因此为了实现在事务隔离上有一定的灵活性。因此,就会有多重隔离级别:

隔离级别 含义
ISOLATION_DEFAULT 使用后端数据库默认的隔离级别
SIOLATION_READ_UNCOMMITTED 允许读取尚未提交的数据变更。可能会导致脏读、幻读或不可重复读
ISOLATION_READ_COMMITTED 允许读取并发事务提交的数据。可以阻止脏读,但是幻读或不可重复读仍可能发生
ISOLATION_REPEATABLE_READ 对同一字段的多次读取结果是一致的,除非数据是被本事务自己所修改,可以阻止脏读和不可重复读,但幻读仍可能发生
ISOLATION_SERIALIZABLE 完全服从ACID的事务隔离级别,确保阻止脏读、不可重复读、幻读。这是最慢的事务隔离级别,因为它通常是通过完全锁定事务相关的数据库来实现的

回滚规则

pring的事务管理器默认是针对unchecked exception回滚,也就是默认对Error异常和RuntimeException异常以及其子类进行事务回滚。

也就是说事务只有在遇到运行期异常才会回滚,而在遇到检查型异常时不会回滚。

这也就是我们之前设计Service业务层逻辑的时候一再强调捕获try catch异常,且将编译期异常转换为运行期异常。

Redis缓存优化
SpringBoot+Redis实现实现Java高并发之秒杀系统_第7张图片
SpringBoot+Redis实现实现Java高并发之秒杀系统_第8张图片

配置JedisConfig序列化

package cn.tycoding.redis;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

@Configuration
public class JedisConfig {

    private Logger logger = LoggerFactory.getLogger(JedisConfig.class);

    @Value("${spring.redis.host}")
    private String host;

    @Value("${spring.redis.port}")
    private int port;

    @Value("${spring.redis.timeout}")
    private int timeout;

    @Value("${spring.redis.jedis.pool.max-active}")
    private int maxActive;

    @Value("${spring.redis.jedis.pool.max-idle}")
    private int maxIdle;

    @Value("${spring.redis.jedis.pool.min-idle}")
    private int minIdle;

    @Value("${spring.redis.jedis.pool.max-wait}")
    private long maxWaitMillis;

    @Bean
    public JedisPool redisPoolFactory(){
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxIdle(maxIdle);
        jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);
        jedisPoolConfig.setMaxTotal(maxActive);
        jedisPoolConfig.setMinIdle(minIdle);
        JedisPool jedisPool = new JedisPool(jedisPoolConfig, host, port, timeout, null);

        logger.info("JedisPool注入成功");
        logger.info("redis地址:" + host + ":" + port);
        return jedisPool;
    }
}

这里是为了将我们在application.yml中配置的参数注入到JedisPool中,使用Spring的@Value注解能读取到Spring配置文件中已经配置的参数的值

package cn.tycoding.redis;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;

@Configuration
public class RedisTemplateConfig {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        redisTemplate.setKeySerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashKeySerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.afterPropertiesSet();
        logger.info("RedisTemplate序列化配置,转化方式:" + jackson2JsonRedisSerializer.getClass().getName());
        return redisTemplate;
    }
}

注意

实现序列化目前而言不是必须的,因为我们使用了Spring-data-redis提供的高度封装的RedisTemplate模板类。

SpringBoot2.x实现Redis的序列化仍是由很多方案,但是我这里使用了Spring-data-redis提供的一种jackson2JsonRedisSerializer的序列化方式。

如果不实现Redis的序列化,可以往Redis中存入数据,但是存入的key都是乱码的,想要避免这一点就必须实现序列化。

这个步骤和我们之前整合SSM+Redis+Shiro+Solr框架中已经讲到了用XML实现序列化配置,这里仅是换成了Java配置而已。

测试代码的页面效果

进入首页
SpringBoot+Redis实现实现Java高并发之秒杀系统_第9张图片

点击下面的立即抢购
SpringBoot+Redis实现实现Java高并发之秒杀系统_第10张图片

SpringBoot+Redis实现实现Java高并发之秒杀系统_第11张图片
SpringBoot+Redis实现实现Java高并发之秒杀系统_第12张图片
码云源码

你可能感兴趣的:(秒杀)