0
点赞
收藏
分享

微信扫一扫

秒杀场景_同步秒杀分析和实战_01


秒杀场景_同步秒杀分析和实战_01_spring

文章目录

  • ​​一、应用部署准备​​
  • ​​1. mysql安装部署​​
  • ​​2. redis安装部署​​
  • ​​3. nacos安装部署​​
  • ​​二、数据库准备​​
  • ​​2.1. 创建数据库​​
  • ​​2.2. 初始化表结构​​
  • ​​2.3. 搭建微服务父工程​​
  • ​​三、商品模块微服务​​
  • ​​3.1. 搭建product-serv模块​​
  • ​​3.2. 配置yml​​
  • ​​3.3. 实体​​
  • ​​3.4. 接口​​
  • ​​3.5. service​​
  • ​​3.6. controller​​
  • ​​3.7. 启动类​​
  • ​​四、秒杀模块微服务​​
  • ​​4.1. 搭建skill-serv模块​​
  • ​​4.2. 配置yml​​
  • ​​4.3. 实体​​
  • ​​4.4. 接口​​
  • ​​4.5. service​​
  • ​​4.6. controller​​
  • ​​4.7. 启动类​​

前言:为什么单独搭建秒杀微服务呢?
为什么不对订单微服务和库存为负进行复用呢?

  • 与正常业务服务隔离避免因秒杀影响主营业务
  • 可根据秒杀流量单独扩容和设置参数
一、应用部署准备
1. mysql安装部署

windows 环境
​​​MySQL 8.0.26 简易配置安装教程 (windows 64位)​​

Linux 环境
​​​Mysql 8.0 安装教程 Linux Centos7​​

2. redis安装部署

windows 环境
​​​windows下载、安装运行redis​​

Linux 环境
​​​(单机)Linux环境安装最新版Redis-6.2.0​​​​linux环境下redis5.0的安装配置​​

秒杀场景_同步秒杀分析和实战_01_redis_02

3. nacos安装部署

版本选择
毕业版本依赖关系(推荐使用)

Spring Cloud Version

Spring Cloud Alibaba Version

Spring Boot Version

Nacos Version

Spring Cloud Hoxton.SR9

2.2.6.RELEASE

2.3.2.RELEASE

1.4.2

nacos官网:
​​​https://nacos.io/zh-cn/docs/quick-start.html​​

下载对用版本的nacos
​​​https://github.com/alibaba/nacos/tags​​

启动nacos

# 启动命令(standalone代表着单机模式运行,非集群模式):
# linux
sh startup.sh -m standalone

# Windows

秒杀场景_同步秒杀分析和实战_01_redis_03

二、数据库准备
2.1. 创建数据库

创建一个名称为​​skill​​的数据库

2.2. 初始化表结构

