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

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介: 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
目录
相关文章
|
2天前
|
数据采集 存储 Java
高德地图爬虫实践:Java多线程并发处理策略
高德地图爬虫实践:Java多线程并发处理策略
|
2天前
|
搜索推荐 前端开发 Java
java医院绩效考核管理系统项目源码
系统需要和his系统进行对接,按照设定周期,从his系统获取医院科室和医生、护士、其他人员工作量,对没有录入信息化系统的工作量,绩效考核系统设有手工录入功能(可以批量导入),对获取的数据系统按照设定的公式进行汇算,且设置审核机制,可以退回修正,系统功能强大,完全模拟医院实际绩效核算过程,且每步核算都可以进行调整和参数设置,能适应医院多种绩效核算方式。
4 0
|
3天前
|
Java API 调度
[Java并发基础]多进程编程
[Java并发基础]多进程编程
|
3天前
|
前端开发 Java 测试技术
Java从入门到精通:4.1.1参与实际项目,锻炼编程与问题解决能力
Java从入门到精通:4.1.1参与实际项目,锻炼编程与问题解决能力
|
8天前
|
安全 Java
深入理解 Java 多线程和并发工具类
【4月更文挑战第19天】本文探讨了Java多线程和并发工具类在实现高性能应用程序中的关键作用。通过继承`Thread`或实现`Runnable`创建线程,利用`Executors`管理线程池,以及使用`Semaphore`、`CountDownLatch`和`CyclicBarrier`进行线程同步。保证线程安全、实现线程协作和性能调优(如设置线程池大小、避免不必要同步)是重要环节。理解并恰当运用这些工具能提升程序效率和可靠性。
|
9天前
|
人工智能 前端开发 Java
Java语言开发的AI智慧导诊系统源码springboot+redis 3D互联网智导诊系统源码
智慧导诊解决盲目就诊问题,减轻分诊工作压力。降低挂错号比例,优化就诊流程,有效提高线上线下医疗机构接诊效率。可通过人体画像选择症状部位,了解对应病症信息和推荐就医科室。
150 10
|
10天前
|
Java 开发者
Java中多线程并发控制的实现与优化
【4月更文挑战第17天】 在现代软件开发中,多线程编程已成为提升应用性能和响应能力的关键手段。特别是在Java语言中,由于其平台无关性和强大的运行时环境,多线程技术的应用尤为广泛。本文将深入探讨Java多线程的并发控制机制,包括基本的同步方法、死锁问题以及高级并发工具如java.util.concurrent包的使用。通过分析多线程环境下的竞态条件、资源争夺和线程协调问题,我们提出了一系列实现和优化策略,旨在帮助开发者构建更加健壮、高效的多线程应用。
7 0
|
10天前
|
存储 缓存 安全
Java并发基础之互斥同步、非阻塞同步、指令重排与volatile
在Java中,多线程编程常常涉及到共享数据的访问,这时候就需要考虑线程安全问题。Java提供了多种机制来实现线程安全,其中包括互斥同步(Mutex Synchronization)、非阻塞同步(Non-blocking Synchronization)、以及volatile关键字等。 互斥同步(Mutex Synchronization) 互斥同步是一种基本的同步手段,它要求在任何时刻,只有一个线程可以执行某个方法或某个代码块,其他线程必须等待。Java中的synchronized关键字就是实现互斥同步的常用手段。当一个线程进入一个synchronized方法或代码块时,它需要先获得锁,如果
24 0
|
11天前
|
缓存 NoSQL Java
使用Redis进行Java缓存策略设计
【4月更文挑战第16天】在高并发Java应用中,Redis作为缓存中间件提升性能。本文探讨如何使用Redis设计缓存策略。Redis是开源内存数据结构存储系统,支持多种数据结构。Java中常用Redis客户端有Jedis和Lettuce。缓存设计遵循一致性、失效、雪崩、穿透和预热原则。常见缓存模式包括Cache-Aside、Read-Through、Write-Through和Write-Behind。示例展示了使用Jedis实现Cache-Aside模式。优化策略包括分布式锁、缓存预热、随机过期时间、限流和降级,以应对缓存挑战。
|
16天前
|
运维 NoSQL 算法
Java开发-深入理解Redis Cluster的工作原理
综上所述,Redis Cluster通过数据分片、节点发现、主从复制、数据迁移、故障检测和客户端路由等机制,实现了一个分布式的、高可用的Redis解决方案。它允许数据分布在多个节点上,提供了自动故障转移和读写分离的功能,适用于需要大规模、高性能、高可用性的应用场景。
16 0

热门文章

最新文章