SpringBoot+Redis实现实现Java高并发之秒杀系统
基于SpringBoot
- 实现Java高并发之秒杀系统
1、技术栈
后端: SpringBoot + Redis
前端: Bootstrap + Jquery
2、测试环境
IDEA + Maven+ Tomcat8.5 + JDK8
3、下载redis
Redis下载地址
4、基本流程图
Spring的声明式事务通过:传播行为、隔离级别、只读提示、事务超时、回滚规则来进行定义。
配置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;
整个项目结构
隔离级别
声明式事务的第二个维度就是隔离级别。隔离级别定义了一个事务可能受其他并发事务影响的程度。多个事务并发运行,经常会操作相同的数据来完成各自的任务,但是可以回导致以下问题:
更新丢失:当多个事务选择同一行操作,并且都是基于最初的选定的值,由于每个事务都不知道其他事务的存在,就会发生更新覆盖的问题。
脏读:事务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缓存优化
配置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配置而已。
测试代码的页面效果
进入首页
点击下面的立即抢购
- 点赞 2
- 收藏
- 分享
- 文章举报
- Springboot+mybatis+redis实现java秒杀系统
- SpringBoot +Redis +RabbitMQ 实现高并发限时秒杀
- 入门基础必备,使用Idea实现SpringBoot+Mysql+Redis+RabbitMQ+Jmeter模拟实现高并发秒杀
- idea基础实现SpringBoot+Mysql+Redis+RabbitMQ+Jmeter模拟高并发秒杀
- 使用idea实现SpringBoot+Mysql+Redis+RabbitMQ+Jmeter的高并发秒杀
- #使用idea创建springboot +Redis +RabbitMQ 实现高并发限时秒杀
- 基于redis的高并发秒杀的JAVA-DEMO实现!
- SpringBoot整合mybatis、shiro、redis实现基于数据库的细粒度动态权限管理系统实例(转)
- Java-redis分布式锁 抢购秒杀系统 实现
- 使用springboot redis RabbitMQ实现商品秒杀
- java通过redis实现秒杀系统设计
- SpringBoot+Vue前后端分离实现高并发秒杀——前端知识总结
- 这是一个秒杀系统,即大量用户抢有限的商品,先到先得 用户并发访问流量非常大,需要分布式的机器集群处理请求 系统实现使用Java
- 【SpringBoot商城秒杀系统项目实战06】安装与集成redis
- SpringBoot整合mybatis、shiro、redis实现基于数据库的细粒度动态权限管理系统实例
- 基于redis的高并发秒杀的JAVA-DEMO实现!
- springboot 整合 redis 主从同步 sentinel哨兵 实现商品抢购秒杀
- SpringBoot整合mybatis、shiro、redis实现基于数据库的细粒度动态权限管理系统实例
- Redis系列-JAVA与redis整合-Spring Data Redis实现一个订阅/发布系统
- SpringBoot整合mybatis、shiro、redis实现基于数据库的细粒度动态权限管理系统实例