秒杀系统的设计思路(应对高并发,超卖等问题的解决思路)

news2025/4/22 15:41:00

首先我们先看一下设计秒杀系统时,我们应该考虑的问题。

解决方案:

 一.页面静态化结合CDN内容分发

前端把能提前放入cdn服务器的东西都放进去,反正把所有能提升效率的步骤都做一下,减少真正秒杀时候服务器的压力。

        秒杀活动的页面内容,大多数都是固定不变的。我们可以对秒杀页面做静态化处理,减少访问服务端的压力。在秒杀活动开始前,提前生成包含秒杀商品信息、倒计时等固定内容的静态 HTML 页面。这些页面可以根据秒杀活动的特点,例如开始时间、结束时间等来预先生成。

示例如下:

// 生成秒杀页面的HTML
public String generateSeckillPage(long goodsId) {
    GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId);
    // 其他固定信息的获取

    // 生成HTML页面,包含秒杀商品信息、倒计时等
    String htmlContent = "<html><head>...</head><body>...</body></html>";

    // 将HTML页面保存到静态文件,或者存储到数据库中

    return htmlContent;
}

        参与秒杀的用户可能分布在全国各地,地域不同,网速不同。为了让用户可以快速的进入到秒杀活动的页面,可以使用 CDN(Content Delivery Network,内容分发网络)让用户可以就近获取所需内容。将生成的静态 HTML 页面通过 CDN 分发,使用户可以就近获取所需内容。CDN 的作用是将页面的静态资源分发到全球不同的节点,用户访问时可以从离他们最近的节点获取内容,减少网络延迟,提高访问速度。

        用户访问秒杀活动页面时,直接请求 CDN 上的静态 HTML 页面,不再需要服务端动态生成页面内容。这样可以减轻服务端的压力,提高页面加载速度,提升用户体验。

二.按钮置灰控制

        秒杀活动开始前,按钮一般需要置灰。只有时间到了,才能变得可以点击。 这防止,秒杀用户在时间快到的前几秒,疯狂请求服务器,然后秒杀时间点 还没到,服务器就自己挂了。

三. 服务单一职责

        我们都知道微服务设计思想,也就是各个功能模块拆分,功能那个类似放 一起,再用分布式部署方式进行部署。

        如用户登录相关,就设计个用户服务,订单相关就搞个订单服务等等。那么,秒杀相关业务逻辑也可以放到一起, 搞个秒杀服务,单独给它搞个秒杀数据库。” 服务单一职责有个好处:如果秒杀 没抗住高并发压力,秒杀库崩了,服务挂了,也不会影响到系统其他服。

四.秒杀地址加盐(url动态化)

        链接如果明文暴露的话,会有人获取到请求 Url,提前秒杀了。因此,需要给 秒杀链接加盐。可以让URL 动态化,如通过 MD5 加密算法加密随机字符串来生成,秒杀链接的URL。

时间校验: 通过获取商品的起始时间和结束时间,判断当前时间与秒杀活动的状态(未开始、进行中、已结束),从而判断用户是否可以参与秒杀。

 GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId);
        long startAt = goods.getStartDate().getTime();
        long endAt = goods.getEndDate().getTime();
        long now = System.currentTimeMillis();
        int miaoshaStatus = 0;
        int remainSeconds = 0;
        if(now < startAt ) {//秒杀还没开始,倒计时
            miaoshaStatus = 0;
            remainSeconds = (int)((startAt - now )/1000);
        }else  if(now > endAt){//秒杀已经结束
            miaoshaStatus = 2;
            remainSeconds = -1;
        }else {//秒杀进行中
            miaoshaStatus = 1;
            remainSeconds = 0;
        }

        但仅仅是这样的话还是不够的,如果我们的秒杀的真实地址被人知道了,就可以写一个脚本不断的获取北京时间,在秒杀开始的毫秒级别时就可以请求。而且机器能在短时间内发送大量的请求,绝对会对我们的秒杀活动造成巨大的影响。
那怎么办呢?
        我们可以把我们的秒杀地址动态化,也就是通过MD5之类的加密算法去处理我们的秒杀地址,存入 redis缓存中,根据前端请求的url获取path。 判断与缓存中的字符串是否一致,一致就认为请求是正常的。这就是秒杀链接加盐,用这样的方法来阻止恶意用户直接请求我们的秒杀地址。

