Java项目:支持并发的秒杀项目(基于Redis)

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 Tair(兼容Redis),内存型 2GB
简介: Java项目:支持并发的秒杀项目(基于Redis)

高并发秒杀系统

分析需求

场景分析

  • 秒杀时大量用户会在同一时间同时进行抢购,网站瞬时访问流量激增。
  • 秒杀一般是访问请求数量远远大于库存数量,只有少部分用户能够秒杀成功。
  • 秒杀业务流程比较简单,一般就是下订单减库存。

问题分析

秒杀系统一般要注意的问题就是 :

  1. 库存少卖,超卖问题(原子性)
  2. 流量削峰,这里我们设定的时候每个用户只能秒杀一次所以比较好处理

执行流程

  • 初始化数据,提前预热要秒杀的商品(项目里设置为启动,如果秒杀列表有就预热)
  • 使用 redis 缓存秒杀的商品信息,使用redis来承担秒杀的压力最后生产秒杀到的用户,再到mysql生成订单
  • 在秒杀时使用(事务,分布式锁两种方式都实现)对商品库存,保证原子性

设计思路图

秒杀系统

技术选型

开发语言 : Java 8 或 Java 11

框架技术: SpringBoot2.x ,Mybatis-plus ,Thymeleaf

中间件: Redis

数据库:MySQL 8.0

数据源: druid 1.16

测试工具: apache jmeter

数据库表设计

三张表,分别是

  1. 商品表:  id 商品id  商品name  商品图片  商品类别  商品价格  库存
  2. 秒杀商品表 : id 商品id  秒杀开始时间  秒杀结束时间  秒杀价  可秒杀的数量
  3. 订单表  id 订单id 商品id  秒杀价格   用户id  地址 电话

sql表