-- ----------------------------
-- Table structure for skill_goods
-- ----------------------------
DROP TABLE IF EXISTS `skill_goods`;
CREATE TABLE `skill_goods` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(100) 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 '秒杀价格',
`status` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '审核状态,0未审核,1审核通过,2审核不通过',
`num` int NULL DEFAULT NULL COMMENT '秒杀商品数',
`stock_count` int NULL DEFAULT NULL COMMENT '剩余库存数',
`introduction` varchar(2000) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '描述',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Table structure for skill_order
-- ----------------------------
DROP TABLE IF EXISTS `skill_order`;
CREATE TABLE `skill_order` (
`id` bigint NOT NULL AUTO_INCREMENT,
`skill_id` bigint NULL DEFAULT NULL COMMENT '秒杀商品ID',
`money` decimal(10, 2) NULL DEFAULT NULL COMMENT '支付金额',
`user_id` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户',
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
`pay_time` datetime NULL DEFAULT NULL COMMENT '支付时间',
`status` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '状态0-未支付, 1-已支付',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;

2.3. 搭建微服务父工程

由于很多依赖都是一样的,因此,搭建一个父工程引入依赖,子模块集成依赖即可
创建eshop-parent父工程
引入依赖

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

<groupId>com.gblfy</groupId>
<artifactId>eshop-parent</artifactId>
<version>1.0-SNAPSHOT</version>

<!--https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E-->
<properties>
<java.version>1.8</java.version>
<spring.cloud-version>Hoxton.SR9</spring.cloud-version>
</properties>

<dependencies>
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--Lombok引入-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- Spring Boot JPA 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!--mvc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--服务注册发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--配置中心-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<!--spring-cloud 版本控制-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud-version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring-cloud-alibaba 版本控制-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.6.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>

三、商品模块微服务
3.1. 搭建product-serv模块

集成父工程

<parent>
<artifactId>eshop-parent</artifactId>
<groupId>com.gblfy</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>product-serv</artifactId>

3.2. 配置yml

server:
port: 9000
spring:
cloud:
nacos:
discovery:
service: product-serv
server-addr: localhost:8848
datasource:
url: jdbc:mysql://localhost:3306/skill?characterEncoding=UTF-8&serverTimezone=GMT%2B8
username: root
password: 123456
redis:
host: localhost
port: 6379

3.3. 实体

package com.gblfy.entity;

import lombok.Data;

import javax.persistence.*;
import java.io.Serializable;
import java.math.BigDecimal;

@Data
@Entity
@Table(name="skill_goods")
public class SkillGood implements Serializable {
public SkillGood() {
}
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

/**
* 标题
*/
@Column(name = "name")
private String name;

/**
* 原价格
*/
@Column(name = "price")
private BigDecimal price;

/**
* 秒杀价格
*/
@Column(name = "cost_price")
private BigDecimal costPrice;
/**
* 审核状态
*/
@Column(name = "status")
private String status;


/**
* 秒杀商品数
*/
@Column(name = "num")
private Integer num;

/**
* 剩余库存数
*/
@Column(name = "stock_count")
private Integer stockCount;

/**
* 描述
*/
@Column(name = "introduction")
private String introduction;

private static final long serialVersionUID = 1L;

}

3.4. 接口

package com.gblfy.dao;

import com.gblfy.entity.SkillGood;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface SkillGoodRepository extends JpaRepository<SkillGood,Long> {

@Query(value="select * from skill_goods where status=1 and num>0 and stock_count>0 and id not in (?1)",nativeQuery = true)
List<SkillGood> findSkill(List<Long> ids);

@Query(value="select * from skill_goods where status=1 and num>0 and stock_count>0",nativeQuery = true)
List<SkillGood> findSkillAll();
}

3.5. service

package com.gblfy.service;

import com.gblfy.dao.SkillGoodRepository;
import com.gblfy.entity.SkillGood;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

@Component
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class SkillGoodService {

private final RedisTemplate redisTemplate;
private final SkillGoodRepository skillGoodRepository;
public static final String SKILL_GOODS_PHONE = "SKILL_GOODS_PHONE";

/**
* 每五秒执行一次 将需要参与秒杀的商品列表加载到内存
*/
@Scheduled(cron = "0/5 * * * * ?")
public void prepareGood() {
System.out.println("开始加载商品");
//获取所有已经在内存当中的商品ID列表
Set<Long> set = redisTemplate.boundHashOps(SKILL_GOODS_PHONE).keys();
List<Long> ids = new ArrayList<>();
for (Long id : set) {
ids.add(id);
}
List<SkillGood> list = null;
//只查询出不在内存当中的商品信息,并加载到内存
if (CollectionUtils.isEmpty(ids)) {
list = skillGoodRepository.findSkillAll();
} else {
list = skillGoodRepository.findSkill(ids);
}
if (!CollectionUtils.isEmpty(list)) {
for (SkillGood skillGood : list) {
redisTemplate.boundHashOps(SKILL_GOODS_PHONE).put(skillGood.getId(), skillGood);
}
}
// 查看当前缓存中所有的商品信息
Set keys = redisTemplate.boundHashOps(SKILL_GOODS_PHONE).keys();
for (Object s : keys) {
SkillGood skillGood = (SkillGood) redisTemplate.boundHashOps(SKILL_GOODS_PHONE).get(s);
System.out.println(skillGood.getName() + " 库存剩余:" + skillGood.getStockCount());
}
}

// 提供查询商品信息的方法
public SkillGood queryProduct(Long productId) {
return (SkillGood) redisTemplate.boundHashOps(SKILL_GOODS_PHONE).get(productId);
}

public void update(SkillGood skillGood) {
skillGoodRepository.save(skillGood);
}
}

3.6. controller

package com.gblfy.controller;

import com.gblfy.entity.SkillGood;
import com.gblfy.service.SkillGoodService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
public class ProductController {

@Autowired
private SkillGoodService skillGoodService;

@GetMapping("/product/{productId}")
@ResponseBody
public SkillGood getProduct(@PathVariable Long productId){
System.out.println("调用商品服务");
SkillGood skillGood=skillGoodService.queryProduct(productId);
return skillGood;
}
@PostMapping("/product")
public String update(@RequestBody SkillGood skillGood){
System.out.println("更新库存");
skillGoodService.update(skillGood);
return "更新库存成功";
}
}

3.7. 启动类

package com.gblfy;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling
public class ProductAplication {

public static void main(String[] args) {
SpringApplication.run(ProductAplication.class);
}

@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
//采用普通的key 为 字符串
template.setKeySerializer(new StringRedisSerializer());
return template;
}
}

四、秒杀模块微服务
4.1. 搭建skill-serv模块

集成父工程

<parent>
<artifactId>eshop-parent</artifactId>
<groupId>com.gblfy</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>skill-serv</artifactId>

4.2. 配置yml