@RequestMapping(value="/path", method=RequestMethod.GET)
@ResponseBody
public Result<String> getMiaoshaPath( MiaoshaUser user,
                                         @RequestParam("goodsId")long goodsId,
                                         @RequestParam(value="verifyCode", defaultValue="0")int verifyCode) {
        if(user == null) {
            return Result.error(CodeMsg.SESSION_ERROR);
        }
        boolean check = miaoshaService.checkVerifyCode(user, goodsId, verifyCode);
        if(!check) {
            return Result.error(CodeMsg.REQUEST_ILLEGAL);
        }
        String path  =miaoshaService.createMiaoshaPath(user, goodsId);
        return Result.success(path);
    }



public String createMiaoshaPath(MiaoshaUser user, long goodsId) {
        if(user == null || goodsId <=0) {
            return null;
        }
        String str = MD5Util.md5(UUIDUtil.uuid()+"123456");
        redisService.set(MiaoshaKey.getMiaoshaPath, ""+user.getId() + "_"+ goodsId, str);
        return str;
    }

实现原理总结如下:

  1. 用户通过前端请求获取秒杀地址的path。
  2. 后端根据用户信息、商品ID等生成动态的秒杀地址。
  3. 将生成的地址通过加盐的加密算法处理,存入Redis缓存中,建立用户ID和商品ID的关联。
  4. 用户提交秒杀请求时,携带path参数,后端根据前端请求的URL获取path,再与Redis缓存中的地址进行比对,判断请求的合法性。

通过这种方法,成功防止了用户通过直接请求秒杀地址进行恶意操作,保障了秒杀活动的公正性和安全性。

五.接口限流进行防刷

        一般有两种方式限流:nginx 限流和redis 限流。

为了防止某个用户请求过于频繁,我们可以对同一用户限流;

为了防止黄牛模拟几个用户请求,我们可以对某个 IP 进行限流;

为了防止有人使用代理,每次请求都更换 IP 请求,我们可以对接口进行限流。

1. Nginx 提供了基于令牌桶算法的限流机制,可以有效控制请求的流量。通过配置 ngx_http_limit_req_module 模块,可以设定每个 IP 或每个 URI 的请求频率上限。

http {
    limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;

    server {
        location /api {
            limit_req zone=one burst=5;
            # 其他配置
        }
    }
}

上述配置中,limit_req_zone 定义了一个名为 one 的限流区域,每个 IP 的请求速率为 1 次/秒,limit_req 用于限制每秒的请求数,burst 指定了可以超出速率的请求数 

2.0为了防止瞬时过大流量压垮系统,还可以使用阿里Sentinel、Hystrix 组件进行限流。

六. 超卖问题解决思路

        只要是一个秒杀系统,就必然会存在超卖问题。不同用户在读请求的时候,发现商品库存足够,然后同时发起请求,进行秒杀操作,减库存,导致库存减为负数。

1.0使用乐观锁解决

        如果要使用乐观锁,那么我们就要给个商品库存一个版本号version字段,在每次我们读取库存的时候把版本号也读取出来,当这一个线程去执行扣减库存的操作的时候,去判断数据库当前的版本号是否是刚刚读取出来的版本号,如果不是则秒杀失败。

/**
     * 查询商品库存
     * @param id 商品id
     * @return
     */
    @Select("SELECT * FROM goods WHERE id = #{id}")
    Goods getStock(@Param("id") int id);

/**
     * 乐观锁方案扣减库存
     * @param id 商品id
     * @param version 版本号
     * @return
     */
    @Update("UPDATE goods SET stock = stock - 1, version = version + 1 WHERE id = #{id} AND stock > 0 AND version = #{version}")
    int decreaseStockForVersion(@Param("id") int id, @Param("version") int version);

service层:

/**
     * 扣减库存
     * @param gid 商品id
     * @param uid 用户id
     * @return SUCCESS 1 FAILURE 0
     */
    @Transactional
    public int sellGoods(int gid, int uid) {

        // 获取库存
        Goods goods = goodsDao.getStock(gid);
        if (goods.getStock() > 0) {
            // 乐观锁更新库存
            int update = goodsDao.decreaseStockForVersion(gid, goods.getVersion());
            // 更新失败,说明其他线程已经修改过数据,本次扣减库存失败,可以重试一定次数或者返回
            if (update == 0) {
                return 0;
            }
            // 库存扣减成功,生成订单
            Order order = new Order();
            order.setUid(uid);
            order.setGid(gid);
            int result = orderDao.insertOrder(order);
            return result;
        }
        // 失败返回
        return 0;
    }

2.0使用redis分布式锁

用 Redis  SET EX PX NX + 校验唯一随机值,再删除释放锁。

