秒杀系统(二)——商品模块展示技术难点

2023-10-27

秒杀系统——商品模块展示技术难点

商品详情页

商品详情页是展示商品详细信息的一个页面,承载在网站的大部分流量和订单的入口。京东商城目前有通用版、全球购、闪购、易车、惠买车、服装、拼购、今日抄底等许多套模板。各套模板的元数据是一样的,只是展示方式不一样。目前商品详情页个性化需求非常多,数据来源也是非常多的,而且许多基础服务做不了的都放我们这,因此我们需要一种架构能快速响应和优雅的解决这些需求问题。

因此我们重新设计了商品详情页的架构,主要包括三部分:

  • 商品详情页系统:商品详情页系统负责静的部分

  • 商品详情页统一服务系统:统一服务负责动的部分

  • 商品详情页动态服务系统:动态服务负责给内网其他系统提供一些数据服务

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xotPDmt2-1644592278299)(G:\技术积累\亿级别并发编程电商\秒杀系统.assets\clipboard-1644589522303.png)]

商品详情页前端结构

前端展示可以分为这么几个维度:商品维度(标题、图片、属性等)、主商品维度(商品介绍、规格参数)、分类维度、商家维度、店铺维度等;另外还有一些实时性要求比较高的如实时价格、实时促销、广告词、配送至、预售等是通过异步加载。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LodPXvxH-1644592278302)(G:\技术积累\亿级别并发编程电商\秒杀系统.assets\clipboard-1644589827766.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q3ZwNUvs-1644592278303)(G:\技术积累\亿级别并发编程电商\秒杀系统.assets\clipboard-1644589827767.png)]

SPU: Standard Product Unit (标准化产品单元),SPU是商品信息聚合的最小单位,是一组可复用、易检索的标准化信息的集合,该集合描述了一个产品的特性。

SKU: Stock keeping unit(库存量单位) SKU即库存进出计量的单位(买家购买、商家进货、供应商备货、工厂生产都是依据SKU进行的),在服装、鞋类商品中使用最多最普遍。 例如纺织品中一个SKU通常表示:规格、颜色、款式。SKU是物理上不可分割的最小存货单元。

单品页流量特点

热点少,各种爬虫、比价软件抓取。

2.1、压测测试,进行压力测试

提升系统反应速度方法:

1、换数据库 ——换数据库

2、分库分表——进行优化

下图是我对电商商品进行Jmeter压测的截图。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hqFd9yiy-1644592278304)(G:\技术积累\亿级别并发编程电商\秒杀系统.assets\clipboard-1644589859440.png)]

Jmeter上图主要看两个参数Average和Throuhtput

其中平均值越小越好,吞吐量是越大越好。

其中遇到情况,就是有时候请求数量过大超过系统承受力,吞吐量更大,是后面大量请求错误,进行压测的时候需要注意。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ExWSsfRQ-1644592278305)(G:\技术积累\亿级别并发编程电商\秒杀系统.assets\clipboard-1644589960118.png)]

2.2、后台

影响系统主要的开销是两方面 ——磁盘IO 、网络IO

获取商品详情信息,下面是我获取商品详细页的部分代码

/**
 * 获取商品详情信息
 *
 * @param id 产品ID
 */
public PmsProductParam getProductInfo(Long id) {
   PmsProductParam productInfo = portalProductDao.getProductInfo(id);
    if (null == productInfo) {
        return null;
    }
    FlashPromotionParam promotion = flashPromotionProductDao.getFlashPromotion(id);
    if (!ObjectUtils.isEmpty(promotion)) {
        productInfo.setFlashPromotionCount(promotion.getRelation().get(0).getFlashPromotionCount());
        productInfo.setFlashPromotionLimit(promotion.getRelation().get(0).getFlashPromotionLimit());
        productInfo.setFlashPromotionPrice(promotion.getRelation().get(0).getFlashPromotionPrice());
        productInfo.setFlashPromotionRelationId(promotion.getRelation().get(0).getId());
        productInfo.setFlashPromotionEndDate(promotion.getEndDate());
        productInfo.setFlashPromotionStartDate(promotion.getStartDate());
        productInfo.setFlashPromotionStatus(promotion.getStatus());
    }
    return productInfo;
}

压测结果:

采用5000并发,可以看到异常率很高,下面进行优化。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wsvLbCTh-1644592278306)(G:\技术积累\亿级别并发编程电商\秒杀系统.assets\image-20220211223948220.png)]

静态化处理

将网页页面进行静态化处理,把它放在CDN(Content Delivery Network内容转发器)上。