CREATE DATABASE /*!32312 IF NOT EXISTS*/`seckill` /*!40100 DEFAULT CHARACTER SET utf8 */ 
USE `seckill`;
DROP TABLE IF EXISTS `goods`;
CREATE TABLE `goods` (
  `id` int NOT NULL AUTO_INCREMENT,
  `goods_id` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '商品id',
  `goods_name` varchar(100) DEFAULT NULL COMMENT '商品名字',
  `goodtype` varchar(100) DEFAULT NULL COMMENT '商品类别',
  `price` double DEFAULT NULL COMMENT '商品价格',
  `img_path` varchar(200) DEFAULT NULL COMMENT '商品图片地址',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb3;
/*Data for the table `goods` */
insert  into `goods`(`id`,`goods_id`,`goods_name`,`goodtype`,`price`,`img_path`) values 
(5,'1001','华为手机','电子用品',5000,'/img/huaweiphone.jpg');
/*Table structure for table `goodsorder` */
DROP TABLE IF EXISTS `goodsorder`;
CREATE TABLE `goodsorder` (
  `id` int NOT NULL AUTO_INCREMENT,
  `order_id` varchar(100) DEFAULT NULL,
  `user_id` varchar(100) DEFAULT NULL,
  `goods_id` varchar(100) DEFAULT NULL,
  `telephone` varchar(20) DEFAULT NULL,
  `address` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb3;
/*Data for the table `goodsorder` */
/*Table structure for table `seckillgoods` */
DROP TABLE IF EXISTS `seckillgoods`;
CREATE TABLE `seckillgoods` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `goods_id` varchar(100) DEFAULT NULL,
  `seckill_price` double DEFAULT NULL COMMENT '秒杀价格',
  `stock_num` int DEFAULT NULL COMMENT '秒杀库存',
  `start_time` datetime DEFAULT NULL COMMENT '开始时间',
  `end_time` datetime DEFAULT NULL COMMENT '结束时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb3;
/*Data for the table `seckillgoods` */
insert  into `seckillgoods`(`id`,`goods_id`,`seckill_price`,`stock_num`,`start_time`,`end_time`) values 
(1,'1001',3599,10,'2022-03-07 00:00:00','2022-03-09 00:00:00');

项目配置文件

application.yml

spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://localhost:3306/seckill?    userSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
    driver-class-name: com.mysql.cj.jdbc.Driver
  redis:
    database: 0
    host: 120.79.14.203
    port: 6379
    password: root
    timeout: 1000
  thymeleaf:
    cache: false
    prefix: classpath:/templates/
    suffix: .html
mybatis-plus:
  configuration:
    map-underscore-to-camel-case: false
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  #  configuration:
  #    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  mapper-locations: classpath:mapper/*

redis 配置类

@Configuration
public class RedisConfig {
    /**
     * @author 冷环渊 Doomwatcher
     * @context:redis 存储数据 序列化方式
     * @date: 2022/3/7 21:19
     * @param redisConnectionFactory
     * @return: org.springframework.data.redis.core.RedisTemplate<java.lang.String, java.lang.Object>
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory
                                                               redisConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        //声明 key 和 value 的序列化方式
        StringRedisSerializer keySerializer = new StringRedisSerializer();
        GenericFastJsonRedisSerializer valueSerializer = new GenericFastJsonRedisSerializer();
        redisTemplate.setKeySerializer(keySerializer);
        redisTemplate.setValueSerializer(valueSerializer);
        redisTemplate.setHashKeySerializer(keySerializer);
        redisTemplate.setValueSerializer(valueSerializer);
        //多种的序列化方式 最好是谁序列化的谁处理
        //GenericFastJsonRedisSerializer
        //Jackson2JsonRedisSerializer
        return redisTemplate;
    }
}

redis工具类

package com.hyc.seckillsystem.util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
 * @projectName: seckillsystem
 * @package: com.hyc.seckillsystem.util
 * @className: RedisUtil
 * @author: 冷环渊 doomwatcher
 * @description: TODO
 * @date: 2022/3/7 21:25
 * @version: 1.0
 */
@Component
public class RedisUtil {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    //    设置缓存
    public void set(String key, Object value) {
        redisTemplate.opsForValue().set(key, value);
    }
//设置缓存和超时时间
    public void set(String key, Object value, long timeout) {
        redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
    }
    //获取命中key 的内容
    public Object get(String key) {
        if (!redisTemplate.hasKey(key)) {
            return null;
        }
        return redisTemplate.opsForValue().get(key);
    }
    //递减操作
    public void decr(String key) {
        redisTemplate.opsForValue().decrement(key);
    }
    //触发 提交 参数里的sessionCallback 队列里的命令
    public Object execute(SessionCallback sessionCallback) {
        return redisTemplate.execute(sessionCallback);
    }
    // 使用脚本语言
    public Object execute(RedisScript script, List keylist) {
        return redisTemplate.execute(script, keylist);
    }
// 是否存在 key
    public boolean hasKey(String key) {
        return redisTemplate.hasKey(key);
    }
}

商品列表

商品列表界面,是比较简单的,设计思路还是基于传统的三层开发

Controller -> servier -> dao这样的分层开发模式

实体类

这里为了方面展示,就展示实体类的属性,小伙伴们自行补充getter/setter以及构造方法等

public class goods {
    @Id
    @TableId(type = IdType.AUTO)
    private Integer id;
    private String goods_id;
    private String goods_name;
    private String goodtype;
    private double price;
    private String img_path;
    @TableField(exist = false)
    private SeckillGoods seckillgoods;
}

DAO

虽然使用了 mybatis-plus 但是我们还是根据自己的查询需求新增了自己的方法,

@Repository
public interface GoodsMapper extends BaseMapper<goods> {
    goods getGoodsByGoodsId(String goodId);
    List<goods> selectGoods();
    List<goods> getGoodList();
}

映射文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hyc.seckillsystem.dao.GoodsMapper">
    <select id="getGoodsByGoodsId" resultType="com.hyc.seckillsystem.pojo.goods">
        select seckill.goods.goods_id,
               seckill.goods.goods_name,
               seckill.goods.goodtype,
               seckill.goods.price,
               seckill.goods.img_path
        from seckill.goods
        where goods_id = #{goodId};
    </select>
    <select id="getGoodList" resultType="com.hyc.seckillsystem.pojo.goods">
        select seckill.goods.goods_id,
               seckill.goods.goods_name,
               seckill.goods.goodtype,
               seckill.goods.price,
               seckill.goods.img_path
        from seckill.goods
    </select>
    <resultMap id="goodsResult" type="com.hyc.seckillsystem.pojo.goods">
        <id property="id" column="id"/>
        <result property="goods_id" column="goods_id"/>
        <result property="goods_name" column="goods_name"/>
        <result property="goodtype" column="goods_type"/>
        <result property="price" column="price"/>
        <result property="img_path" column="img_path"/>
        <association property="seckillgoods" javaType="com.hyc.seckillsystem.pojo.SeckillGoods">
            <result property="seckill_price" column="seckill_price"/>
            <result property="stock_num" column="stock_num"/>
        </association>
    </resultMap>
    <select id="selectGoods" resultMap="goodsResult">
        SELECT a.`goods_id`,
               a.`goods_name`,
               a.`goodtype`,
               a.`price`,
               a.`img_path`,
               b.`seckill_price`,
               b.`stock_num`
        FROM seckill.goods AS a
                 LEFT JOIN seckill.seckillgoods AS b
                           ON a.`goods_id` = b.`goods_id`
    </select>
</mapper>

service 实现类

两种查找商品数据的方法

  1. getlist:逐个表 匹配查询
  2. selectGoods链表查询

返回商品详情(包括秒杀价格等)VO getGoodsDetail(String goodId)

service 层的设计思路就是 调用DAO层接口 实现对数据库中取出数据的处理,并且提供给controller封装好的接口

@Service
public class GoodsServiceImpl implements GoodsService {
    @Autowired
    GoodsMapper goodsMapper;
    @Autowired
    SeckillGoodsMapper seckillGoodsMapper;
    /**
     * @author 冷环渊 Doomwatcher
     * @context:逐个表 匹配查询
     * @date: 2022/3/7 16:20
     * @param
     * @return: java.util.List<com.hyc.seckillsystem.vo.GoodVo>
     */
    @Override
    public List<GoodVo> getlist() {
        /* 两种方式
         * 每个表单独查询
         * 从第一个表查多个数据,再根据数据去第二个表中查询
         * */
        List<goods> goods = goodsMapper.getGoodList();
        ArrayList<GoodVo> result = new ArrayList<>();
        for (goods good : goods) {
            Map<String, Object> map = new HashMap();
            map.put("goods_id", good.getGoods_id());
            SeckillGoods seckillGoods = seckillGoodsMapper.getSeckillByGoodsId(good.getGoods_id());
            GoodVo vo = new GoodVo();
            vo.setGoodsId(good.getGoods_id());
            vo.setGoodsName(good.getGoods_name());
            vo.setGoodType(good.getGoodtype());
            vo.setPrice(good.getPrice());
            vo.setImgPath(good.getImg_path());
            vo.setSeckillPrice(seckillGoods.getSeckill_price());
            vo.setStockNum(seckillGoods.getStock_num());
            result.add(vo);
        }
        return result;
    }
    @Override
    //返回秒杀商品详情页面
    public GoodsDetailVo getGoodsDetail(String goodId) {
        SeckillGoods seckillGoods = seckillGoodsMapper.getSeckillByGoodsId(goodId);
        goods good = goodsMapper.getGoodsByGoodsId(goodId);
        //商品表信息1
        GoodsDetailVo goodsDetailVo = new GoodsDetailVo();
        goodsDetailVo.setGoodsId(good.getGoods_id());
        goodsDetailVo.setGoodsName(good.getGoods_name());
        goodsDetailVo.setGoodType(good.getGoodtype());
        goodsDetailVo.setPrice(good.getPrice());
        goodsDetailVo.setImgPath(good.getImg_path());
        //秒杀表信息
        goodsDetailVo.setSeckillPrice(seckillGoods.getSeckill_price());
        goodsDetailVo.setStockNum(seckillGoods.getStock_num());
        goodsDetailVo.setStartTime(seckillGoods.getStart_time());
        goodsDetailVo.setEndTime(seckillGoods.getEnd_time());
        return goodsDetailVo;
    }
    /**
     * @author 冷环渊 Doomwatcher
     * @context: 联表查询
     * @date: 2022/3/7 16:20
     * @param
     * @return: com.hyc.seckillsystem.vo.GoodVo
     */
    @Override
    public List<GoodVo> selectGoods() {
        List<goods> goods = goodsMapper.selectGoods();
        ArrayList<GoodVo> result = new ArrayList<>();
        for (goods good : goods) {
            GoodVo vo = new GoodVo();
            vo.setGoodsId(good.getGoods_id());
            vo.setGoodsName(good.getGoods_name());
            vo.setGoodType(good.getGoodtype());
            vo.setPrice(good.getPrice());
            vo.setImgPath(good.getImg_path());
            vo.setSeckillPrice(good.getSeckillGoods().getSeckill_price());
            vo.setStockNum(good.getSeckillGoods().getStock_num());
            result.add(vo);
        }
        return result;
    }
}

Controller

Controller层,负责控制接口数据和渲染界面的值传递 , 跳转 , 这里基本上不包含业务代码

尽可能的再service层 封装好方法 让Controller只负责调用

@Controller
@Slf4j
public class GoodsController {
    @Autowired
    GoodsService goodsService;
    @GetMapping(value = "/index")
    public String list(Model model) {
        //    逐层调用
        List<GoodVo> getlist = goodsService.getlist();
        model.addAttribute("goodslist", getlist);
        return "list";
    }
    @GetMapping(value = "/goodDetail/{goodsId}")
    public String getGoodDetail(@PathVariable String goodsId, Model model) {
        GoodsDetailVo goodsDetail = goodsService.getGoodsDetail(goodsId);
        model.addAttribute("GoodsDetail", goodsDetail);
        //获取到时间
        Date startTime = goodsDetail.getStartTime();
        Date endTime = goodsDetail.getEndTime();
        Date nowtime = new Date();
        /*设置状态值
         * 0 未开始
         * 1 正在秒杀
         * 2 秒杀结束
         * */
        int status;
        int remainSeconds = -1;
        if (nowtime.before(startTime)) {
            //    秒杀未开始
            status = 0;
            remainSeconds = (int) ((startTime.getTime() - nowtime.getTime()) / 1000);
        } else if (nowtime.after(endTime)) {
            //秒杀已经结束
            status = 2;
        } else {
            //正在秒杀中
            status = 1;
        }
        model.addAttribute("status", status);
        model.addAttribute("remainSeconds", remainSeconds);
        return "Detail";
    }
}

秒杀商品

这里才是我们的重头戏这里我们主要讲解使用思路,不过多的去展示无用代码如实体类等,我们这里从最开始的

  1. 直接处理
  2. redis 事务处理
  3. 分布式锁 Lua处理

三种方式 由浅至深的来理解秒杀的思路和超卖问题的解决

直接处理

  • 判断用户id 的有效性 我们没有用户
  • 判断goodsid的有效性
  • 判断当前是否处于可以秒杀的状态
  • 判断是否有剩余库存
  • 判断用户的秒杀权限(是否秒杀过)
  • 减少库存
  • 生成新的订单
public String handle(String userId, String goodsId) {
    String stockkey = goodsId + "_count";
    String startTimekey = goodsId + "_startTime";
    String userkey = goodsId + "_" + userId;
    String dateStr = (String) redisUtil.get(startTimekey);
    SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    Date startTime = null;
    try {
        startTime = format.parse(dateStr);
    } catch (ParseException e) {
        e.printStackTrace();
    }
    if (startTime == null || new Date().before(startTime)) {
        return "秒杀还没开始";
    }
    int stockNum = (int) redisUtil.get(stockkey);
    if (stockNum <= 0) {
        return "秒杀一空";
    }
    if (redisUtil.get(userkey) != null) {
        return "用户秒杀成功过了";
    }
    //减少库存
    redisUtil.decr(stockkey);
    //限额 1 可以这么处理
    //如果限额 多个 可以获取value 自增
    redisUtil.set(goodsId + "_" + userId, 1);
    return userId + "用户秒杀成功";
}

测试

这个时候启动 jmeter 来测试一下接口的结果

  • 库存 10
  • 一百个线程 抢这 10个 手机

查看 redis 中 库存 key 的数量 为 -4

再次测试

通过测试和查看日志可以看到,我们直接处理,系统不行不能在第一时间反应过来是否超过了库存,导致特价商品被超额卖出,那这个问题怎么解决呢?

事务处理

优秀成熟的数据库 一定会有对事务的支持, redis 也不例外

Redis的事务在不出现异常的时候是原子操作,exec是触发事务执行的命令

相关命令:

  • watch  设置一个key 表示开始对这个key的命令监听
  • multi   将 后续的命令 加入到执行队列里
  • exec  出发执行队列里的命令
  • discard 停止提交,redis不能回滚

使用事务

public String handleByTrans(String userId, String goodsId) {
        String stockkey = goodsId + "_count";
        String startTimekey = goodsId + "_startTime";
        String userkey = goodsId + "_" + userId;
        String dateStr = (String) redisUtil.get(startTimekey);
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date startTime = null;
        try {
            startTime = format.parse(dateStr);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        if (startTime == null || new Date().before(startTime)) {
            return "秒杀还没开始";
        }
        if (redisUtil.get(userkey) != null) {
            return "用户秒杀成功过了";
        }
        SessionCallback sessionCallback = new SessionCallback() {
            @Override
            public Object execute(RedisOperations redisOperations) throws DataAccessException {
                // 编写事务逻辑
                // 调用 watch 开始事务, mutil 开始将之后的命令加入队列 exec 执行队列里的命令
                redisOperations.watch(stockkey);
                int stockNum = (int) redisUtil.get(stockkey);
                if (stockNum <= 0) {
                    return "秒杀一空";
                }
                redisOperations.multi();
                redisUtil.decr(stockkey);
                redisUtil.set(userkey, 1);
                return redisOperations.exec();
            }
        };
        redisUtil.execute(sessionCallback);
        if (redisUtil.hasKey(userkey)) {
            return userId;
        }
        return userId + "秒杀失败";
    }

测试

和之前一样的案例 100 抢 10

可以发现使用了 事务之后我们解决了超卖的问题

举一反三

那除了事务 还有什么方式可以解决超卖问题呢?

脚本语言 : lua

redis是支持执行脚本语言的且一段脚本执行的时候是不会被其他操作影响的,保证原子性

编写lua 脚本

--接受到参数1
local userId = KEYS[1];
--接收到参数2
local goodsId = KEYS[2];
--redis中的 key 存储为 局部变量
--秒杀商品数量
--.. 是lua 里面字符串的拼接
local cntkey = goodsId .. "_count";
--秒杀用户 id
local orderkey = goodsId .. "_" .. userId;
--判断是否秒杀过
local orderExists = redis.call("get", orderkey);
if (orderExists and tonumber(orderExists) == 1) then
    return 2;
end
--判断库存是否为空
local num = redis.call("get", cntkey);
if (num and tonumber(num) <= 0) then
    return 0;
else
    --    秒杀成功
    redis.call("decr", cntkey);
    redis.call("set", orderkey, 1);
end
return 1

实例代码

我们可以使用 DefaultRedisScript 类 来执行我们写在 resouce 下的lua脚本

script.setScriptSource(new ResourceScriptSource( new ClassPathResource("lua/sekill.lua")));

设置脚本的位置,之后用之前工具类封装的redis方法将脚本和需要的参数list 传入执行即可

//使用lua 脚本 保证原子性 (分布式锁)
    @Override
    public String handleByLua(String userId, String goodsId) {
        String startTimekey = goodsId + "_startTime";
        String dateStr = (String) redisUtil.get(startTimekey);
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date startTime = null;
        try {
            startTime = format.parse(dateStr);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        if (startTime == null || new Date().before(startTime)) {
            return "秒杀还没开始";
        }
        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        //设置返回脚本类型
        script.setResultType(Long.class);
        script.setScriptSource(new ResourceScriptSource(
                new ClassPathResource("lua/sekill.lua")));
        List<String> keylist = new ArrayList<>();
        keylist.add(userId);
        keylist.add(goodsId);
        Object execute = redisUtil.execute(script, keylist);
        String resultStr = String.valueOf(execute);
        if ("0".equals(resultStr)) {
            return "秒杀库存已经空了";
        } else if ("1".equals(resultStr)) {
            return userId;
        } else if ("2".equals(resultStr)) {
            return "该用户秒杀过了 =>" + userId;
        } else {
            return "秒杀和抢购可能出了点问题,正在紧急维护";
        }
    }

测试

同样的 100 -》 10 ,从结果来看,也是保证了不超卖,不少卖的

解决了秒杀的问题,剩下就是将秒杀之后的成功用户信息和商品信息传给订单模块即可

Controller

@Controller
@Slf4j
public class redisController {
    @Autowired
    redisService redisService;
    @Autowired
    RedisUtil redisUtil;
    // 为了 方面测试 每次重启项目都会重置秒杀次数
    @PostConstruct
    public void initdata() {
        redisService.initdata();
    }
    @PostMapping(value = "/seckillAPI")
    public String seckill(String userId, String goodsId, Model model) {
        String key = goodsId + "_" + userId;
        if (userId == null || goodsId == null) {
            return "参数异常";
        }
        String result = redisService.seckill(userId, goodsId);
        if (redisUtil.hasKey(key)) {
            log.info(result);
            model.addAttribute("userId", result);
            model.addAttribute("goodsId", goodsId);
        }
        return "order";
    }
}

生成订单

解决了秒杀的问题,生成订单其实就简单和常规的不少

就是查询 必要的信息生成一个订单信息

service 实现类

@Service
public class orderServiceImpl implements orderService {
    @Autowired
    GoodsMapper goodsMapper;
    @Autowired
    SeckillGoodsMapper seckillGoodsMapper;
    @Autowired
    orderMapper ordermapper;
    @Override
    public orderVo createOrder(order order) {
        int insert = ordermapper.insert(order);
        if (insert != 0) {
            List<order> orders = ordermapper.selectList(null);
            for (order o : orders) {
                goods goods = goodsMapper.getGoodsByGoodsId(o.getGoods_id());
                SeckillGoods seckillGoods = seckillGoodsMapper.getSeckillByGoodsId(o.getGoods_id());
                orderVo orderVo = new orderVo();
                orderVo.setOrderId(String.valueOf(o.getId()));
                orderVo.setUserId(o.getUser_id());
                orderVo.setGoodsId(o.getGoods_id());
                orderVo.setTelephone(o.getTelephone());
                orderVo.setAddress(o.getAddress());
                orderVo.setImgPath(goods.getImg_path());
                orderVo.setSeckillPrice(seckillGoods.getSeckill_price());
                return orderVo;
            }
        }
        return null;
    }
}

Controller

等待用户 确认信息之后 就可以生成订单 同步到数据库了

@Controller
public class orderController {
    @Autowired
    orderService orderService;
    @PostMapping(value = "/create")
    public String createOrder(order order, Model model) {
        orderVo order1 = orderService.createOrder(order);
        model.addAttribute("orderinfo", order1);
        return "success";
    }
}

流程展示

总结

设计一个秒杀项目 其实要考虑的东西十分的多,我们这次的系统也不是最终的版本,先做出来的核心的,

套用鱼皮的话 先有 再调优 追求更好

拓展

  • 页面动静分离
  • nginx ip 分流
  • MQ 流量削峰,异步任务
  • 前端验证码
  • 数据库与缓存同步策略(MQ redis 都可以实现)

上述都是我学过的知识点暂时并且实战过,会在今后不断的将优化功能实现出来

这些都是我在考虑的范围内,今后会不断的学习 晚上其中的内容,

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
6天前
|
缓存 NoSQL 关系型数据库
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
本文详解缓存雪崩、缓存穿透、缓存并发及缓存预热等问题,提供高可用解决方案,帮助你在大厂面试和实际工作中应对这些常见并发场景。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
|
3天前
|
Java Android开发
Eclipse 创建 Java 项目
Eclipse 创建 Java 项目
17 4
|
6天前
|
NoSQL Java API
springboot项目Redis统计在线用户
通过本文的介绍,您可以在Spring Boot项目中使用Redis实现在线用户统计。通过合理配置Redis和实现用户登录、注销及统计逻辑,您可以高效地管理在线用户。希望本文的详细解释和代码示例能帮助您在实际项目中成功应用这一技术。
15 3
|
8天前
|
SQL Java 数据库连接
从理论到实践:Hibernate与JPA在Java项目中的实际应用
本文介绍了Java持久层框架Hibernate和JPA的基本概念及其在具体项目中的应用。通过一个在线书店系统的实例,展示了如何使用@Entity注解定义实体类、通过Spring Data JPA定义仓库接口、在服务层调用方法进行数据库操作,以及使用JPQL编写自定义查询和管理事务。这些技术不仅简化了数据库操作,还显著提升了开发效率。
20 3
|
7天前
|
存储 消息中间件 NoSQL
使用Java操作Redis数据类型的详解指南
通过使用Jedis库,可以在Java中方便地操作Redis的各种数据类型。本文详细介绍了字符串、哈希、列表、集合和有序集合的基本操作及其对应的Java实现。这些示例展示了如何使用Java与Redis进行交互,为开发高效的Redis客户端应用程序提供了基础。希望本文的指南能帮助您更好地理解和使用Redis,提升应用程序的性能和可靠性。
22 1
|
11天前
|
存储 设计模式 分布式计算
Java中的多线程编程:并发与并行的深度解析####
在当今软件开发领域,多线程编程已成为提升应用性能、响应速度及资源利用率的关键手段之一。本文将深入探讨Java平台上的多线程机制,从基础概念到高级应用,全面解析并发与并行编程的核心理念、实现方式及其在实际项目中的应用策略。不同于常规摘要的简洁概述,本文旨在通过详尽的技术剖析,为读者构建一个系统化的多线程知识框架,辅以生动实例,让抽象概念具体化,复杂问题简单化。 ####
|
11天前
|
前端开发 Java 数据库
如何实现一个项目,小白做项目-java
本教程涵盖了从数据库到AJAX的多个知识点,并详细介绍了项目实现过程,包括静态页面分析、数据库创建、项目结构搭建、JSP转换及各层代码编写。最后,通过通用分页和优化Servlet来提升代码质量。
28 1
|
16天前
|
Java 数据库连接 数据库
如何构建高效稳定的Java数据库连接池,涵盖连接池配置、并发控制和异常处理等方面
本文介绍了如何构建高效稳定的Java数据库连接池,涵盖连接池配置、并发控制和异常处理等方面。通过合理配置初始连接数、最大连接数和空闲连接超时时间,确保系统性能和稳定性。文章还探讨了同步阻塞、异步回调和信号量等并发控制策略,并提供了异常处理的最佳实践。最后,给出了一个简单的连接池示例代码,并推荐使用成熟的连接池框架(如HikariCP、C3P0)以简化开发。
36 2
|
18天前
|
JavaScript Java 项目管理
Java毕设学习 基于SpringBoot + Vue 的医院管理系统 持续给大家寻找Java毕设学习项目(附源码)
基于SpringBoot + Vue的医院管理系统,涵盖医院、患者、挂号、药物、检查、病床、排班管理和数据分析等功能。开发工具为IDEA和HBuilder X,环境需配置jdk8、Node.js14、MySQL8。文末提供源码下载链接。
|
20天前
|
存储 NoSQL Java
Java 使用 Redis
10月更文挑战第22天
25 0