日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

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

發布時間:2024/3/24 53 豆豆
生活随笔 收集整理的這篇文章主要介紹了 秒杀系统(二)——商品模块展示技术难点 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

秒殺系統——商品模塊展示技術難點

商品詳情頁

商品詳情頁是展示商品詳細信息的一個頁面,承載在網站的大部分流量和訂單的入口。京東商城目前有通用版、全球購、閃購、易車、惠買車、服裝、拼購、今日抄底等許多套模板。各套模板的元數據是一樣的,只是展示方式不一樣。目前商品詳情頁個性化需求非常多,數據來源也是非常多的,而且許多基礎服務做不了的都放我們這,因此我們需要一種架構能快速響應和優雅的解決這些需求問題。

因此我們重新設計了商品詳情頁的架構,主要包括三部分:

  • 商品詳情頁系統:商品詳情頁系統負責靜的部分

  • 商品詳情頁統一服務系統:統一服務負責動的部分

  • 商品詳情頁動態服務系統:動態服務負責給內網其他系統提供一些數據服務

商品詳情頁前端結構

前端展示可以分為這么幾個維度:商品維度(標題、圖片、屬性等)、主商品維度(商品介紹、規格參數)、分類維度、商家維度、店鋪維度等;另外還有一些實時性要求比較高的如實時價格、實時促銷、廣告詞、配送至、預售等是通過異步加載。

SPU:?Standard Product Unit (標準化產品單元),SPU是商品信息聚合的最小單位,是一組可復用、易檢索的標準化信息的集合,該集合描述了一個產品的特性。

SKU: Stock keeping unit(庫存量單位) SKU即庫存進出計量的單位(買家購買、商家進貨、供應商備貨、工廠生產都是依據SKU進行的),在服裝、鞋類商品中使用最多最普遍。 例如紡織品中一個SKU通常表示:規格、顏色、款式。SKU是物理上不可分割的最小存貨單元。

單品頁流量特點

熱點少,各種爬蟲、比價軟件抓取。

2.1、壓測測試,進行壓力測試

提升系統反應速度方法:

1、換數據庫 ——換數據庫

2、分庫分表——進行優化

下圖是我對電商商品進行Jmeter壓測的截圖。

Jmeter上圖主要看兩個參數Average和Throuhtput

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

其中遇到情況,就是有時候請求數量過大超過系統承受力,吞吐量更大,是后面大量請求錯誤,進行壓測的時候需要注意。

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并發,可以看到異常率很高,下面進行優化。

靜態化處理

將網頁頁面進行靜態化處理,把它放在CDN(Content Delivery Network內容轉發器)上。

不直接訪問數據庫,轉而去訪問CDN。采用FreeMarker工具生成靜態化工具。

FreeMarker 是一款模板引擎:即基于模板和數據源生成輸出文本(html網頁,配置文件,電子郵件,源代碼)的通用工具。它是一個 java 類庫,最初被設計用來在MVC模式的Web開發框架中生成HTML頁面,它沒有被綁定到Servlet或HTML或任意Web相關的東西上。也可以用于非Web應用環境中。

模板編寫使用FreeMarker Template Language(FTL)。使用方式類似JSP的EL表達式。模板中專注于如何展示數據,模板之外可以專注于要展示什么數據。

使用模板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 {@AutowiredItemService 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

@Overridepublic 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-buttonsize="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端口號。這個問題下一篇再總結

壓力測試:

我們發現吞吐量有一定的提高。但是問題還是有的。

總結

以上是生活随笔為你收集整理的秒杀系统(二)——商品模块展示技术难点的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。