不直接访问数据库,转而去访问CDN。采用FreeMarker工具生成静态化工具。

FreeMarker 是一款模板引擎:即基于模板和数据源生成输出文本(html网页,配置文件,电子邮件,源代码)的通用工具。它是一个 java 类库,最初被设计用来在MVC模式的Web开发框架中生成HTML页面,它没有被绑定到Servlet或HTML或任意Web相关的东西上。也可以用于非Web应用环境中。

模板编写使用FreeMarker Template Language(FTL)。使用方式类似JSP的EL表达式。模板中专注于如何展示数据,模板之外可以专注于要展示什么数据。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pIy6uC7L-1644592278309)(G:\技术积累\亿级别并发编程电商\秒杀系统.assets\clipboard-1644590469602.png)]

使用模板Template和数据源 Java Object生成输出文本(html网页、配置文件、电子邮件、源代码)

pom引入:

<dependency>

    <groupId>org.freemarker</groupId>

    <artifactId>freemarker</artifactId>

    <version>2.3.23</version>

</dependency>

来一个demo:

使用步骤:

第一步:创建一个Configuration对象,直接new一个对象。构造方法的参数就是freemarker对于的版本号。

第二步:设置模板文件所在的路径。

第三步:设置模板文件使用的字符集。一般就是utf-8.

第四步:加载一个模板,创建一个模板对象。

第五步:创建一个模板使用的数据集,可以是pojo也可以是map。一般是Map。

第六步:创建一个Writer对象,一般创建一FileWriter对象,指定生成的文件名。

第七步:调用模板对象的process方法输出文件。

第八步:关闭流。

public class FreeMarkTest {

    public static void main(String[] args) throws Exception {
        // 第一步:创建一个Configuration对象,直接new一个对象。构造方法的参数就是freemarker对于的版本号。
        Configuration configuration = new Configuration(Configuration.getVersion());
        // 第二步:设置模板文件所在的路径。
        configuration.setDirectoryForTemplateLoading(new File("D:\\ProgramData\\ftl"));
        // 第三步:设置模板文件使用的字符集。一般就是utf-8.
        configuration.setDefaultEncoding("utf-8");
        // 第四步:加载一个模板,创建一个模板对象。
        Template template = configuration.getTemplate("test.ftl");
        // 第五步:创建一个模板使用的数据集,可以是pojo也可以是map。一般是Map。
        Map dataModel = new HashMap<>();
        //向数据集中添加数据
        dataModel.put("hello", "我们来测试下数据看可以显示出来嘛");
        // 第六步:创建一个Writer对象,一般创建一FileWriter对象,指定生成的文件名。
        Writer out = new FileWriter(new File("D:\\ProgramData\\ftl\\test.html"));
        // 第七步:调用模板对象的process方法输出文件。
        template.process(dataModel, out);
        // 第八步:关闭流。
        out.close();

    }
<h1>
${hello}
</h1>

list标签:

<#list studentList as student> 
    ${student.id}/${studnet.name} 
 </#list>

if条件标签:

<#if student_index % 2 == 0>
<#else>
</#if>

Null值的处理:

<#if a??> 
    a不为空时。。 
<#else> 
    a为空时###
</#if>

日期标签:

当前日期: ${date?date}

当前时间:${date?time}

当前日期和时间:${date?datetime}

自定义日期格式:${date?string("yyyyMM/dd HH:mm: ss")}

包含标签:

<#include "hello.ftl"/>

实战:

ItemController

@RestController

@Api(description = "商品列表信息")

@RequestMapping("/item")

public class ItemController {

    @Autowired

    ItemService itemService;

    

    @RequestMapping(value = "/static/{id}",method = RequestMethod.GET)

    @ApiOperation(value = "静态化商品")

    public CommonResult<String> buildStatic(@PathVariable Long id){

        String path = itemService.toStatic(id);

        if(StringUtils.isEmpty(path)){

            return  CommonResult.failed("静态化商品页面出现异常");

        }

        return  CommonResult.success(path);

    }

}

接口:

public interface ItemService {

    /**
     * 静态化商品详情页
     * @param id
     * @return
     */