if (jedis.set(key_resource id, uni request id,"NX","Ex",100s) == 1)[ //加锁
    try {
        do something //业务处理
    catch(){
    }
    finally {
        //判断是不是当前线程加的锁,是才释放if (uni_request id.equals(jedis.get(key_resource_ id))) {
        jedis.del(lockKey);
        }
    }
}
七.MQ异步处理

如果瞬间流量特别大,可以使用消息队列削峰,异步处理。用户请求过来的时 候,先放到消息队列,再拿出来消费。

八.限流&降级&熔断

 限流,就是限制请求,防止过大的请求压垮服务器;

降级,就是秒杀服务有问题了,就降级处理,不要影响别的服务;

熔断,服务有问题就熔断,一般熔断降级是一起出现。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

day5--java基础编程:异常,内部类

6 异常 6.1 异常概述 出现背景&#xff1a; 在使用计算机语言进行项目开发的过程中&#xff0c;即使程序员把代码写得尽善尽美&#xff0c;在系统的运行过程中仍然会遇到一些问题&#xff0c;因为很多问题不是靠代码能够避免的&#xff0c;比如:客户输入数据的格式&#xff0c…

EI级 | Matlab实现TCN-BiGRU-Multihead-Attention多头注意力机制多变量时间序列预测

EI级 | Matlab实现TCN-BiGRU-Multihead-Attention多头注意力机制多变量时间序列预测 目录 EI级 | Matlab实现TCN-BiGRU-Multihead-Attention多头注意力机制多变量时间序列预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 1.【EI级】 Matlab实现TCN-BiGRU-Mult…

petalinux

基于网络参考&#xff0c;运行一个xilinx pcie rc的参考&#xff0c;选择vcu118平台&#xff08;基于microblaze&#xff09;&#xff1a;该参考通过pcie rc对挂在的外部ssd进行读写测试 Zynq PCI Express Root Complex design in Vivado - FPGA Developer 1&#xff0c;安装…

使用YOLOv8和Grad-CAM技术生成图像热图

目录 yolov8导航 YOLOv8&#xff08;附带各种任务详细说明链接&#xff09; 概述 环境准备 代码解读 导入库 定义letterbox函数 调整尺寸和比例 计算填充 应用填充 yolov8_heatmap类定义和初始化 后处理函数 绘制检测结果 类的调用函数 热图生成细节 参数解释 we…

Netty(一)-NIO

一、Netty 现在的互联网环境下&#xff0c;分布式系统大行其道&#xff0c;而分布式系统的根基在于网络编程&#xff0c;而Netty恰恰是Java领域网络编程的王者。如果要致力于开发高性能的服务器程序&#xff0c;高性能的客户端程序&#xff0c;必须掌握Netty。 1、NIO NIO&…

鸿蒙(OpenHarmony)系统之智能语音部件(1)

本文重点参考&#xff1a; OpenHarmony/ai_intelligent_voice_framework 一、总体概述 1. 功能简介及架构 智能语音组件包括智能语音服务框架和智能语音驱动&#xff0c;主要实现了语音注册及语音唤醒相关功能。 智能语音组件架构图如下图所示&#xff1a; &#xff08;1&a…

分库分表之Mycat应用学习四

4 分片策略详解 分片的目标是将大量数据和访问请求均匀分布在多个节点上&#xff0c;通过这种方式提升数 据服务的存储和负载能力。 4.1 Mycat 分片策略详解 总体上分为连续分片和离散分片&#xff0c;还有一种是连续分片和离散分片的结合&#xff0c;例如先 范围后取模。 …

弱电工程计算机网络系统基础知识

我们周围无时无刻不存在一张网&#xff0c;如电话网、电报网、电视网、计算机网络等&#xff1b;即使我们身体内部也存在许许多多的网络系统&#xff0c;如神经系统、消化系统等。最为典型的代表即计算机网络&#xff0c;它是计算机技术与通信技术两个领域的结合。 计算机网络的…

C语言函数篇——sqrt()函数

sqrt()函数介绍&#xff1a; sqrt()函数是C语言中用于计算一个数的平方根的数学函数。它接受一个浮点数作为参数&#xff0c;并返回该数的平方根。 sqrt()函数的语法&#xff1a; double sqrt(double x); 其中&#xff0c;x是要计算平方根的数。 sqrt()函数的应用案例&#x…

【中小型企业网络实战案例 五】配置可靠性和负载分担

