RocketMQ实现优惠券秒杀

news/2024/11/5 14:10:16/文章来源:https://www.cnblogs.com/xu1feng/p/18527732

秒杀

07-秒杀.jpg

架构图

image.png

准备数据库

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;-- ----------------------------
-- Table structure for goods
-- ----------------------------
DROP TABLE IF EXISTS `goods`;
CREATE TABLE `goods`  (`id` int(11) NOT NULL AUTO_INCREMENT,`goods_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL,`price` decimal(10, 2) NULL DEFAULT NULL,`stocks` int(255) NULL DEFAULT NULL,`status` int(255) NULL DEFAULT NULL,`pic` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL,`create_time` datetime(0) NULL DEFAULT NULL,`update_time` datetime(0) NULL DEFAULT NULL,PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;-- ----------------------------
-- Records of goods
-- ----------------------------
INSERT INTO `goods` VALUES (1, '小米12s', 4999.00, 1000, 2, 'xxxxxx', '2023-02-23 11:35:56', '2023-02-23 16:53:34');
INSERT INTO `goods` VALUES (2, '华为mate50', 6999.00, 10, 2, 'xxxx', '2023-02-23 11:35:56', '2023-02-23 11:35:56');
INSERT INTO `goods` VALUES (3, '锤子pro2', 1999.00, 100, 1, NULL, '2023-02-23 11:35:56', '2023-02-23 11:35:56');-- ----------------------------
-- Table structure for order_records
-- ----------------------------
DROP TABLE IF EXISTS `order_records`;
CREATE TABLE `order_records`  (`id` int(11) NOT NULL AUTO_INCREMENT,`user_id` int(11) NULL DEFAULT NULL,`order_sn` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL,`goods_id` int(11) NULL DEFAULT NULL,`create_time` datetime(0) NULL DEFAULT NULL,PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;SET FOREIGN_KEY_CHECKS = 1;

创建项目seckill-web(接收用户秒杀请求)

pom.xml

<?xml version="1.0" encoding="UTF-8"?>  
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">  <modelVersion>4.0.0</modelVersion>  <parent>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-starter-parent</artifactId>  <version>2.7.11</version>  <relativePath/> <!-- lookup parent from repository -->  </parent>  <groupId>com.xyf</groupId>  <artifactId>e-seckill-web</artifactId>  <version>1.0-SNAPSHOT</version>  <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-web</artifactId>  </dependency>  <dependency>  <groupId>org.apache.rocketmq</groupId>  <artifactId>rocketmq-spring-boot-starter</artifactId>  <version>2.2.2</version>  </dependency>  <dependency>  <groupId>com.alibaba</groupId>  <artifactId>fastjson</artifactId>  <version>2.0.25</version>  </dependency>  <dependency>  <groupId>org.projectlombok</groupId>  <artifactId>lombok</artifactId>  <optional>true</optional>  </dependency>  <dependency>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-starter-test</artifactId>  <scope>test</scope>  </dependency>  </dependencies>  <build>  <plugins>  <plugin>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-maven-plugin</artifactId>  <configuration>  <excludes>  <exclude>  <groupId>org.projectlombok</groupId>  <artifactId>lombok</artifactId>  </exclude>  </excludes>  </configuration>  </plugin>  </plugins>  </build>  </project>

修改配置文件application.yml

[server:  port: 8081  tomcat:  threads:  max: 400  
spring:  redis:  host: localhost  port: 16379  database: 0  
rocketmq:  name-server: 127.0.0.1:9876](<server:port: 8001tomcat:threads:max: 400
spring:application:name: seckill-webredis:host: 127.0.0.1port: 16379database: 0lettuce:pool:enabled: truemax-active: 100max-idle: 20min-idle: 5
rocketmq:name-server: 127.0.0.1:9876     # rocketMq的nameServer地址producer:group: seckill-producer-group        # 生产者组别send-message-timeout: 3000  # 消息发送的超时时间retry-times-when-send-async-failed: 2  # 异步消息发送失败重试次数max-message-size: 4194304       # 消息的最大长度>)

创建SecKillController

@RestController  
public class SeckillController {  @Autowired  private StringRedisTemplate redisTemplate;  @Autowired  private RocketMQTemplate rocketMQTemplate;  /**  * 压测时自动是生成用户id  */    AtomicInteger ai = new AtomicInteger(0);  /**  * 1.一个用户针对一种商品只能抢购一次  * 2.做库存的预扣减  拦截掉大量无效请求  * 3.放入mq 异步化处理订单  *  * @return  */  @GetMapping("doSeckill")  public String doSeckill(Integer goodsId /*, Integer userId*/) {  int userId = ai.incrementAndGet();  // unique key 唯一标记 去重  String uk = userId + "-" + goodsId;  // set nx  set if not exist  Boolean flag = redisTemplate.opsForValue().setIfAbsent("seckillUk:" + uk, "");  if (!flag) {  return "您以及参与过该商品的抢购,请参与其他商品抢购!";  }  // 假设库存已经同步了  key:goods_stock:1  val:10        Long count = redisTemplate.opsForValue().decrement("goods_stock:" + goodsId);  // getkey  java  setkey    先查再写 再更新 有并发安全问题  if (count < 0) {  return "该商品已经被抢完,请下次早点来哦O(∩_∩)O";  }  // 放入mq  HashMap<String, Integer> map = new HashMap<>(4);  map.put("goodsId", goodsId);  map.put("userId", userId);  rocketMQTemplate.asyncSend("seckillTopic3", JSON.toJSONString(map), new SendCallback() {  @Override  public void onSuccess(SendResult sendResult) {  System.out.println("发送成功" + sendResult.getSendStatus());  }  @Override  public void onException(Throwable throwable) {  System.err.println("发送失败" + throwable);  }  });  return "拼命抢购中,请稍后去订单中心查看";  }  }

创建项目seckill-service(处理秒杀)

pom.xml

<?xml version="1.0" encoding="UTF-8"?>  
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">  <modelVersion>4.0.0</modelVersion>  <parent>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-starter-parent</artifactId>  <version>2.7.11</version>  <relativePath/> <!-- lookup parent from repository -->  </parent>  <groupId>com.xyf</groupId>  <artifactId>f-seckill-service</artifactId>  <version>1.0-SNAPSHOT</version>  <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-web</artifactId>  </dependency>  <dependency>  <groupId>org.mybatis.spring.boot</groupId>  <artifactId>mybatis-spring-boot-starter</artifactId>  <version>2.3.0</version>  </dependency>  <dependency>  <groupId>org.apache.rocketmq</groupId>  <artifactId>rocketmq-spring-boot-starter</artifactId>  <version>2.2.2</version>  </dependency>  <dependency>  <groupId>com.alibaba</groupId>  <artifactId>fastjson</artifactId>  <version>2.0.25</version>  </dependency>  <dependency>  <groupId>com.mysql</groupId>  <artifactId>mysql-connector-j</artifactId>  <scope>runtime</scope>  </dependency>  <dependency>  <groupId>org.projectlombok</groupId>  <artifactId>lombok</artifactId>  <optional>true</optional>  </dependency>  <dependency>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-starter-test</artifactId>  <scope>test</scope>  </dependency>  </dependencies>  <build>  <plugins>  <plugin>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-maven-plugin</artifactId>  <configuration>  <excludes>  <exclude>  <groupId>org.projectlombok</groupId>  <artifactId>lombok</artifactId>  </exclude>  </excludes>  </configuration>  </plugin>  </plugins>  </build>  </project>

修改yml文件

server:  port: 8002  
spring:  application:  name: seckill-service  datasource:  driver-class-name: com.mysql.cj.jdbc.Driver  url: jdbc:mysql://127.0.0.1:13306/spike?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC  username: root  password: 123456  redis:  host: 127.0.0.1  port: 16379  database: 0  lettuce:  pool:  enabled: true  max-active: 100  max-idle: 20  min-idle: 5  
mybatis:  configuration:  log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  mapper-locations: classpath*:mapper/*.xml  
rocketmq:  name-server: 127.0.0.1:9876

逆向生成实体类等

修改启动类

@SpringBootApplication  
@MapperScan(basePackages = {"com.xyf.mapper"})  
public class SpikeServiceApplication {  public static void main(String[] args) {  SpringApplication.run(SpikeServiceApplication.class, args);  }  }

修改GoodsMapper

List<Goods> selectSeckillGoods();

修改GoodsMapper.xml

<select id="selectSeckillGoods" resultType="com.xyf.domain.Goods">  select id, stocks from goods where `status` = 2  
</select>

同步mysql数据到redis

/**  * 1. 每天10点 晚上8点 通过定时任务 将mysql的库存 同步到redis中去  * 2. 为了测试方便 希望项目启动的时候 就同步数据  */  
@Component  
public class DataSync {  @Resource  private GoodsMapper goodsMapper;  @Resource  private StringRedisTemplate redisTemplate;  //    @Scheduled(cron = "* * 10 * * ? ")  
//    public void initData() {  
//  
//    }  /**  * 我希望这个方法在项目启动之后  * 并且在这个类的属性注入完毕以后  *  * bean的生命周期  *  * 实例化 new  * 属性复制  * 初始化 (前PostConstruct/中InitializingBean/后BeanPostProcessor)自定义的一个initMethod方法  * 使用  * 销毁  * -------------  */    @PostConstruct  public void initData() {  List<Goods> goodsList = goodsMapper.selectSeckillGoods();  if (CollectionUtils.isEmpty(goodsList)) {  return;  }  goodsList.forEach(goods -> {  redisTemplate.opsForValue().set("goodsId:" + goods.getId(), goods.getStocks().toString());  });  }  }

创建秒杀监听

@Component  
@RocketMQMessageListener(topic = "seckillTopic3", consumerGroup = "seckill-consumer-group")  
public class SeckillMsgListener implements RocketMQListener<MessageExt> {  @Autowired  private GoodsService goodsService;  @Autowired  private StringRedisTemplate redisTemplate;  // 20s  int time = 20000;  @Override  public void onMessage(MessageExt message) {  String s = new String(message.getBody());  JSONObject jsonObject = JSON.parseObject(s);  Integer goodsId = jsonObject.getInteger("goodsId");  Integer userId = jsonObject.getInteger("userId");  // 做真实的抢购业务  减库存 写订单表    todo 答案2  但是不符合分布式  
//        synchronized (SeckillMsgListener.class) {  
//            goodsService.realDoSeckill(goodsId, userId);  
//        }  // 自旋锁  一般 mysql 每秒1500/s写   看数量 合理的设置自旋时间  todo 答案3  int current = 0;  while (current <= time) {  // 一般在做分布式锁的情况下  会给锁一个过期时间 防止出现死锁的问题  Boolean flag = redisTemplate.opsForValue().setIfAbsent("goods_lock:" + goodsId, "", 10, TimeUnit.SECONDS);  if (flag) {  try {  goodsService.realSeckill(goodsId, userId);  return;  } finally {  redisTemplate.delete("goods_lock:" + goodsId);  }  } else {  current += 200;  try {  Thread.sleep(200);  } catch (InterruptedException e) {  e.printStackTrace();  }  }  }  }  
}

修改GoodsService

/**  * 真正处理秒杀的业务  * @param userId  * @param goodsId  */  
void realSeckill(Integer userId, Integer goodsId);

修改GoodsServiceImpl

@Service  
public class GoodsServiceImpl implements GoodsService {  @Autowired  private GoodsMapper goodsMapper;  @Autowired  private OrderRecordsMapper orderRecordsMapper;  @Override  public int deleteByPrimaryKey(Integer id) {  return goodsMapper.deleteByPrimaryKey(id);  }  @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 id) {  return goodsMapper.selectByPrimaryKey(id);  }  @Override  public int updateByPrimaryKeySelective(Goods record) {  return goodsMapper.updateByPrimaryKeySelective(record);  }  @Override  public int updateByPrimaryKey(Goods record) {  return goodsMapper.updateByPrimaryKey(record);  }  /**  * @param goodsId  * @param userId  */  @Override  @Transactional(rollbackFor = RuntimeException.class)  public void realSeckill(Integer goodsId, Integer userId) {  // 扣减库存  插入订单表  Goods goods = goodsMapper.selectByPrimaryKey(goodsId);  int finalStock = goods.getStocks() - 1;  if (finalStock < 0) {  // 只是记录日志 让代码停下来   这里的异常用户无法感知  throw new RuntimeException("库存不足:" + goodsId);  }  goods.setStocks(finalStock);  goods.setUpdateTime(new Date());  // insert 要么成功 要么报错  update 会出现i<=0的情况  // update goods set stocks =  1 where id = 1  没有行锁  int i = goodsMapper.updateByPrimaryKey(goods);  if (i > 0) {  // 写订单表  OrderRecords orderRecords = new OrderRecords();  orderRecords.setGoodsId(goodsId);  orderRecords.setUserId(userId);  orderRecords.setCreateTime(new Date());  // 时间戳生成订单号  orderRecords.setOrderSn(String.valueOf(System.currentTimeMillis()));  orderRecordsMapper.insert(orderRecords);  }  }  
}  /**  * mysql行锁  innodb  行锁  * 分布式锁  * todo 答案1  *  * @param goodsId  * @param userId  */  
//    @Override  
//    @Transactional(rollbackFor = RuntimeException.class)  
//    public void realDoSeckill(Integer goodsId, Integer userId) {  
//        // update goods set stocks = stocks - 1 ,update_time = now() where id = #{value}  
//        int i = goodsMapper.updateStocks(goodsId);  
//        if (i > 0) {  
//            // 写订单表  
//            OrderRecords orderRecords = new OrderRecords();  
//            orderRecords.setGoodsId(goodsId);  
//            orderRecords.setUserId(userId);  
//            orderRecords.setCreateTime(new Date());  
//            // 时间戳生成订单号  
//            orderRecords.setOrderSn(String.valueOf(System.currentTimeMillis()));  
//            orderRecordsMapper.insert(orderRecords);  
//        }  
//    }

秒杀总结

技术选型:SpringBoot + Redis + MySQL + RocketMQ + Security ......

设计:(抢优惠券...)

  1. 设计seckill-web接收处理秒杀请求
  2. 设计seckill-service处理秒杀真实业务的

部署细节: 2C 2B

  1. 用户量:50w
  2. QPS:2w+ 自己打日志、Nginx(access.log)
  3. 日活量:1w-2w 1%-5%
  4. 几台服务器(什么配置)
  5. 带宽

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/827136.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2024.11.5 人工智能在小学教育教学中的应用

【知识小课堂1】概念与历史 人工智能(Artificial Intelligence),引文缩写为AI。它是研究、开发用于模拟延伸和扩展人的智能的理论、方法、技术及应用系统的一门新的技术科学。 (一)学科范畴 人工智能是一门边沿学科,属于自然科学、社会科学、技术科学三向交叉学科。 (二)涉及…

S7-1200对V90 PN进行位置控制的三种方法

S7-1200系列PLC通过PROFINET与V90 PN伺服驱动器搭配进行位置控制,实现的方法主要有以下三种: • 方法一、在PLC中组态位置轴工艺对象,V90使用标准报文3,通过MC_Power、MC_MoveAbsolute等PLC Open标准程序块进行控制, 这种控制方式属于中央控制方式(位置控制在PLC中计算,驱…

11.5 人工智能学习内容

人工智能(Artificial Intel ligence) 引文缩写为AI。它是研究、开发用于模拟、延伸和扩展人的智能的理论、方法、技术及应用系统的一门新的技术科学。 (一)学科范畴 人工智能是一门边沿学科,属于自然科学、社会科学、技术科学三向交叉学科。 (二)涉及学科与领域 哲学和认…

Nuxt.js 应用中的 nitro:build:public-assets 事件钩子详解

title: Nuxt.js 应用中的 nitro:build:public-assets 事件钩子详解 date: 2024/11/5 updated: 2024/11/5 author: cmdragon excerpt: nitro:build:public-assets 是 Nuxt 3 中的一个生命周期钩子,在复制公共资产之后调用。该钩子使开发者能够在构建 Nitro 服务器之前,对公…

FB284功能说明

FB284功能说明带增量编码器V90,使用参考挡块+编码器零脉冲方式回零时,参考挡块回零开关接到哪里,怎样配置 回零开关连接到一个PLC的数字量输入点,PLC内编程将其状态关联到FB284功能块ConfigEPos输入引脚的bit6。 (1)将V90参数P29240设置为1(选择参考挡块+零脉冲方式回零)…

《图解设计模式》 第九部分 避免浪费

第二十章 Flyweight 模式public class BigcharFactory{//这里对使用到的内容进行了缓存private HashMap pool = new HashMap();//有则直接取,无则创建并保存到缓存。public synchronized BigChar getBigChar(char charname){BigChar bc = (BigChar) pool.get("" + c…

黑马PM-电商项目-订单管理

支付管理订单管理订单统计评价管理

记录一下从keil官网下载DFP(芯片支持包)的方法

1.打开官网 www.keil.arm.com2.点击右上角的下载按钮,进入下一页面3.选择MDK-ARM会进入到MDK下载页面。 这里我们不用下载MDK。4.选择左下角的芯片列表按钮5.选择我们使用的芯片(我这里使用的是STM32L051系列)6.然后选择DFP7.在右上角选择下载DFP文件

.NET 全能高效的 CMS 内容管理系统

前言 推荐一款强大的企业级工具 — SSCMS 内容管理系统。 SSCMS 为企业级客户设计,完全开源免费,适用于商业用途且无需支付任何产品或授权费用。 本文将详细介绍 SSCMS 系统的功能、用户界面及使用注意事项等内容。 项目介绍 SSCMS 基于 .NET Core 开发,无论是在 Windows、L…

NewStar CTF 2024 misc WP

decompress压缩包套娃,一直解到最后一层,将文件提取出来提示给出了一个正则,按照正则爆破密码,一共五位,第四位是数字 ^([a-z]){3}\d[a-z]$一共就五位数,直接ARCHPR爆破,得到密码 xtr4m,解压得到flagpleasingMusic题目描述中提到:一首歌可以好听到正反都好听根据提示(…