    String toStatic(Long id);

}

静态化核心代码: ItemServiceImpl

@Override

public String toStatic(Long id) {

    //查询商品信息
    PmsProduct pmsProduct=productMapper.selectByPrimaryKey(id);
    
    if (pmsProduct==null){
        return null;
    }
    String outPath="";
    try {
        String userHome = System.getProperty("user.home");
        // 第一步:创建一个Configuration对象,直接new一个对象。构造方法的参数就是freemarker对于的版本号。
        Configuration configuration = new Configuration(Configuration.getVersion());
        // 第二步:设置模板文件所在的路径。
        configuration.setDirectoryForTemplateLoading(new File(userHome+"/template/ftl"));
        // 第三步:设置模板文件使用的字符集。一般就是utf-8.
        configuration.setDefaultEncoding("utf-8");
        // 第四步:加载一个模板,创建一个模板对象。
        Template template = null;
        template = configuration.getTemplate("report.ftl");
        // 第五步:创建一个模板使用的数据集,可以是pojo也可以是map。一般是Map。
        Map dataModel = new HashMap();
        // 向数据集中添加数据
        dataModel.put("item", pmsProduct);
        String images= pmsProduct.getPic();
        if(StringUtils.isNotEmpty(images)){
            String[] split = images.split(",");
            List<String> imageList= Arrays.asList(split);
            dataModel.put("imageList", imageList);
        }
        // 第六步:创建一个Writer对象,一般创建一FileWriter对象,指定生成的文件名。
        outPath=userHome+"/template/report/1000"+pmsProduct.getId()+".html";
        Writer out = new FileWriter(new File(outPath));
        // 第七步:调用模板对象的process方法输出文件。
        template.process(dataModel, out);
        // 第八步:关闭流。
        out.close();
    } catch (IOException e) {
        e.printStackTrace();
    } catch (TemplateException te) {
        te.printStackTrace();
    }
    return outPath;

}

前端:pms/index.vue

<el-button

  size="mini"

  @click="product_static(scope.$index, scope.row)"></el-button>
定义vue的product_static方法的js代码 

script:

product_static(index,obj){
console.log(index,obj.id)
  this.$confirm('确认要静态化', '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        }).then(()=>{
          productStatic(obj.id).then(response=>{
            this.$message({
              message: '静态化成功',
              type: 'success',
              duration: 1000
            });
            this.editSkuInfo.dialogVisible=false;
          });
        });
     }

product.js:

export function productStatic(id) {

  return request({

    url:'/item/static/'+id,

    method:'get',

  })

}

优化:如果发生价格改变、秒杀 or 倒计时、下单的情况下 ?? 由于静态化文件不能实时修改 ,js没有生效(js、css、图片url)

这个方案只适合小流量架构 。为什么?

re:如果类似京东这种商品级别,使用页面静态化,每次修改页面栏位需要生成的新页面太多,并不适合。

小流量架构:https://www.processon.com/view/link/5e5774dae4b0cb56daac5a80

分布式场景下:

1000个静态商品页面使用 1个模板,当商品界面发生修改,需要修改的页面数量:1000个静态商品页面 * 机房(服务)数量;

公式 :1000个静态页面*机房数量。

过程: 是修改了一个字段,然后生成1k个静态页面,然后拷贝到其他N-1台服务器上

小米:1000个商品页面 12台 12000个静态化数据 CDN 12*1000 个静态化页面

京东:10000000个商品也米娜 50台 上亿级别静态化页面, 京东商品多,静态化页面太多

插入、修改、数据调整,这些都需要重新生成静态页面。

1个模板改了所有的静态化页面跟着改, 如果修改静态页面的一个字段(如果改个字段),需要重新生成所有的静态页面

架构方案的问题:

问题一:

我们知道数据新增分:增量和全量数据

如果后台的小二新增了很多的商品,那我们都要对这些商品进行静态化,但是现在有个问题。那这些数据如何同步了?这是一个新增商品同步的问题,那这个问题怎么解决比较好了?。

在这里插入图片描述

背景:不同应用部署在不同服务器甚至在不同的机房不同的国家。数据修改后,需要进行数据同步

同步的方案

1、通过网络同步的方式 就是其中一台服务器静态化之后,然后把文件同步到其他应用服务器上去。比如我们的linux命令scp方式。这种方式虽然可行,但是我们发现问题还是蛮多的,有多少个节点就需要同步多少份,等于是商品的数量*服务器的应用数数。很显然这种办法不是最优的解决办法

如果上述办法无法解决,那我们就用另外的方案,同学们你们觉得还有其他的方案没有?

**2、定时任务:**可以在某个应用用一个定时任务,然后分别去执行数据库需要静态化的数据即可,可以解决上述1数据同步的问题,因为所有的任务都是在本机运行,就不需要数据同步了。但是也有一个问题。就是如何避免不通的机器跑的数据不要重复,也就是A和B定时任务都跑了一份商品。这个是这种方案需要解决的。(比较直观的就是上锁) 我理解:就是每个节点服务器上启动定时任务,自动去复制静态化页面和数据