server:
port: 13000
spring:
cloud:
nacos:
discovery:
service: skill-serv
server-addr: localhost:8848
datasource:
url: jdbc:mysql://localhost:3306/skill?characterEncoding=UTF-8&serverTimezone=GMT%2B8
username: root
password: 123456
redis:
host: localhost
port: 6379

4.3. 实体

package com.gblfy.entity;


import javax.persistence.*;
import java.io.Serializable;
import java.math.BigDecimal;

@Entity
@Table(name = "skill_goods")
@Data
public class SkillGood implements Serializable {
public SkillGood() {
}
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

/**
* 标题
*/
@Column(name = "name")
private String name;

/**
* 原价格
*/
@Column(name = "price")
private BigDecimal price;

/**
* 秒杀价格
*/
@Column(name = "cost_price")
private BigDecimal costPrice;
/**
* 审核状态
*/
@Column(name = "status")
private String status;


/**
* 秒杀商品数
*/
@Column(name = "num")
private Integer num;

/**
* 剩余库存数
*/
@Column(name = "stock_count")
private Integer stockCount;

/**
* 描述
*/
@Column(name = "introduction")
private String introduction;

private static final long serialVersionUID = 1L;

package com.gblfy.entity;

import javax.persistence.*;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;

@Entity
@Table(name = "skill_order")
@Data
public class SkillOrder implements Serializable {
/**
* 主键
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

/**
* 秒杀商品ID
*/
@Column(name = "skill_id")
private Long skillId;

/**
* 支付金额
*/
@Column(name = "money")
private BigDecimal money;

/**
* 用户
*/
@Column(name = "user_id")
private String userId;
/**
* 创建时间
*/
@Column(name = "create_time")
private Date createTime;

/**
* 支付时间
*/
@Column(name = "pay_time")
private Date payTime;

/**
* 状态
*/
@Column(name = "status")
private String status;

private static final long serialVersionUID = 1L;

4.4. 接口

package com.gblfy.dao;

import com.gblfy.entity.SkillOrder;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface SkillOrderRepository extends JpaRepository<SkillOrder,Long> {
}

4.5. service

package com.gblfy.service;

import com.gblfy.entity.SkillGood;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Service
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class ProductService {

private final RestTemplate restTemplate;

public SkillGood getGoodById(Long productId) {
return restTemplate.getForObject("http://product-serv/product/" + productId, SkillGood.class);
}

public void update(SkillGood skillGood) {
ResponseEntity<String> result= restTemplate.postForEntity("http://product-serv/product/",skillGood,String.class);
System.out.println(result.getBody());
}
}

package com.gblfy.service;

import com.gblfy.dao.SkillOrderRepository;
import com.gblfy.entity.SkillGood;
import com.gblfy.entity.SkillOrder;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;
import java.util.Date;

@Service
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class SkillGoodService {
public static final String SKILL_GOODS_PHONE = "SKILL_GOODS_PHONE";

private final RedisTemplate redisTemplate;
private final SkillOrderRepository skillOrderRepository;
private final ProductService productService;


@Transactional
public void add(Long productId, String userId) throws Exception {

SkillGood skillGood = productService.getGoodById(productId);
if (skillGood == null) {
throw new Exception("商品已经被抢光拉");
}
if (skillGood.getStockCount() > 0) {
SkillOrder skillOrder = new SkillOrder();
skillOrder.setMoney(skillGood.getCostPrice());
skillOrder.setPayTime(new Date());
skillOrder.setStatus("0");
skillOrder.setUserId(userId);
skillOrder.setCreateTime(new Date());
skillOrder.setSkillId(productId);
skillOrderRepository.save(skillOrder);
skillGood.setStockCount(skillGood.getStockCount() - 1);
redisTemplate.boundHashOps(SKILL_GOODS_PHONE).put(skillGood.getId(), skillGood);
System.out.println("成功秒杀 剩余库存:" + skillGood.getStockCount());
}
if (skillGood.getStockCount() <= 0) {
System.out.println("库存已经是负数了:" + skillGood.getStockCount());
redisTemplate.boundHashOps(SKILL_GOODS_PHONE).delete(skillGood.getId());
productService.update(skillGood);
}
}
}

4.6. controller

package com.gblfy.controller;

import com.gblfy.service.SkillGoodService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class SkillController {

@Autowired
private SkillGoodService skillGoodService;

@GetMapping("/skill")
public String add(Long productId,String userId) {
try{
skillGoodService.add(productId,userId);
return "抢单成功";
}catch (Exception e){
return "商品已经抢光";
}
}
}

4.7. 启动类

package com.gblfy;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@EnableDiscoveryClient
public class SkillServApplication {

public static void main(String[] args) {
SpringApplication.run(SkillServApplication.class, args);
}

@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
//采用普通的key 为 字符串
template.setKeySerializer(new StringRedisSerializer());
return template;
}

@Bean
@LoadBalanced
public RestTemplate create() {
return new RestTemplate();
}
}


举报

相关推荐

0 条评论