SpringBoot简单设计一个秒杀系统流程

SpringBoot简单设计一个秒杀系统流程


一、秒杀系统

1.1 秒杀的场景

  1. 淘宝限量抢购商品
  2. 火车票(12306系统)

1.2 保护的方法

  1. 乐观锁防止超卖
  2. 令牌桶限流
  3. Redis 缓存
  4. MD5加密,隐藏秒杀接口

二、防止超卖

1.数据库的设计

创建两张表 商品信息stock 和 stock_order 订单表

DROP TABLE IF EXISTS `stock`;
CREATE TABLE `stock` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL COMMENT '名称',
  `count` int(11) NOT NULL COMMENT '库存',
  `sale` int(11) NOT NULL COMMENT '已售',
  `version` int(11) NOT NULL COMMENT '乐观锁,版本号',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `stock_order`;
CREATE TABLE `stock_order` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `sid` int(11) NOT NULL COMMENT '库存ID',
  `name` varchar(255) NOT NULL COMMENT '商品名称',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=596 DEFAULT CHARSET=utf8;

2.pojo实体层

Order 订单

@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class Order {
    private Integer id;
    private Integer sid;
    private String name;
    private Date createTime;
}

Stock 商品信息

@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class Stock {

    private Integer id;
    private String name;
    private Integer count;
    private Integer sale;
    private Integer version;
}

3.Mapper.xml

StockMapper.xml 商品信息

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xizi.miaosha.mapper.StockMapper">

    <select id="checkStock" parameterType="INT" resultType="com.xizi.miaosha.pojo.Stock">
        select id,name,count,sale,version from stock
        where id=#{id}
    </select>
    
    <update id="updateStock"   parameterType="com.xizi.miaosha.pojo.Stock" >
        update stock set
        sale=#{sale}
        where
          id=#{id}
    </update>
</mapper>

