前言:
各位老铁们好,今天2B哥给大伙来介绍下平常咱们逛京东时候打开商品详细页其中实现的技术。是不是很吊哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈
看2B哥的商品图是不是更吊,最近在家办公,除了写代码就疯狂用它了。
大家不要笑,其实商品中心里技术难度有二:
一、商品搜索
二、商品详细页
今天和大家重点讲商品详细页(商品搜索也想学可以看文章末尾福利)。
商品中心业务:
一件商品从他发布到下架大致流程:
商品分类+商品(1对多)
t_catalog商品分类表
参数 | 名称 | 类型 | 备注 |
---|---|---|---|
id | 自增长 | int | 唯一 |
name | 分类名称 | String | |
code | 编码,简码 | String | 唯一 |
pid | 父ID | Int | ~~ ~~ |
order1 | 排序 | Int | |
type | 类型 | String | 类型,a:文章目录;p:产品目录 |
showInNav | 是否显示在首页的导航条上 | String | y:显示,n:不显示;默认n。仅对type=p有效 |
t_product商品表
字段名 | 数据类型 | 默认值 | 描述 |
---|---|---|---|
id | int | 唯一,商品ID | |
catalogID | varchar | 商品类别catalog表id | |
name | varchar | 商品名称 | |
introduce | text | 商品简介 | |
price | DECIMAL(9,2) | 定价 | |
nowPrice | DECIMAL(9,2) | 现价 | |
picture | String | 小图片地址 | |
score | Int | 0 | 赠送积分 |
~~ ~~ | |||
isnew | String | n | 是否新品。n:否,y:是 |
sale | String | n | 是否特价。n:否,y:是 |
activityID | String | 绑定的活动ID | |
giftID | String | 绑定的礼品ID | |
hit | int | 0 | 浏览次数 |
unit | String | 商品单位。默认“item:件” | |
createAccount | String | 录入人账号 | |
createtime | datetime | 录入时间 | |
updateAccount | String | 最后修改人账号 | |
updatetime | String | 最后修改时间 | |
isTimePromotion | String | n | 是否限时促销。n:否,y:是 |
status | Int | 0 | 商品状态。1:新增,2:已上架,3:已下架 |
productHTML | LONGTEXT | 商品介绍 | |
images | String | 商品多张图片集合,逗号分割 | |
sellcount | Int | 销售数量 | 默认:0 |
stock | Int | 剩余库存数 | 默认:0 |
searchKey | String | 搜索关键词 | |
title | String | 页面标题 | |
description | String | 页面描述 | |
keywords | String | 页面关键词 |
分类+商品+品牌:
我要查看苹果所有的产品(手机苹果、电脑苹果)需求
分类+商品+品牌+属性:
更加快速找到我们想购买的商品
t_attribute商品属性(参数)表
参数 | 名称 | 类型 | 备注 |
---|---|---|---|
id | 自增长 | int | 唯一 |
name | 属性/参数名称 | String | |
catalogID | 类别ID | Int | |
pid | 父ID | Int | 该字段具有双重含义。0表示属性大类,一般情况下产品只有两层attribute,一层为属性名称类别,一层为属性;-1:参数 |
order1 | 排序 | Int |
t_attribute_link商品属性(参数)中间表
参数 | 名称 | 类型 | 备注 |
---|---|---|---|
id | 自增长 | int | 唯一 |
attrID | 属性(参数)ID | Int | |
productID | 商品ID | Int | |
value | 商品参数值 | String | 名称从属性表中取得 |
分类+商品+品牌+属性+规格:
t_spec商品规格表
字段名 | 数据类型 | 默认值 | 描述 |
---|---|---|---|
id | int | 唯一 | |
productID | String | 商品ID | |
specColor | String | 颜色 | |
specSize | String | 尺寸 | |
specStock | Int | 此规格的商品库存数 | |
specPrice | Double | 此规格的商品价格 | |
specStatus | String | y:显示规格;n:不显示规格 |
SPU :
SPU(Standard Product Unit):标准化产品单元。是商品信息聚合的最小单位,是一组可复用、易检索的标准化信息的集合,该集合描述了一个产品的特性。通俗点讲,属性值、特性相同的商品就可以称为一个SPU。
举例:Iphone6
SKU:
SKU=Stock Keeping Unit(库存量单位)。即库存进出计量的基本单元,可以是以件,盒,托盘等为单位。
举例:Iphonex+土豪金+32G
商品中心技术实现(详细页):
商品详细页:商品+图片+库存+店铺+商品相关的信息
特点:QPS高、相应速度高、变化少(商品名称和属性和信息一般不会修改)
总结:发现打开一个商品详细页对于后台要请求的数据还是挺多的,现在问题来了如果
京东这么大的流量,商品页面没有做相应的处理,肯定是要玩玩的。那优化方案是什么了?
业界有两种解决方案:
1、中小型电商平台:页面静态化
2、大型电商平台:动态渲染页面。
中小型电商平台:页面静态化:
常用模板技术:freemarker velocity
@RequestMapping(value = "/item/static/{id}",method = RequestMethod.DELETE)
@ApiOperation(value = "静态化商品")
public Result<String> buildStatic(@PathVariable Long id){
String path = itemService.toStatic(id);
if(StringUtils.isEmpty(path)){
return new ResultUtil<String>().setErrorMsg("静态化商品页面出现异常");
}
return new ResultUtil<String>().setData(path);
}
此方式的缺点大家知道是什么吗?
1、不利于分布式(静态化页面过多不利于同步)
2、模板文件改动需要重新生成(JD上亿商品要要死吧)
大型电商平台:动态渲染页面。:
这种技术的话需要用到一些脚本语音:OpenResty、Lua、Nginx
OpenResty® 是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。
OpenResty® 通过汇聚各种设计精良的 Nginx 模块(主要由 OpenResty 团队自主开发),从而将 Nginx 有效地变成一个强大的通用 Web 应用平台。这样,Web 开发人员和系统工程师可以使用 Lua 脚本语言调动 Nginx 支持的各种 C 以及 Lua 模块,快速构造出足以胜任 10K 乃至 1000K 以上单机并发连接的高性能 Web 应用系统。
OpenResty® 的目标是让你的Web服务直接跑在 Nginx 服务内部,充分利用 Nginx 的非阻塞 I/O 模型,不仅仅对 HTTP 客户端请求,甚至于对远程后端诸如 MySQL、PostgreSQL、Memcached 以及 Redis 等都进行一致的高性能响应。
OpenResty安装:
下载:https://openresty.org/download/openresty-1.15.8.2-win64.zip
实战:
主要解决两个点:
** 一、流量分发**
** 二、缓存数据**
nginx+lua开发流量分发:
流量分发的nginx,会发送http请求到后端的应用层nginx上去,所以要先引入lua http lib包
wget https://raw.githubusercontent.com/pintsized/lua‐resty‐http/master/lib/resty/http_headers.lua
wget https://raw.githubusercontent.com/pintsized/lua‐resty‐http/master/lib/resty/http.lua
最近网络不稳定,也可以从:https://github.com/bungle/lua-resty-template 去下载这两个lua脚本
文件放在:openresty-1.15.8.2-win642\lualib\resty下
在nginx.conf文http模块中加入:
导入lua相关的包
lua_package_path '../lualib/?.lua;;';
lua_package_cpath '../lualib/?.so;;';
包含lua.conf文件
include lua.conf; (实际演示文件名为:lua-demo.conf)
lua.conf文件内容(实际演示文件名为:lua-demo.conf):
新建lua.conf文件
server {
listen 331;
location /product {
default_type 'text/html;charset=UTF-8';
lua_code_cache on;
content_by_lua_file D:/dis.lua; 流量分发逻辑写在dis.lua文件中
}
}
dis.lua代码:
local uri_args = ngx.req.get_uri_args()
local productId = uri_args["productId"]
local host = {"127.0.0.1:332","127.0.0.1:333"}
local hash = ngx.crc32_long(productId)
hash = (hash % 2) + 1
backend = "http://"..host[hash]
local method = uri_args["method"]
local requestBody = "/"..method.."?productId="..productId
local http = require("resty.http")
local httpc = http.new()
local resp, err = httpc:request_uri(backend,{
method = "GET",path = requestBody, keepalive=false
})
if not resp then
ngx.say("request error :", err)
return
end
ngx.say(resp.body)
httpc:close()
端口号为:332的 lua-demo.conf
端口号为:333的 lua-demo.conf
访问:
http://localhost:331/product?method=product&productId=122
http://localhost:331/product?method=product&productId=123
nginx+lua开发页面缓存与渲染:
应用层还需要用到模板动态渲染技术,所以还需要引入lua的template包
wget https://raw.githubusercontent.com/bungle/lua‐resty‐template/master/lib/resty/template.lua
wget https://raw.githubusercontent.com/bungle/lua‐resty‐template/master/lib/resty/template/html.lua
最近网络不稳定,也可以从:https://github.com/bungle/lua-resty-template 去下载这两个lua脚本
两个lua文件放在:openresty-1.15.8.2-win642\lualib\resty\html下
增加html静态模板
<html>
<head>
<meta http‐equiv="Content‐Type" content="text/html; charset=utf‐8" />
</head>
<body>
<h1>
商品id: {* productId *}<br/>
商品名称: {* productName *}<br/>
商品原价: {* productPrice *}<br/>
商品描述: {* productSpecification *}<br/>
商品: {* productPictureList *}<br/>
</h1>
</body>
增加product.lua
local uri_args = ngx.req.get_uri_args()
local productId = uri_args["productId"]
local cache_ngx = ngx.shared.my_cache
local productCacheKey = "product_info_"..productId
local productCache = cache_ngx:get(productCacheKey)
if productCache == "" or productCache == nil then
local http = require("resty.http")
local httpc = http.new()
local resp, err = httpc:request_uri("http://127.0.0.1:7777",{
method = "GET",
path = "/goods/productDet?productId="..productId
})
productCache = resp.body
local expireTime = math.random(600,1200)
cache_ngx:set(productCacheKey, productCache, expireTime)
end
local cjson = require("cjson")
local productCacheJSON =cjson.decode(productCache)
local context = {
productId = productCacheJSON.productId,
productName = productCacheJSON.productName,
productPrice = productCacheJSON.salePrice,
productPictureList = productCacheJSON.productImageSmall,
productSpecification = productCacheJSON.detail
}
local template = require("resty.template")
template.render("product.html", context)
启动tomcat
@RequestMapping(value = "/goods/productDet",method = RequestMethod.GET)
@ApiOperation(value = "商品详情")
public ProductDet getProductDet2(Long productId){
ProductDet productDet=contentService.getProductDet(productId);
return productDet;
}
{
productId: 562379,
salePrice: 49,
productName: "坚果 Pro 软胶保护套",
subTitle: "TPU 环保材质、耐磨、耐油、耐久性强",
limitNum: 100,
productImageBig: "http://image.smartisanos.cn/resource/902befd5f32a8caf4ca79b55d39ee25a.jpg",
detail: "<img src="http://image.smartisanos.cn/resource/98521dbfe1dd1e67db3f7ca21e76c9ef.jpg" style="width:1220px;height:7000px;" alt="" />",
productImageSmall:
[
"http://image.smartisanos.cn/resource/902befd5f32a8caf4ca79b55d39ee25a.jpg"
]
}
启动nginx测试:
http://localhost:331/product?method=product&productId=562379
总结:
大致过程如上所述,其中细节问题还是挺多的,2b哥今天也弄了大半天,主要是这种nginx和lua的报错和语法不太熟悉,很容易出错的哦。不过终于还是把他弄好了。如果大家需要更详细的教程,可以关注2B哥的微信(微信搜:java2b)。喜欢文章的点个赞吧
商品还有其他的更细的业务哦,因为篇幅关系不能全部写完,如果有不足之处欢迎大家指出来哦。