Ø Springboot
Ø Redis
Ø Rocketmq
Ø Mysql
Ø MybatisPlus
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3DcJaPnq-1663581493965)(D:/noteLog/typora_files/秒杀/img/wps1.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fkLQCTOU-1663581493965)(D:/noteLog/typora_files/秒杀/img/1663524142863.png)]
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for goods
-- ----------------------------
DROP TABLE IF EXISTS `goods`;
CREATE TABLE `goods` (
`goods_id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '商品ID',
`goods_name` varchar(300) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '商品名称',
`price` decimal(15, 2) DEFAULT NULL COMMENT '现价',
`content` text CHARACTER SET utf8 COLLATE utf8_general_ci COMMENT '详细描述',
`status` int(1) DEFAULT 0 COMMENT '默认是1,表示正常状态, -1表示删除, 0下架',
`total_stocks` int(11) DEFAULT 0 COMMENT '总库存',
`create_time` datetime(0) DEFAULT NULL COMMENT '录入时间',
`update_time` datetime(0) DEFAULT NULL COMMENT '修改时间',
`spike` int(11) DEFAULT 0 COMMENT '是否参与秒杀1是0否',
PRIMARY KEY (`goods_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 95 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '商品' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of goods
-- ----------------------------
INSERT INTO `goods` VALUES (18, 'Apple iPhone XS Max 移动联通电信4G手机 ', 1.01, '\n\n\n\n
\n \n\n
\n \n\n
\n \n\n
\n \n\n
\n \n\n
\n \n\n
\n \n\n
\n \n\n
\n \n\n
\n \n\n
\n \n\n
\n', 1, 10, '2019-03-29 14:40:00', '2019-06-22 18:28:32', 1);
INSERT INTO `goods` VALUES (59, '兰蔻粉水清滢柔肤水400ml 爽肤水女保湿舒缓滋润嫩肤', 420.00, '
', 1, 10, '2019-04-21 19:15:34', '2019-04-29 14:30:44', 1);
INSERT INTO `goods` VALUES (68, '【Dole都乐】菲律宾都乐非转基因木瓜1只 单只约410g', 26.00, '
', 1, 10, '2019-04-21 21:56:38', '2019-05-22 10:30:37', 0);
INSERT INTO `goods` VALUES (69, '阿迪达斯官方 adidas 三叶草 NITE JOGGER 男子经典鞋BD7956', 1199.00, '
', 1, 10, '2019-04-21 22:10:04', '2019-05-23 20:17:03', 0);
INSERT INTO `goods` VALUES (70, '【Dole都乐】比利时Truval啤梨12只 进口水果新鲜梨 单果120g左右', 38.00, '
', 1, 10, '2019-04-22 16:43:33', '2019-06-22 09:40:24', 0);
INSERT INTO `goods` VALUES (71, '旗舰店官网 自拍神器 梵高定制', 6998.00, '
\n

', 1, 10, '2019-04-23 15:43:26', '2019-05-21 11:01:59', 0);
-- ----------------------------
-- Table structure for order
-- ----------------------------
DROP TABLE IF EXISTS `order`;
CREATE TABLE `order` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`userid` int(11) DEFAULT NULL,
`goodsid` int(11) DEFAULT NULL,
`createtime` datetime(0) DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 23 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.7.0version>
<relativePath/>
parent>
<groupId>com.powernodegroupId>
<artifactId>seckill-webartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>d-seckill-webname>
<description>d-seckill-webdescription>
<properties>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.apache.rocketmqgroupId>
<artifactId>rocketmq-spring-boot-starterartifactId>
<version>2.2.2version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
server:
port: 8081
tomcat:
threads:
max: 1000 # tomcat
min-spare: 100 #
max-connections: 8192 #
accept-count: 1000 #
spring:
application:
name: seckill-web #
redis:
host: localhost
port: 6379
database: 0
rocketmq:
name-server: 47.100.238.122:9876
producer:
group: seckill-producer-group
package com.powernode.controller;
import com.powernode.utils.BloomFilter;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
*
* 时间: 2022-06-16 10:02
*/
@RestController
public class SeckillController {
@Autowired
private BloomFilter bloomFilter;
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private RocketMQTemplate rocketMQTemplate;
/**
* 秒杀的入口
* 1.一个用户针对一个商品只能买一件
* 2.库存的判断
* 3.组装数据放mq
* 4.return
*
* @param goodsId 商品的id
* @param userId 用户的id 因为没有做登录 做测试把用户id传进来
* @return
*/
@GetMapping("doSeckill")
public String doSeckill(Integer goodsId, Integer userId) {
// 做去重操作
// 找到一个唯一的标识
String seckillId = userId + "-" + goodsId;
if (bloomFilter.isExist(seckillId)) {
return "该商品您已经参与过抢购,请参与其他商品(#^.^#)";
}
// 存放进去
bloomFilter.add(seckillId);
// 判断库存是否足够 这里做一个库存扣减的操作 redis
// 在操作redis的时候 直接减少 返回值就是减后的结果
Long count = redisTemplate.opsForValue().decrement("goods_stock:" + goodsId);
if (count < -3L) {
// 这个地方判断的临界值 如果是0的话 后面写数据库出现问题 会导致有的请求没有成功处理
// 页面上的库存数量就会大于0
// 这里可以多放10%-30%的流量进入抢购
return "该商品已经被抢完,下次早点来哦(*^▽^*)";
}
rocketMQTemplate.asyncSend("seckillTopic", seckillId, new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
System.out.println("放入mq成功");
}
@Override
public void onException(Throwable e) {
System.out.println("放入mq失败:" + e.getMessage());
}
});
//
return "正在拼命抢购中,请稍后再订单中心查看";
}
}
package com.powernode.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
/**
* 布隆过滤器
* 空间占用和速度上都比较好
* 亿万数据量去重
* 缺点:只能判断重复 取不到具体的值 存储不建议使用
* 可能存在一定误判 概率很低 千万分之一
* size的范围大小
* 散列次数大一点 折中考虑
*/
@Component // spring在创建bean对象的时候默认调他的无参构造
public class BloomFilter {
@Autowired
private StringRedisTemplate redisTemplate;
private Integer size = 2 << 30;
private Integer[] seeds = {7, 11, 13, 17, 19};
public static final Integer HASH_FUN_NUM = 5;
private HashFun[] hashFuns = new HashFun[HASH_FUN_NUM];
public BloomFilter() {
for (int i = 0; i < seeds.length; i++) {
hashFuns[i] = new HashFun(this.size, seeds[i]);
}
}
public void add(String msg) {
for (HashFun hashFun : hashFuns) {
int position = hashFun.getPosition(msg);
// 存起来 存在redis的bitmap true 就是把这个位置站住
redisTemplate.opsForValue().setBit("seckill-bloom", position, true);
}
}
public Boolean isExist(String msg) {
for (HashFun hashFun : hashFuns) {
int position = hashFun.getPosition(msg);
Boolean flag = redisTemplate.opsForValue().getBit("seckill-bloom", position);
if (!flag) {
// 只要有一个没有对应上 就不存在
return false;
}
}
return true;
}
public static BloomFilter myBloomFilter() {
return new BloomFilter();
}
public static void main(String[] args) {
BloomFilter bloomFilter = new BloomFilter();
//bloomFilter.add("qihdkasjbhckajcghasd");
Boolean filterExist = bloomFilter.isExist("qihdkasjbhckajcghase");
System.out.println(filterExist);
}
}
package com.powernode.utils;
public class HashFun {
/**
* 位图大小
*/
private Integer size;
/**
* 种子数 质数
* 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47
*/
private Integer seed;
public HashFun(Integer size, Integer seed) {
this.size = size;
this.seed = seed;
}
public int hash(String msgId) {
int h = 0;
if (msgId == null || msgId.equals("")) {
return h;
}
char[] chars = msgId.toCharArray();
for (int i = 0; i < chars.length; i++) {
h = this.seed * h + chars[i];
}
return h;
}
public int getPosition(String msgId) {
int hash = hash(msgId);
return hash & (this.size - 1);
}
public static void main(String[] args) {
HashFun hashFun = new HashFun(2 << 30, 23);
//
//int i = 17 & (16 - 1);
//System.out.println(i);
String msgId = "qwpduiqoiuerq";
int hash = hashFun.hash(msgId);
System.out.println(hash);
int position = hashFun.getPosition(msgId);
System.out.println(position);
}
}
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.7.0version>
<relativePath/>
parent>
<groupId>com.powernodegroupId>
<artifactId>seckill-serviceartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>seckill-servicename>
<description>seckill-servicedescription>
<properties>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.2.2version>
dependency>
<dependency>
<groupId>org.apache.rocketmqgroupId>
<artifactId>rocketmq-spring-boot-starterartifactId>
<version>2.2.2version>
dependency>
<dependency>
<groupId>org.redissongroupId>
<artifactId>redisson-spring-boot-starterartifactId>
<version>3.13.6version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
exclude>
excludes>
configuration>
<configuration>
<configurationFile>GeneratorMapper.xmlconfigurationFile>
<verbose>trueverbose>
<overwrite>trueoverwrite>
configuration>
plugin>
plugins>
build>
project>
server:
port: 8082
spring:
application:
name: seckill-service
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/seckill?serverTimezone=GMT%2B8
password: 123456
username: root
redis:
host: localhost
port: 6379
database: 0
task:
scheduling:
pool:
size: 4 # 如果你自己用定时任务 注意阻塞问题
mybatis:
mapper-locations: classpath*:mapper/*.xml
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true
rocketmq:
name-server: 47.100.238.122:9876
consumer:
group: seckill-consumer-group
DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<classPathEntry location="D:\javaTools\maven_repository\mysql\mysql-connector-java\5.1.46\mysql-connector-java-5.1.46.jar"/>
<context id="tables" targetRuntime="MyBatis3">
<commentGenerator>
<property name="suppressAllComments" value="true" />
commentGenerator>
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://127.0.0.1:3306/seckill"
userId="root"
password="root">
jdbcConnection>
<javaModelGenerator targetPackage="com.powernode.model" targetProject="src/main/java">
<property name="enableSubPackages" value="false" />
<property name="trimStrings" value="false" />
javaModelGenerator>
<sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources">
<property name="enableSubPackages" value="false" />
sqlMapGenerator>
<javaClientGenerator type="XMLMAPPER" targetPackage="com.powernode.mapper" targetProject="src/main/java">
<property name="enableSubPackages" value="false" />
javaClientGenerator>
<table tableName="t_user"
domainObjectName="User"
enableCountByExample="false"
enableUpdateByExample="false"
enableDeleteByExample="false"
enableSelectByExample="false"
selectByExampleQueryId="false"/>
context>
generatorConfiguration>
package com.powernode;
import org.mybatis.spring.annotation.MapperScan;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@MapperScan(basePackages = {"com.powernode.mapper"})
@EnableScheduling // 开启定时任务
public class ESeckillServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ESeckillServiceApplication.class, args);
}
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer()
.setAddress("redis://localhost:6379")
.setDatabase(0);
return Redisson.create(config);
}
}
同步mysql数据到redis
package com.powernode.config;
import com.powernode.domain.Goods;
import com.powernode.mapper.GoodsMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.TimeoutUtils;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import javax.annotation.PostConstruct;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
*
* 时间: 2022-06-16 10:41
* 将mysql的数据同步到redis
* 提供web去判断库存
* 搞个定时任务
*/
@Component
public class DataSync {
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private GoodsMapper goodsMapper;
/**
* 一般在开发中 周期性执行代码 可以使用这个定时任务
*/
@Scheduled(initialDelay = 0, fixedDelay = 1000)
public void initData22() {
long id = Thread.currentThread().getId();
try {
TimeUnit.SECONDS.sleep(20);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
@Scheduled(initialDelay = 0, fixedDelay = 2000)
public void initData3333() {
}
// 我现在测试方便 希望当前这个项目 一启动就执行某个方法(操作一些bean对象)
/**
* 在当前对象的构造方法调用以后执行
* spring在创建bean对象的时候 遵循了这个注解的规范
* spring在当前bean对象实例化以后,属性注入完以后
* 同步阻塞式执行该方法
* 这个注解作用的方法必须是无参 无返回值 public修饰的
*/
@PostConstruct
public void initData() {
// 查询数据库
List<Goods> goodsList = goodsMapper.selectSeckillGoods();
if (CollectionUtils.isEmpty(goodsList)) {
return;
}
goodsList.forEach(goods -> {
redisTemplate.opsForValue().set("goods_stock:" + goods.getGoodsId(), goods.getTotalStocks().toString());
});
}
}
package com.powernode.listener;
import com.powernode.service.GoodsService;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.spring.annotation.ConsumeMode;
import org.apache.rocketmq.spring.annotation.MessageModel;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
*
* 时间: 2022-06-16 11:31
*/
@Component
@RocketMQMessageListener(topic = "seckillTopic",
consumerGroup = "${rocketmq.consumer.group}",
consumeMode = ConsumeMode.CONCURRENTLY,
consumeThreadNumber = 20,
maxReconsumeTimes = 5,
messageModel = MessageModel.CLUSTERING
)
public class SeckillListener implements RocketMQListener<MessageExt> {
@Autowired
private GoodsService goodsService;
/**
* 处理秒杀业务方法
*
* @param message
*/
@Override
public void onMessage(MessageExt message) {
String msg = new String(message.getBody());
String[] split = msg.split("-");
int userId = Integer.parseInt(split[0]);
int goodsId = Integer.parseInt(split[1]);
goodsService.realDoSeckill(userId, goodsId);
}
}
package com.powernode.service;
import com.powernode.domain.Goods;
/**
*
* 时间: 2022-06-16 10:39
*/
public interface GoodsService {
int deleteByPrimaryKey(Integer goodsId);
int insert(Goods record);
int insertSelective(Goods record);
Goods selectByPrimaryKey(Integer goodsId);
int updateByPrimaryKeySelective(Goods record);
int updateByPrimaryKey(Goods record);
//新增realDoSeckill 做秒杀方法
void realDoSeckill(Integer userId, Integer goodsId);
}
package com.powernode.service.impl;
import com.powernode.domain.Order;
import com.powernode.mapper.OrderMapper;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.DependsOn;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import com.powernode.domain.Goods;
import com.powernode.mapper.GoodsMapper;
import com.powernode.service.GoodsService;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
import java.util.concurrent.TimeUnit;
/**
*
* 时间: 2022-06-16 10:39
*/
@Service
//@DependsOn(A.class)
public class GoodsServiceImpl implements GoodsService {
@Resource
private GoodsMapper goodsMapper;
@Autowired
private OrderMapper orderMapper;
@Autowired
private RedissonClient redissonClient;
private Long time = 1000L;
@Autowired
private StringRedisTemplate redisTemplate;
@Override
public int deleteByPrimaryKey(Integer goodsId) {
return goodsMapper.deleteByPrimaryKey(goodsId);
}
@Override
public int insert(Goods record) {
return goodsMapper.insert(record);
}
@Override
public int insertSelective(Goods record) {
return goodsMapper.insertSelective(record);
}
@Override
public Goods selectByPrimaryKey(Integer goodsId) {
return goodsMapper.selectByPrimaryKey(goodsId);
}
@Override
public int updateByPrimaryKeySelective(Goods record) {
return goodsMapper.updateByPrimaryKeySelective(record);
}
@Override
public int updateByPrimaryKey(Goods record) {
return goodsMapper.updateByPrimaryKey(record);
}
/**
* 做秒杀
* 如果参与秒杀的商品比较多
* 那么在mq里面的消息很多
* 会导致数据的压力非常大 数据库连接有限
* 我们尽可能减少排队的数量
* ---------
* 除了使用mysql的行锁以外 还可以使用其他的锁工具去处理这个问题
* 如果数据库不是innodb 该如何去加锁 起到分布式锁的作用?
*
* 使用redis
* 分布式锁
* mysql的行锁**
* redis setnx(set if not exist)*****
* zookeeper
*
* @param userId
* @param goodsId
*/
//@Override
//@Transactional(rollbackFor = RuntimeException.class)
//public void realDoSeckill(Integer userId, Integer goodsId) {
// // 直接使用行锁 控制卖超问题
// int i = goodsMapper.updateGoodsStock(goodsId);
// if (i > 0) {
// // 写订单表
// writeOrder(goodsId, userId);
// }
//}
/**
* redis分布式锁
*
* @param userId
* @param goodsId
*/
//@Override
//@Transactional(rollbackFor = RuntimeException.class)
//public void realDoSeckill(Integer userId, Integer goodsId) {
// // 当A B 两个线程同时进入去获取redis的key B没有成功 那么B就结束了 在库存充足的情况下 不是很合理
// // 结合自旋来处理这个场景问题 自旋锁 (可以让这个线程在这里空转)
// // jedis#setnx == redisTemplate#setIfAbsent
// long currentTime = 0L;
// while (currentTime < time) {
// // 我们在设置锁的时候 建议添加一个过期时间 避免出现死锁的情况
// // 讨论业务代码执行超时问题 续命
// Boolean flag = redisTemplate.opsForValue().setIfAbsent("goods_lock:" + goodsId, "", 60, TimeUnit.SECONDS);
// // 我们可以拿到获取到所得这个线程的id
// // 当时间快过期了 我就去判断一下当前线程是否还在里面执行 3 次 如果超过一定的次数 就不处理了
// //long id = Thread.currentThread().getId();
// //redisTemplate.expire("goods_lock:" + goodsId, 60, TimeUnit.SECONDS);
// if (flag) {
// try {
// // 直接使用行锁 控制卖超问题
// int i = goodsMapper.updateGoodsStock(goodsId);
// if (i > 0) {
// // 写订单表
// writeOrder(goodsId, userId);
// }
// // 加了自旋注意return的时机
// return;
// } finally {
// // 一定会执行
// // 使用redis来做分布式锁 一定要删除掉
// redisTemplate.delete("goods_lock:" + goodsId);
// }
// } else {
// // 可以给定一些机会
// currentTime += 200;
// // 没获取成功 就走else
// try {
// Thread.sleep(200);
// } catch (InterruptedException e) {
// throw new RuntimeException(e);
// }
// }
// }
//
//
//}
@Override
@Transactional(rollbackFor = RuntimeException.class)
public void realDoSeckill(Integer userId, Integer goodsId) {
RLock lock = redissonClient.getLock("goodsLock:" + goodsId);
lock.lock(30, TimeUnit.SECONDS);
// 直接使用行锁 控制卖超问题
try {
int i = goodsMapper.updateGoodsStock(goodsId);
if (i > 0) {
// 写订单表
writeOrder(goodsId, userId);
}
} finally {
lock.unlock();
}
}
private void writeOrder(Integer goodsId, Integer userId) {
Order order = new Order();
order.setCreatetime(new Date());
order.setUserid(userId);
order.setGoodsid(goodsId);
int i = orderMapper.insert(order);
if (i <= 0) {
throw new RuntimeException("写入订单表失败");
}
}
}
package com.powernode.mapper;
import com.powernode.domain.Goods;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface GoodsMapper {
int deleteByPrimaryKey(Integer goodsId);
int insert(Goods record);
int insertSelective(Goods record);
Goods selectByPrimaryKey(Integer goodsId);
int updateByPrimaryKeySelective(Goods record);
int updateByPrimaryKey(Goods record);
List<Goods> selectSeckillGoods();
int updateGoodsStock(Integer goodsId);
}
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.powernode.mapper.GoodsMapper">
<resultMap id="BaseResultMap" type="com.powernode.domain.Goods">
<id column="goods_id" jdbcType="INTEGER" property="goodsId" />
<result column="goods_name" jdbcType="VARCHAR" property="goodsName" />
<result column="price" jdbcType="DECIMAL" property="price" />
<result column="content" jdbcType="LONGVARCHAR" property="content" />
<result column="status" jdbcType="INTEGER" property="status" />
<result column="total_stocks" jdbcType="INTEGER" property="totalStocks" />
<result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
<result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
<result column="spike" jdbcType="INTEGER" property="spike" />
resultMap>
<sql id="Base_Column_List">
goods_id, goods_name, price, content, `status`, total_stocks, create_time, update_time,
spike
sql>
<select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from goods
where goods_id = #{goodsId,jdbcType=INTEGER}
select>
<delete id="deleteByPrimaryKey" parameterType="java.lang.Integer">
delete from goods
where goods_id = #{goodsId,jdbcType=INTEGER}
delete>
<insert id="insert" keyColumn="goods_id" keyProperty="goodsId" parameterType="com.powernode.domain.Goods" useGeneratedKeys="true">
insert into goods (goods_name, price, content,
`status`, total_stocks, create_time,
update_time, spike)
values (#{goodsName,jdbcType=VARCHAR}, #{price,jdbcType=DECIMAL}, #{content,jdbcType=LONGVARCHAR},
#{status,jdbcType=INTEGER}, #{totalStocks,jdbcType=INTEGER}, #{createTime,jdbcType=TIMESTAMP},
#{updateTime,jdbcType=TIMESTAMP}, #{spike,jdbcType=INTEGER})
insert>
<insert id="insertSelective" keyColumn="goods_id" keyProperty="goodsId" parameterType="com.powernode.domain.Goods" useGeneratedKeys="true">
insert into goods
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="goodsName != null and goodsName != ''">
goods_name,
if>
<if test="price != null">
price,
if>
<if test="content != null and content != ''">
content,
if>
<if test="status != null">
`status`,
if>
<if test="totalStocks != null">
total_stocks,
if>
<if test="createTime != null">
create_time,
if>
<if test="updateTime != null">
update_time,
if>
<if test="spike != null">
spike,
if>
trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="goodsName != null and goodsName != ''">
#{goodsName,jdbcType=VARCHAR},
if>
<if test="price != null">
#{price,jdbcType=DECIMAL},
if>
<if test="content != null and content != ''">
#{content,jdbcType=LONGVARCHAR},
if>
<if test="status != null">
#{status,jdbcType=INTEGER},
if>
<if test="totalStocks != null">
#{totalStocks,jdbcType=INTEGER},
if>
<if test="createTime != null">
#{createTime,jdbcType=TIMESTAMP},
if>
<if test="updateTime != null">
#{updateTime,jdbcType=TIMESTAMP},
if>
<if test="spike != null">
#{spike,jdbcType=INTEGER},
if>
trim>
insert>
<update id="updateByPrimaryKeySelective" parameterType="com.powernode.domain.Goods">
update goods
<set>
<if test="goodsName != null and goodsName != ''">
goods_name = #{goodsName,jdbcType=VARCHAR},
if>
<if test="price != null">
price = #{price,jdbcType=DECIMAL},
if>
<if test="content != null and content != ''">
content = #{content,jdbcType=LONGVARCHAR},
if>
<if test="status != null">
`status` = #{status,jdbcType=INTEGER},
if>
<if test="totalStocks != null">
total_stocks = #{totalStocks,jdbcType=INTEGER},
if>
<if test="createTime != null">
create_time = #{createTime,jdbcType=TIMESTAMP},
if>
<if test="updateTime != null">
update_time = #{updateTime,jdbcType=TIMESTAMP},
if>
<if test="spike != null">
spike = #{spike,jdbcType=INTEGER},
if>
set>
where goods_id = #{goodsId,jdbcType=INTEGER}
update>
<update id="updateByPrimaryKey" parameterType="com.powernode.domain.Goods">
update goods
set goods_name = #{goodsName,jdbcType=VARCHAR},
price = #{price,jdbcType=DECIMAL},
content = #{content,jdbcType=LONGVARCHAR},
`status` = #{status,jdbcType=INTEGER},
total_stocks = #{totalStocks,jdbcType=INTEGER},
create_time = #{createTime,jdbcType=TIMESTAMP},
update_time = #{updateTime,jdbcType=TIMESTAMP},
spike = #{spike,jdbcType=INTEGER}
where goods_id = #{goodsId,jdbcType=INTEGER}
update>
<select id="selectSeckillGoods" resultMap="BaseResultMap">
select goods_id ,total_stocks from goods where spike = 1 and `status` = 1
select>
<update id="updateGoodsStock">
UPDATE goods
SET total_stocks = total_stocks - 1,
update_time = now()
WHERE
goods_id = #{value}
AND total_stocks - 1 >= 0
update>
mapper>
package com.powernode.service;
import com.powernode.domain.Order;
public interface OrderService{
int deleteByPrimaryKey(Integer id);
int insert(Order record);
int insertSelective(Order record);
Order selectByPrimaryKey(Integer id);
int updateByPrimaryKeySelective(Order record);
int updateByPrimaryKey(Order record);
}
package com.powernode.service.impl;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import com.powernode.domain.Order;
import com.powernode.mapper.OrderMapper;
import com.powernode.service.OrderService;
@Service
public class OrderServiceImpl implements OrderService{
@Resource
private OrderMapper orderMapper;
@Override
public int deleteByPrimaryKey(Integer id) {
return orderMapper.deleteByPrimaryKey(id);
}
@Override
public int insert(Order record) {
return orderMapper.insert(record);
}
@Override
public int insertSelective(Order record) {
return orderMapper.insertSelective(record);
}
@Override
public Order selectByPrimaryKey(Integer id) {
return orderMapper.selectByPrimaryKey(id);
}
@Override
public int updateByPrimaryKeySelective(Order record) {
return orderMapper.updateByPrimaryKeySelective(record);
}
@Override
public int updateByPrimaryKey(Order record) {
return orderMapper.updateByPrimaryKey(record);
}
}
package com.powernode.mapper;
import com.powernode.domain.Order;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface OrderMapper {
int deleteByPrimaryKey(Integer id);
int insert(Order record);
int insertSelective(Order record);
Order selectByPrimaryKey(Integer id);
int updateByPrimaryKeySelective(Order record);
int updateByPrimaryKey(Order record);
}
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.powernode.mapper.OrderMapper">
<resultMap id="BaseResultMap" type="com.powernode.domain.Order">
<id column="id" jdbcType="INTEGER" property="id" />
<result column="userid" jdbcType="INTEGER" property="userid" />
<result column="goodsid" jdbcType="INTEGER" property="goodsid" />
<result column="createtime" jdbcType="TIMESTAMP" property="createtime" />
resultMap>
<sql id="Base_Column_List">
id, userid, goodsid, createtime
sql>
<select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from `order`
where id = #{id,jdbcType=INTEGER}
select>
<delete id="deleteByPrimaryKey" parameterType="java.lang.Integer">
delete from `order`
where id = #{id,jdbcType=INTEGER}
delete>
<insert id="insert" keyColumn="id" keyProperty="id" parameterType="com.powernode.domain.Order" useGeneratedKeys="true">
insert into `order` (userid, goodsid, createtime
)
values (#{userid,jdbcType=INTEGER}, #{goodsid,jdbcType=INTEGER}, #{createtime,jdbcType=TIMESTAMP}
)
insert>
<insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="com.powernode.domain.Order" useGeneratedKeys="true">
insert into `order`
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="userid != null">
userid,
if>
<if test="goodsid != null">
goodsid,
if>
<if test="createtime != null">
createtime,
if>
trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="userid != null">
#{userid,jdbcType=INTEGER},
if>
<if test="goodsid != null">
#{goodsid,jdbcType=INTEGER},
if>
<if test="createtime != null">
#{createtime,jdbcType=TIMESTAMP},
if>
trim>
insert>
<update id="updateByPrimaryKeySelective" parameterType="com.powernode.domain.Order">
update `order`
<set>
<if test="userid != null">
userid = #{userid,jdbcType=INTEGER},
if>
<if test="goodsid != null">
goodsid = #{goodsid,jdbcType=INTEGER},
if>
<if test="createtime != null">
createtime = #{createtime,jdbcType=TIMESTAMP},
if>
set>
where id = #{id,jdbcType=INTEGER}
update>
<update id="updateByPrimaryKey" parameterType="com.powernode.domain.Order">
update `order`
set userid = #{userid,jdbcType=INTEGER},
goodsid = #{goodsid,jdbcType=INTEGER},
createtime = #{createtime,jdbcType=TIMESTAMP}
where id = #{id,jdbcType=INTEGER}
update>
mapper>
链接:https://pan.baidu.com/s/1qoZm_eBp0v9ghbUIPcCe0g?pwd=0lqd
提取码:0lqd