**3、消息中间件:**还有一种办法就是通过消息中间件来解决。订阅topic然后生成当前服务器静态化的页面。

问题二:

我们的freemark它是数据要事先按我这个模板生产好的,那就是说一定你改了模板,如果要生效的话,需要重新在把数据取出来和我们这个模板进行匹配生产更多的的静态html文件。那这是一个比较大的问题

如果后台数据有变更呢?如何及时同步到其它服务端?

如果页面静态化了,我们搜索打开一个商品详细页,怎么知道要我需要的访问的静态页面?

万一我们模板需要修改了怎么办?

牵一发动全身。

后台优化:

redis缓存:

redis设置:RedisConifg》RedisOpsUtil

/**
 * 获取商品详情信息
 *
 * @param id 产品ID
 */
public PmsProductParam getProductInfo(Long id) {
    PmsProductParam productInfo = null;
    //从缓存Redis里找
    productInfo = redisOpsUtil.get(RedisKeyPrefixConst.PRODUCT_DETAIL_CACHE + id, PmsProductParam.class);
    if(null!=productInfo){
        return productInfo;
    }
    productInfo = portalProductDao.getProductInfo(id);
    if (null==productInfo) {
        log.warn("没有查询到商品信息,id:"+id);
        return null;
    }
    FlashPromotionParam promotion = flashPromotionProductDao.getFlashPromotion(id);
    if (!ObjectUtils.isEmpty(promotion)) {
        productInfo.setFlashPromotionCount(promotion.getRelation().get(0).getFlashPromotionCount());
        productInfo.setFlashPromotionLimit(promotion.getRelation().get(0).getFlashPromotionLimit());
        productInfo.setFlashPromotionPrice(promotion.getRelation().get(0).getFlashPromotionPrice());
        productInfo.setFlashPromotionRelationId(promotion.getRelation().get(0).getId());
        productInfo.setFlashPromotionEndDate(promotion.getEndDate());
        productInfo.setFlashPromotionStartDate(promotion.getStartDate());
        productInfo.setFlashPromotionStatus(promotion.getStatus());
    }
    redisOpsUtil.set(RedisKeyPrefixConst.PRODUCT_DETAIL_CACHE + id, productInfo, 3600, TimeUnit.SECONDS);
    return productInfo;
}

好处:

加入redis之后我们发现提高了可以把之前请求 数据库查询的商品都缓存到redis中,通过对redis的访问来减少对数据里的依赖,减少了依赖本质就是减少了磁盘IO。

问题:

提高请求的吞吐量,除了减少磁盘IO,还有网络IO,我们可以发现,请求redis其实也会涉及到网络IO,我们所有的请求都要走xxx端口号。这个问题下一篇再总结

压力测试:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TxTIvsZu-1644592278311)(G:\技术积累\亿级别并发编程电商\秒杀系统.assets\clipboard-1644592204483.png)]

我们发现吞吐量有一定的提高。但是问题还是有的。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