Ordermapper.xml 创建订单

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xizi.miaosha.mapper.OrderMapper">

    <insert id="createOrder" parameterType="com.xizi.miaosha.pojo.Order" useGeneratedKeys="true" keyProperty="id">
        insert into stock_order values (#{id},#{sid},#{name},#{createTime});
    </insert>

</mapper>

4.Mapper层

订单的mapper

package com.xizi.miaosha.mapper;

import com.xizi.miaosha.pojo.Order;
import org.springframework.stereotype.Repository;

@Repository
public interface OrderMapper {
    /*
        创建订单
     */
    int createOrder(Order order);
}

商品信息的mapper

package com.xizi.miaosha.mapper;

import com.xizi.miaosha.pojo.Stock;
import org.springframework.stereotype.Repository;

@Repository
public interface StockMapper {

    //根据商品id查询库存信息的方法
    Stock checkStock(Integer id);

    /*
    根据商品扣除库存
 */
    int updateStock(Stock stock);
}

5.Service层

OrderService接口

package com.xizi.miaosha.service;


/**
 * 订单业务
 */
public interface OrderService {
    /*
    处理秒杀的下单方法
     */
     Integer kill(Integer id);
}

OrderServiceImpl 实现类

package com.xizi.miaosha.service.impl;

@Service
@Transactional
@Slf4j
public class OrderServiceImpl implements OrderService {
    
    @Autowired
    private StockMapper stockMapper;

    @Autowired
    private OrderMapper orderMapper;

    @Override
    public  Integer  kill(Integer id) {
        //根据商品id效验ku库存
        Stock stock = checkStock(id);
        //扣除库存
        updateSale(stock);
            //创建订单
        Integer orderId = createOrder(stock);
        return orderId;
    }


    //效验库存
    private Stock checkStock(Integer id){
        Stock stock = stockMapper.checkStock(id);
        if(stock.getSale().equals(stock.getCount())){
            throw new RuntimeException("库存不足!!!");
        }
        return stock;
    }
    //扣除库存
    //在业务层进行商品售卖+1操作
    private void updateSale(Stock stock){
        stock.setSale(stock.getSale()+1);
        int updateRows = stockMapper.updateStock(stock);
        if(updateRows==0){
            throw new RuntimeException("抢购失败,请重试!!!");
        }
    }

    //创建订单
    private Integer createOrder(Stock stock){
        Order order = new Order();
        order.setSid(stock.getId()).setName(stock.getName()).setCreateTime(new Date());
        orderMapper.createOrder(order);
        return order.getId();
    }
}

6.Controller层

@RestController
@RequestMapping("/stock")
@Slf4j
public class StockController {

    @Autowired
    private OrderService orderService;
 
    //开发秒杀方法  
    @RequestMapping(value = "/kill0",method = RequestMethod.GET)
    public  String kill0(Integer id){
        try {
            System.out.println("秒杀商品的id: "+id);
            //根据秒杀的商品id 去调用秒杀业务
            Integer orderId = orderService.kill(id);
            return "秒杀成功,订单id为:"+orderId;
        } catch (Exception e) {
            e.printStackTrace();
            return e.getMessage();
        }
    }
 }

7.正常测试

在这里插入图片描述

正常测试没有问题

在这里插入图片描述

8.使用Jmeter进行压力测试

配置当前接口 配置了2000个线程数

  • jmeter压力测试 jmeter -n -t [文件地址]

在这里插入图片描述

出现超卖现象 卖出了143 大于实际的20

在这里插入图片描述

在这里插入图片描述

9.解决超卖现象

1.第一种:悲观锁 加同步代码块 效率低

    //开发秒杀方法  使用悲观锁防止超卖
    @RequestMapping(value = "/kill0",method = RequestMethod.GET)
    public  String kill0(Integer id){
        try {
            //悲观锁  同步代码块 同步执行 效率降低
            //保证当前线程得执行比事务大
          	synchronized(this){
            System.out.println("秒杀商品的id: "+id);
            //根据秒杀的商品id 去调用秒杀业务
            Integer orderId = orderService.kill(id);
            return "秒杀成功,订单id为:"+orderId;
            	}

        } catch (Exception e) {
            e.printStackTrace();
            return e.getMessage();
        }
    }

Jmeter测试 2000线程数 解决商品超卖

在这里插入图片描述


注意事项

如果使用synchronized方法进行同步处理 该业务上使用了注解 @Transactional 会出现超卖问题异常问题 @Transactional 事务注解带有同步得功能 当前事务同步能力大于 synchronized方法

2. 第二种:乐观锁 Version版本 效率高

实际上是把主要防止超卖问题交给数据库解决,利用数据库中定义的version字段以及数据库中的事务实现在并发情况下商品的超卖问题


在OrderServiceImpl 修改扣除库存方法

    //扣除库存
    private void updateSale(Stock stock){
        //在sql层面完成销量的+1 和版本号的+1  并且根据商品id和版本号同时查询更新的商品
        int updateRows = stockMapper.updateStock(stock);
        if(updateRows==0){
            throw new RuntimeException("抢购失败,请重试!!!");
        }
    }

修改StockMapper.xml 更新库存方法

<update id="updateStock"   parameterType="com.xizi.miaosha.pojo.Stock" >
    update stock set
    sale=sale+1 ,
    version=version+1
    where
    id=#{id} and
    version=#{version}
</update>

添加新的接口进行测试

    //开发秒杀方法  使用乐观锁防止超卖
    @RequestMapping(value = "/kill",method = RequestMethod.GET)
    public  String kill(Integer id){
        try {
            //悲观锁  同步代码块 同步执行 效率降低
            //保证当前线程得执行比事务大
//            synchronized(this){
            System.out.println("秒杀商品的id: "+id);
                //根据秒杀的商品id 去调用秒杀业务
                Integer orderId = orderService.kill(id);
                return "秒杀成功,订单id为:"+orderId;
//            }
        } catch (Exception e) {
            e.printStackTrace();
            return e.getMessage();
        }
    }

测试结果 解决商品超卖问题

在这里插入图片描述

三 .接口限流

限流:是对某一时间窗口内的请求数进行限制,保持系统的可用性和稳定性,防止因流量暴增而导致的系统运行缓慢或宕机

  1. 1 接口限流

在面临高并发的抢购请求时,我们如果不对接口进行限流,可能会对后台系统造成极大的压力。大量的请求抢购成功时需要调用下单的接口,过多的请求打到数据库会对系统的稳定性造成影响。

  1. 2 如何解决接口限流

常用的限流算法有令牌桶和和漏桶(漏斗算法),而Google开源项目Guava中的RateLimiter使用的就是令牌桶控制算法。在开发高并发系统时有三把利器用来保护系统:缓存、降级和限流

  • 缓存:缓存的目的是提升系统访问速度和增大系统处理容量
  • 降级:降级是当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务和页面有策略的降级,以此释放服务器资源以保证核心任务的正常运行
  • 限流:限流的目的是通过对并发访问/请求进行限速,或者对一个时间窗口内的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务、排队或等待、降级等处理。
  1. 3 令牌桶和漏斗算法
  • 漏斗算法:漏桶算法思路很简单,水(请求)先进入到漏桶里,漏桶以一定的速度出水,当水流入速度过大会直接溢出,可以看出漏桶算法能强行限制数据的传输速率。
  • 令牌桶算法:最初来源于计算机网络。在网络传输数据时,为了防止网络拥塞,需限制流出网络的流量,使流量以比较均匀的速度向外发送。令牌桶算法就实现了这个功能,可控制发送到网络上数据的数目,并允许突发数据的发送。大小固定的令牌桶可自行以恒定的速率源源不断地产生令牌。如果令牌不被消耗,或者被消耗的速度小于产生的速度,令牌就会不断地增多,直到把桶填满。后面再产生的令牌就会从桶中溢出。最后桶中可以保存的最大令牌数永远不会超过桶的大小。这意味,面对瞬时大流量,该算法可以在短时间内请求拿到大量令牌,而且拿令牌的过程并不是消耗很大的事情。

3.4 令牌桶简单使用

  1. 导入依赖
     <!--google开源工具类 RateLimter令牌桶的实现-->
        <!--使用令牌桶算法导入依赖-->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>28.2-jre</version>
        </dependency>

在Controller层添加方法


   	//创建令牌桶的实例
    //每次令牌桶放行10个请求
    private RateLimiter rateLimiter= RateLimiter.create(10);

    //开发秒杀方法  使用乐观锁防止超卖+令牌桶算法限流
    @RequestMapping(value = "/killtoken",method = RequestMethod.GET)
    public  String killtoken(Integer id){
       		 //1.没有获取到token请求一直知道获取到token 令牌
        	//log.info("等待的时间: "+  rateLimiter.acquire());

            //加入令牌桶的限流措施
 			//2.设置一个等待时间,如果在等待的时间内获取到了token 令牌,则处理业务,如果在等待时间内没有获取到响应token则抛弃
            //设置超过两秒没有拿到令牌  抛弃请求
            if(!rateLimiter.tryAcquire(2,TimeUnit.SECONDS )){
                log.info("抛弃的请求:抢购失败,当前秒杀活动过于火爆,请重试");
                return "抢购失败,当前秒杀活动过于火爆,请重试";
            }
        System.out.println("秒杀商品的id: "+id);
        try {
            //根据秒杀的商品id 去调用秒杀业务
            Integer orderId = orderService.kill(id);
            System.out.println("秒杀成功,订单id为:"+orderId);
            return "秒杀成功,订单id为:"+orderId;

        } catch (Exception e) {
            e.printStackTrace();
            return e.getMessage();
        }
    }

测试 令牌桶算法限流

在这里插入图片描述

四. 限时抢购的实现

使用Redis来记录秒杀商品的时间,对秒杀过期的请求进行拒绝处理!


  1. 启动 Redis 服务

在这里插入图片描述

  1. 将秒杀商品放入Redis并设置超时
    这里我们使用String类型 以kill + 商品id作为key 以商品id作为value,设置180秒超时(可随意设置时间)
    在这里插入图片描述

127.0.0.1:6379> set kill1 1 EX 180
OK

  1. 导入Redis依赖
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  1. 修改配置连接redis
	spring.redis.port=6379
	spring.redis.host=localhost
	spring.redis.database=0
  1. 使用redis控制抢购超时的请求 OrderServiceImpl层中修改
@Service
@Transactional
@Slf4j
public class OrderServiceImpl implements OrderService {
    
    @Autowired
    private StockMapper stockMapper;

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public  Integer  kill(Integer id) {
//        校验redis中秒杀商品是否超时
        if(!stringRedisTemplate.hasKey("kill"+id)){
            throw new RuntimeException("当前商品的抢购活动已经结束了----");
        }
        //根据商品id效验ku库存
        Stock stock = checkStock(id);
        //扣除库存
        updateSale(stock);
            //创建订单
        Integer orderId = createOrder(stock);
        return orderId;
    }

测试 在设置的180s后秒杀接口关闭

在这里插入图片描述

五. 抢购接口隐藏(MD5加密)

防止抢购的链接被不断地请求,只要稍微写点爬虫代码,模拟一个抢购请求,就可以不通过点击下单按钮,直接在代码中请求我们的接口,完成下单,可以写一些脚本抢购各种秒杀商品。

将抢购接口进行隐藏,抢购接口隐藏(接口加盐)的具体做法:

  • 每次点击秒杀按钮,先从服务器获取一个秒杀验证值(接口内判断是否到秒杀时间)。
  • Redis以缓存用户ID和商品ID为Key,秒杀地址为Value缓存验证值
  • 用户请求秒杀商品的时候,要带上秒杀验证值进行校验。

在这里插入图片描述

1.创建用户表

DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `password` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

2. User实体层

@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class User {
    private Integer id;
    private  String name;
    private String password;
}

3. UserMapper

package com.xizi.miaosha.mapper;

import com.xizi.miaosha.pojo.User;
import org.springframework.stereotype.Repository;

@Repository
public interface UserMapper {
	//通过id 查询用户
    User findById(Integer id);
}

4.UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xizi.miaosha.mapper.UserMapper">
	<!--//通过id 查询用户-->
    <select id="findById" parameterType="Integer" resultType="User">
        select id,name,password from user where id=#{id}
    </select>
</mapper>

5.OrderServiceImpl 生成MD5签名

@Service
@Transactional
@Slf4j
public class OrderServiceImpl implements OrderService {
    
    @Autowired
    private StockMapper stockMapper;

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;


    // //生成md5签名得方法
    @Override
    public String getMd5(Integer id, Integer userId) {
        //验证userId 用户是否存在
        User user = userMapper.findById(userId);
        if(user==null){
            throw new RuntimeException("用户信息不存在");
        }
        log.info("用户的信息:[{}]",user.toString());
        //验证商品id  存放商品信息
        Stock stock = stockMapper.checkStock(id);
        if(stock==null){
            throw new RuntimeException("商品信息不存在");
        }
        log.info("商品的信息:[{}]",stock.toString());
        //生成md5签名 放入redis 服务
        String hashKey="KEY_"+userId+id;
        //随机盐
        String key = DigestUtils.md5DigestAsHex((userId + id + "!XIZIzz").getBytes());
        stringRedisTemplate.opsForValue().set(hashKey, key,240, TimeUnit.SECONDS);
        log.info("Redis写入:[{}] [{}]",hashKey,key);
        return key;
    }

2. Controller层添加生成MD5方法

@RestController
@RequestMapping("/stock")
@Slf4j
public class StockController {

    @Autowired
    private OrderService orderService;
    
    //生成md5值得方法
    @RequestMapping("md5")
    public String getMd5(Integer id,Integer userId){
        String md5;
        try {
            md5=orderService.getMd5(id,userId);
        } catch (Exception e) {
            e.printStackTrace();
            return "获取md5失败: "+e.getMessage();
        }
        return "获取md5信息为: "+md5;
    }

3. 测试MD5

在这里插入图片描述

4.Controller创建一个携带md5下单接口

   //开发秒杀方法  使用乐观锁防止超卖+令牌桶算法限流+md5加密(id+userId)
//    抢购接口隐藏  不能直接访问 必须先进行MD5加密存入redis 在请求接口的时候比较MD5是否相等
    @RequestMapping(value = "/killtokenmd5",method = RequestMethod.GET)
    public  String killtokenmd5(Integer id,Integer userId,String md5){
        System.out.println("秒杀商品的id: "+id);
        //加入令牌桶的限流措施
        if(!rateLimiter.tryAcquire(2,TimeUnit.SECONDS )){
            log.info("抛弃的请求:抢购失败,当前秒杀活动过于火爆,请重试");
            return "抢购失败,当前秒杀活动过于火爆,请重试";
        }

        try {
            //根据秒杀的商品id 去调用秒杀业务
            Integer orderId = orderService.kill(id,userId,md5);
            System.out.println("秒杀成功,订单id为:"+orderId);
            return "秒杀成功,订单id为:"+orderId;

        } catch (Exception e) {
            e.printStackTrace();
            return e.getMessage();
        }
    }

5. OrderService层重载kill()方法

/**
 * 订单业务
 */
public interface OrderService {

    /*
    处理秒杀的下单方法
     */
     Integer kill(Integer id);

    //生成md5签名得方法
    String getMd5(Integer id, Integer userId);

    //用来处理秒杀的下单方法 并返回订单id 加入 md5接口
    Integer kill(Integer id, Integer userId, String md5);
}

6.OrderServiceImpl 实现层

    //用来处理秒杀的下单方法 并返回订单id 加入 md5接口
    @Override
    public Integer kill(Integer id, Integer userId, String md5) {
//        校验redis中秒杀商品是否超时
        if(!stringRedisTemplate.hasKey("kill"+id)){
            throw new RuntimeException("当前商品的抢购活动已经结束了----");
        }

        //先验证签名
        String hashKey="KEY_"+userId+id;
        String s = stringRedisTemplate.opsForValue().get(hashKey);
        if(s==null){
            throw new RuntimeException("没有携带验证签名,请求不合法");
        }
        if(!s.equals(md5)){
            throw new RuntimeException("当前请求数据不合法,请稍后再试!");
        }
        //根据商品id效验ku库存
        Stock stock = checkStock(id);
        //扣除库存
        updateSale(stock);
        //创建订单
        Integer orderId = createOrder(stock);
        return orderId;

    }

7. 测试

在这里插入图片描述


六. 单用户限制频率

  • 用redis给每个用户做访问统计,甚至是带上商品id,对单个商品做访问统计,这都是可行的。

  • 我们先实现一个对用户的访问频率限制,我们在用户申请下单时,检查用户的访问次数,超过访问次数,则不让他下单!

在这里插入图片描述

1.UserService接口

package com.xizi.miaosha.service;

public interface UserService {
    //向redis中写入用户访问次数
    int saveUserCount(Integer userId);

    //判断单位时间调用次数
    boolean getUserCount(Integer userId);
}

2.UserServiceImpl 实现类

package com.xizi.miaosha.service.impl;

import com.xizi.miaosha.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    //根据不同用户id生成调用次数的key


    @Override
    public int saveUserCount(Integer userId) {
        String limitKey="LIMIT"+"_"+userId;
        //获取redis中指定key的调用次数
        String limitNum = stringRedisTemplate.opsForValue().get(limitKey);
        int limit=-1;
        if(limitNum==null){
            //第一次调用放入redis中设置为0
            stringRedisTemplate.opsForValue().set(limitKey, "0",3600, TimeUnit.SECONDS);
        }else{
            //不是第一次调用每次+1
             limit= Integer.parseInt(limitNum) + 1;
             stringRedisTemplate.opsForValue().set(limitKey, String.valueOf(limit),3600, TimeUnit.SECONDS);
        }
        return limit; //返回调用次数
    }

    @Override
    public boolean getUserCount(Integer userId) {
        String limitKey="LIMIT"+"_"+userId;
        String limitNum = stringRedisTemplate.opsForValue().get(limitKey);
        if(limitKey==null){
            log.error("该用户没有申请验证值记录,异常");
            return  true;
        }
        return Integer.parseInt(limitNum)>10;
    }
}

3.Controler创建接口

    //开发秒杀方法  使用乐观锁防止超卖+令牌桶算法限流+md5加密(id+userId)+单用户次数调用频率
    @RequestMapping(value = "/killtokenmd5limit",method = RequestMethod.GET)
    public  String killtokenmd5limit(Integer id,Integer userId,String md5){
        System.out.println("秒杀商品的id: "+id);
        //加入令牌桶的限流措施
        if(!rateLimiter.tryAcquire(2,TimeUnit.SECONDS )){
            log.info("抛弃的请求:抢购失败,当前秒杀活动过于火爆,请重试");
            return "抢购失败,当前秒杀活动过于火爆,请重试";
        }
        try {
            //加入单用户
            int count = userService.saveUserCount(userId);
            log.info("用户截止该次访问次数为:[{}]",count);
            //进行判断
            boolean userCount = userService.getUserCount(id);
            if(userCount){
               log.info("购买失败,超过频率限制!");
               return "购买失败,超过频率限制!";
            }
            //根据秒杀的商品id 去调用秒杀业务
            Integer orderId = orderService.kill(id,userId,md5);
            System.out.println("秒杀成功,订单id为:"+orderId);
            return "秒杀成功,订单id为:"+orderId;

        } catch (Exception e) {
            e.printStackTrace();
            return e.getMessage();
        }
    }

4.Jmeter压力测试

在这里插入图片描述

七. GitHub源码

点击下载源码进行参考

https://github.com/Y960303802/SpringBoot-Simple-

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
spring-boot-seckill分布式秒杀系统一个SpringBoot开发的从0到1构建的分布式秒杀系统,项目案例基本成型,逐步完善中。 开发环境: JDK1.8、Maven、Mysql、IntelliJ IDEA、SpringBoot1.5.10、zookeeper3.4.6、kafka_2.11、redis-2.8.4、curator-2.10.0 启动说明: 1、启动前 请配置application.properties中相关redis、zk以及kafka相关地址,建议在Linux下安装使用。 2、数据库脚本位于 src/main/resource/sql 下面,启动前请自行导入。 3、配置完成,运行Application中的main方法,访问 http://localhost:8080/seckill/swagger-ui.html 进行API测试。 4、秒杀商品页:http://localhost:8080/seckill/index.shtml ,部分功能待完成。 5、本测试案例单纯为了学习,某些案例并不适用于生产环境,大家根据所需自行调整。 秒杀架构: 架构层级 1、一般商家在做活动的时候,经常会遇到各种不怀好意的DDOS攻击(利用无辜的吃瓜群众夺取资源),导致真正的我们无法获得服务!所以说高防IP还是很有必要的。 2、搞活动就意味着人多,接入SLB,对多台云服务器进行流量分发,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 3、基于SLB价格以及灵活性考虑后面我们接入Nginx做限流分发,来保障后端服务的正常运行。 4、后端秒杀业务逻辑,基于Redis 或者 Zookeeper 分布式锁,Kafka 或者 Redis 做消息队列,DRDS数据库中间件实现数据的读写分离。 优化思路 1、分流、分流、分流,重要的事情说三遍,再牛逼的机器也抵挡不住高级别的并发。 2、限流、限流、限流,毕竟秒杀商品有限,防刷的前提下没有绝对的公平,根据每个服务的负载能力,设定流量极限。 3、缓存、缓存、缓存、尽量不要让大量请求穿透到DB层,活动开始前商品信息可以推送至分布式缓存。 4、异步、异步、异步,分析并识别出可以异步处理的逻辑,比如日志,缩短系统响应时间。 5、主备、主备、主备,如果有条件做好主备容灾方案也是非常有必要的(参考某年锤子的活动被攻击)。 6、最后,为了支撑更高的并发,追求更好的性能,可以对服务器的部署模型进行优化,部分请求走正常的秒杀流程,部分请求直接返回秒杀失败,缺点是开发部署时需要维护两套逻辑。 分层优化 1、前端优化:活动开始前生成静态商品页面推送缓存和CDN,静态文件(JS/CSS)请求推送至文件服务器和CDN。 2、网络优化:如果是全国用户,最好是BGP多线机房,减少网络延迟。 3、应用服务优化:Nginx最佳配置、Tomcat连接池优化、数据库配置优化、数据库连接池优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值