【中小型企业网络实战案例 三】配置DHCP动态分配地址-CSDN博客 【中小型企业网络实战案例 四】配置OSPF动态路由协议 【中小型企业网络实战案例 二】配置网络互连互通-CSDN博客 【中小型企业网络实战案例 一】规划、需求和基本配置_大小企业网络配置实例-CSDN博客 配置VRRP联…

matlab导出高清图片,须经修改后放入latex(例如添加文字说明,matlab画图不易操作)

一、背景 我们在写文章时&#xff0c;使用matlab画图后&#xff0c;如果不需要对图片进行额外修改或调整&#xff0c;例如添加文字说明&#xff0c;即可直接从matlab导出eps格式图片&#xff0c;然后插入到latex使用。 通常latex添加图片&#xff0c;是需要eps格式的。 但很…

matplotlib单变量和双变量可视化

使用seaborn 库的tips数据集&#xff0c;其中包含了某餐厅服务员收集的顾客付小费的相关数据&#xff08;评论区&#xff09; 单变量可视化 直方图 直方图是观察单个变量最常用的方法。这些值是经过"装箱"&#xff08;bin&#xff09;处理的 直方图会将数据分组后绘…

通用定时器PWM波输出原理

1通用PWM波输出原理 总结&#xff1a;PWM波周期或频率由ARR决定&#xff0c;PWM波占空比由CCRx决定 1通用PWM模式 1.1PWM模式1 PWM模式1&#xff1a; 递增&#xff1a;CNT < CCRx&#xff0c;输出有效电平1 CNT > CCRx&#xff0c;输出无效电平0 递减&#xff1a;CNT …

蜕变,我的2023

作者&#xff1a;苍何&#xff0c;前大厂高级 Java 工程师&#xff0c;阿里云专家博主&#xff0c;CSDN 2023 年 实力新星&#xff0c;土木转码&#xff0c;现任部门技术 leader&#xff0c;专注于互联网技术分享&#xff0c;职场经验分享。 &#x1f525;热门文章推荐&#xf…

uniapp中组件库的丰富NumberBox 步进器的用法

目录 基本使用 #步长设置 #限制输入范围 #限制只能输入整数 #禁用 #固定小数位数 #异步变更 #自定义颜色和大小 #自定义 slot API #Props #Events #Slots 基本使用 通过v-model绑定value初始值&#xff0c;此值是双向绑定的&#xff0c;无需在回调中将返回的数值重…

【Linux专区】如何配置新服务器 | 添加普通用户到sudoers | 配置vim | git免账号密码pull push

&#x1f49e;&#x1f49e;欢迎来到 Claffic 的博客&#x1f49e;&#x1f49e; &#x1f449; 专栏&#xff1a;《Linux专区》&#x1f448; &#x1f4ac;前言&#xff1a; 时隔131天&#xff0c;你的好友Claffic重新发文了&#xff01;(✿◕‿◕✿) 上期已经带大家白嫖了阿…

MFC - 给系统菜单(About Dialog)发消息

文章目录 MFC - 给系统菜单(About Dialog)发消息概述笔记resource.h菜单的建立菜单项的处理MSDN上关于系统菜单项值的说法END MFC - 给系统菜单(About Dialog)发消息 概述 做了一个对话框程序, 在系统菜单(在程序上面的标题栏右击)中有"关于"的菜单. 这个是程序框架…

Git:常用命令(一)

取得项目的Git 仓库 从当前目录初始化 1 git init 初始化后&#xff0c;在当前目录下会出现一个名为.git 的目录&#xff0c;所有Git 需要的数据和资源都存放在这个目录中。不过目前&#xff0c;仅仅是按照既有的结构框架初始化好了里边所有的文件和目录&#xff0c;但我们还…

考pmp有用么?

PMP考出来究竟有什么用&#xff0c;这个问题一直是站在边缘的朋友经常思考的问题&#xff0c;其实我想说的是&#xff0c;当能力和经验都充足的时候&#xff0c;可能这单单的一张证书就能有莫大的作用&#xff0c;帮助你实现目前所追求的东西。 当我利用这张证书达到我的目的之…

idea部署javaSE项目(awt+swing项目)_idea导入eclipse的javaSE项目

一.idea打开项目 选择需要部署的项目 二、设置JDK 三、引入数据库驱动包 四、执行sql脚本 四、修改项目的数据库连接 找到数据库连接文件 五.其他系统实现 JavaSwing实现学生选课管理系统 JavaSwing实现学校教务管理系统 JavaSwingsqlserver学生成绩管理系统 JavaSwing用…