秒杀系统(二)——商品模块展示技术难点 的相关文章

  • Android开发之Retrofit/OkHttp使用

    OkHttp 简介 OkHttp是时下最火的Http请求框架 其官网及源码地址如下 OkHttp官网地址 http square github io okhttp OkHttp源码地址 https github com square okh
  • .Net下正则匹配规则

    Net中常用的正则表达式选项 1 IgnoreCase 忽略大小写 匹配时不区分大小写 2 Multiline 多行模式 更改 和 的含义 使它们分别在任意一行的行首和行尾匹配 而不仅仅在整个字符串的开头和结尾匹配 在此模式下 的精确含意是
  • 寻找小数

    题目描述 有一个分数a b 你需要找到数字c在这个数的小数点后第一次出现的位置 输入格式 输入一行 包含三个整数a b c 输出格式 输出一个整数 如果不存在c 输出 1 样例输入 1 2 0 样例输出 2 约定 1 lt a
  • TCP并发服务器的编程实现

    TCP并发服务器的编程实现 1 基于TCP的服务器编程模型 创建通信端点 套接字 返回该端点的文件描述符 sfd socket 2 2 将sfd和本地的ip地址和端口号绑定 bind 2 3 将sfd设置为被动连接状态 监听客户端的到来 如
  • linux中docker报错:ERROR: Got permission denied while trying to connect to the Docker daemon socket。

    文章目录 一 问题描述 二 问题分析 三 解决方法 1 切换成root用户操作 这是最直接的方法 切换命令 2 添加docker的用户组 把当前用户加入组中 四 gpasswd命令用法 一 问题描述 在运行docker命令 如docker
  • Redis集群教程(Redis cluster tutorial)

    本博文翻译自Redis官网 http redis io topics cluster tutorial 本文档以温和的方式介绍Redis集群 不使用复杂的方式来理解分布式系统的概念 它介绍了如何建立 测试和使用一个集群 没有详细的覆盖Red
  • C语言——猜数字游戏

    游戏规则 输入1则开始游戏 输入0则结束游戏 输入其他数字则会提醒选择错误 输入1 游戏开始 系统会随机生成一个数字 游戏这需要不断根据提醒调整输入的数字 直到输入正确 系统会输出 恭喜你 猜对了 include
  • 每日一题 蛇形矩阵

    蛇形矩阵 输入两个整数n和m 输出一个n行m列的矩阵 将数字 1 到 n m 按照回字蛇形填充至矩阵中 具体矩阵形式可参考样例 输入格式 输入共一行 包含两个整数n和m 输出格式 输出满足要求的矩阵 矩阵占n行 每行包含m个空格隔开的整数
  • Spring——IoC和DI

    目录 一 初识Spring 为什么要使用Spring 什么是Spring Spring框架的核心 由哪些模块组成 二 Core Container 核心容器 IoC 控制反转 什么是 IoC IoC 的作用 IoC 的优点 IoC 的缺点
  • SpringBoot-Web开发

    Spring Boot非常适合web应用程序开发 您可以使用嵌入式Tomcat Jetty Undertow或Netty来创建一个自包含的HTTP服务器 大多数web应用程序使用spring boot starter web模块来快速启动和
  • 基于深度置信网络(DBN)的语音分类识别(Matlab实现)

    基于深度置信网络 DBN 的语音分类识别 Matlab实现 引言 语音分类识别是指根据输入的语音信号 将其划分到预先定义的不同类别中 这一领域广泛应用于语音识别 语音合成 语音转换等众多应用场景中 本文将介绍一种基于深度置信网络 Deep
  • Java web 项目Tamcat在IDEA控制台输出乱码

    遇到乱码问题怎么解决呢 出现乱码其实就是编码格式有问题 设置一下呗 我们先查看一下编码格式 在改一下 1 查看编码格式 首选进入Tamcat安装的根目录 进入conf目录 找到logging prooperties文件并打开 查看编码格式
  • 机箱嗡嗡一直响

    新买的电脑 用了一段时间后 机箱嗡嗡蜂鸣响 特别吵耳朵 一直找不到原因 后来发现原因是硬盘螺丝没有拧紧 把螺丝拧紧了 嗡嗡声没有了 过了一段时间 又发现嗡嗡声 网上一查是说机箱与硬盘产生共振了 第一次发现物理原来在生活中这么普遍 我把机箱侧
  • python第三方库安装成功,但是在pycharm中不能用

    在电脑终端使用pip安装好第三方模块但是在pycham中却显示无法找到此第三方模块 无法导入此模块该如何解决呢 方案一 在Pycharm中 依次打开File gt Settings 弹窗如下图 点击右侧 号 输入自己需要导入包的名称 在下面
  • Advanced Level 1036 Boys vs Girls (25 point(s))

    题目 This time you are asked to tell the difference between the lowest grade of all the male students and the highest grad
  • 基于pytorch 构建神经网络进行气温预测

    import numpy as np import pandas as pd import matplotlib pyplot as plt import torch import warnings warnings filterwarni
  • Three.js的物体点击选中拾取原理剖析

    Hello 大家好 今天来说一下three js的物体拾取原理 声明 本文介绍的是three js全屏模式下的原理 如果涉及到three js渲染在网页的一部分 请自行推导 第一节 介绍Three js的坐标系 先来粗浅的介绍一下Three
  • python提取特定时间段内的数据

    尝试一下 data Date pd to datetime data Date data data data Date gt pd to datetime 20120701 data Date lt pd to datetime 20120
  • 物理课后习题12910

    文章目录 1 第一章 质点运动学 2 第二章 质点动力学 3 第9章 电荷与真空中的静电场 4 第10章 导体和电介质中的静电场 1 第一章 质点运动学 2 第二章 质点动力学 3 第9章 电荷与真空中的静电场 4 第10章 导体和电介质中

随机推荐