基于Vue+SpringCloudAlibaba微服务电商项目实战-商品服务-015:亿万级别商品详情页面实现动态优化

1 亿万级别商品详情页面如何设计

今日课程任务

  1. 回顾商品详情页面静态化的好处
  2. 基于FreeMarker实现生成商品详情静态页面
  3. 将商品详情页面直接放入CDN缓存中运行
  4. 快速构建OpenResty(nginx)运行环境
  5. 基于OpenResty+lua实现静态化模版引擎
  6. 使用lua语言动态调用后端接口获取实时数据
  7. 基于OpenResty+lua实现亿万级别商品详情页面设计

亿万级别商品详情页面设计
在这里插入图片描述
商品详情页面的设计存在哪些问题?

  1. Mysql与ES如何保持数据同步的问题
    方案1 每次发布商品的时候调用es接口实现数据的同步
    方案2 使用canal解决
  2. 在商品详情页面除了商品价格和库存经常发生变化以外,其他数据可能很少发生变化,所以需要将商品详情页面设计成静态化形式。
  3. 商品页面静态化存在哪些好处?(FreeMarker实现)
    访问速度非常快、不需要查询数据库得到数据减轻数据库访问压力、对SEO搜索非常友好。

2 使用Nginx缓存商品详情页面

  1. 可以采用nginx或者cdn直接缓存商品详情页面

Nginx缓存商品详情页面原理:
直接将网页源代码缓存到nginx目录中,缓存的key为商品详情的url路径地址。

Nginx与MySQL数据不一致如何解决?

  1. 直接清除nginx缓存 不靠谱
  2. 在url后面加上最新更新商品的时间戳

基于ES实现商品的搜索
新建模块mt-shop-service-api-product、mt-shop-service-product
核心代码

public interface ProductDetailService {

    @GetMapping("/getProductsearchDetails")
    BaseResponse<ProductDto> getProductsearchDetails(@RequestParam ("productId") Long productId);
}
@RestController
public class ProductDetailServiceImpl extends BaseApiService implements ProductDetailService {
    @Autowired
    private ProductReposiory productReposiory;

    @Override
    public BaseResponse<ProductDto> getProductsearchDetails(Long productId) {
        Optional<ProductEntity> optionalResult = productReposiory.findById(productId);
        ProductEntity productEntity = optionalResult.get();
        if (productEntity == null) {
            return setResultError("没有查询到数据");
        }
        ProductDto productDto = doToDto(productEntity, ProductDto.class);
        return setResultSuccess(productDto);
    }
}

创建商品web应用
新建模块mt-shop-web/mt-shop-commodity-web

@FeignClient("app-mayikt-product")
public interface ProductDetailsFeign extends ProductDetailService {
}
@Controller
public class CommodityIndexController {

    @Autowired
    private ProductDetailsFeign productDetailsFeign;

    @RequestMapping("/getProductsearchDetails")
    public String getProductsearchDetails(Long productId, HttpServletRequest request) {
        // 控制层采用feign客户端调用商品服务接口
        BaseResponse<ProductDto> productsearchDetails = productDetailsFeign.getProductsearchDetails(productId);
        ProductDto productDto = productsearchDetails.getData();
        request.setAttribute("p", productDto);
        return "productsearchDetails";
    }

    @RequestMapping("/generateHtml")
    @ResponseBody
    public String generateHtml(Long productId) throws IOException, TemplateException {
        //创建配置类
        Configuration configuration = new Configuration(Configuration.getVersion());
        //设置模板路径
        String classpath = this.getClass().getResource("/").getPath();
        configuration.setDirectoryForTemplateLoading(new File(classpath + "/templates/"));
        //设置字符集
        //configuration.setDefaultEncoding("UTF‐8");
        //加载模板
        Template template = configuration.getTemplate("productsearchDetails.ftl");
        //数据模型
        Map<String, Object> map = new HashMap<>();
        // 调用商品服务接口获取商品详情的数据
        BaseResponse<ProductDto> productsearchDetails = productDetailsFeign.getProductsearchDetails(productId);
        ProductDto productDto = productsearchDetails.getData();
        map.put("p", productDto);
        //静态化
        String content = FreeMarkerTemplateUtils.processTemplateIntoString(template, map);
        InputStream inputStream = IOUtils.toInputStream(content);
        //输出文件  nginx 目录
        Integer id = productDto.getId();
        FileOutputStream fileOutputStream = new FileOutputStream(new File("F:\\path\\nginx\\nginx-1.17.3\\html\\" + id + ".html"));
        int copy = IOUtils.copy(inputStream, fileOutputStream);
        return copy > 0 ? "success" : "fail";
    }
}

配置nginx conf/nginx.conf文件

events {
  #的最大连接数(包含所有连接数)1024
  worker_connections  1024;  ## Default: 1024
}

http{
   # 代理缓存配置
   proxy_cache_path "./meite_cachedata"  levels=1:2 keys_zone=meitecache:256m inactive=1d max_size=1000g; 
   server {
     listen 80;
     location /{
        #使用缓存名称
        proxy_cache meitecache;
		#对以下状态码实现缓存
        proxy_cache_valid 200 206 304 301 302 1d;
		#缓存的key
        proxy_cache_key $request_uri;
        add_header X-Cache-Status $upstream_cache_status;
		#反向代理地址
        proxy_pass http://127.0.0.1:8030/;
      }
   }
}

测试效果:
在这里插入图片描述

3 使用FreeMarker生成静态化模板页面

商品详情页面中频繁变化的是价格和库存,因此在一个商品详情页面中库存和商品价格不能放入到缓存中,要实时读取,其他可以作为伪静态页面直接读取即可。

productsearchDetails.ftl

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>${p.name}</title>
</head>
<body>

<h1>商品名称:${p.name}</h1>
<img src="${p.mainImage}">
<div class="footer">
    <p>客服小微QQ:483966038 <span></span>Email:644064779@.com <span></span>官方粉丝群:193086273<br> Copyright ©2016-2019
        上海每特教育科技有限公司 版本所有Powered by 蚂蚁课堂 <span></span>沪ICP备18018606号-2</p>
</div>

</body>
</html>

查询ES数据替换上述${}内容生成静态html页面

@Controller
public class CommodityIndexController {

    @Autowired
    private ProductDetailsFeign productDetailsFeign;

    @RequestMapping("/getProductsearchDetails")
    public String getProductsearchDetails(Long productId, HttpServletRequest request) {
        // 控制层采用feign客户端调用商品服务接口
        BaseResponse<ProductDto> productsearchDetails = productDetailsFeign.getProductsearchDetails(productId);
        ProductDto productDto = productsearchDetails.getData();
        request.setAttribute("p", productDto);
        return "productsearchDetails";
    }

    @RequestMapping("/generateHtml")
    @ResponseBody
    public String generateHtml(Long productId) throws IOException, TemplateException {
        //创建配置类
        Configuration configuration = new Configuration(Configuration.getVersion());
        //设置模板路径
        String classpath = this.getClass().getResource("/").getPath();
        configuration.setDirectoryForTemplateLoading(new File(classpath + "/templates/"));
        //设置字符集
        //configuration.setDefaultEncoding("UTF‐8");
        //加载模板
        Template template = configuration.getTemplate("productsearchDetails.ftl");
        //数据模型
        Map<String, Object> map = new HashMap<>();
        // 调用商品服务接口获取商品详情的数据
        BaseResponse<ProductDto> productsearchDetails = productDetailsFeign.getProductsearchDetails(productId);
        ProductDto productDto = productsearchDetails.getData();
        map.put("p", productDto);
        //静态化
        String content = FreeMarkerTemplateUtils.processTemplateIntoString(template, map);
        InputStream inputStream = IOUtils.toInputStream(content);
        //输出文件  nginx 目录
        Integer id = productDto.getId();
        FileOutputStream fileOutputStream = new FileOutputStream(new File("D:\\devtool\\nginx\\nginx-1.17.3\\html\\" + id + ".html"));
        int copy = IOUtils.copy(inputStream, fileOutputStream);
        return copy > 0 ? "success" : "fail";
    }
}

测试效果:
在这里插入图片描述
1 每次在发布商品的时候,通过ftl技术生成好静态商品详情页面放入到nginx的目录中,提前预热商品详情页面。
2 也可以将静态html页面放入第三方cdn中,遵循就近原则访问可以减少带宽的传输距离,从而提高整个页面访问效率。

4 openresty+lua实现亿级别商品详情页面原理

openresty+nginx+lua实现亿级别商品详情页面设计原理

  1. 后台每次在更新商品详情页面的时候,会直接生成对应的商品详情页面的模板,放入到nginx中,采用lua脚本读取mysql/es/接口拿到数据源,再渲染给客户端。
  2. 优点:可以灵活扩展操作nginx、减轻服务后台访问压力。
    整个商品详情页面数据缓存到nginx中,对价格、库存等数据进行实时查询,既可以做缓存又能动态实时读取mysql数据,无需调用web层能有效减轻后台服务压力。

5 openresty的基本介绍

Openresty http://openresty.org/cn/ 官网
Lua语言介绍 https://baike.baidu.com/item/lua/7570719?fr=aladdin
OpenResty®是一个基于Nginx与Lua的高性能Web平台,其内部集成了大量精良的Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态Web应用、Web服务和动态网关。(可以理解为nginx加强版本)

linux环境上安装openresty环境

yum install yum-utils -y
yum-config-manager --add-repo https://openresty.org/package/centos/openresty.repo
yum install openresty –y
yum install openresty-resty -y
yum --disablerepo="*" --enablerepo="openresty" list available
yum install pcre-devel openssl-devel gcc curl

openresty-1.13.6.2下载:http://openresty.org/en/download.html
上传到/usr/local目录后解压
tar -zxvf openresty-1.13.6.2.tar.gz
cd openresty-1.13.6.2
./configure
make
make install
安装完成之后,nginx默认安装在 /usr/local/openresty/nginx目录下
cd  /usr/local/openresty/nginx
/usr/local/openresty/nginx/sbin/nginx 启动nginx
/usr/local/openresty/nginx/sbin/nginx  -s reload 重启nginx

测试效果:
在这里插入图片描述

6 openrety整合简单lua代码指令

指定简单lua脚本 直接修改conf/nginx.conf配置文件

location /luademo {
    default_type 'text/html';
    content_by_lua_block {
    ngx.say("meite mayikt");
    }
}

指定文件位置/usr/local/openresty/nginx/lua

location /luademo2 {
    default_type 'text/html';
    content_by_lua_file /usr/local/openresty/nginx/lua/luademo.lua;
}

luademo.lua文件上传到/usr/local/openresty/nginx/lua/目录
luademo.lua

ngx.say("meite 111 333 mayikt");

测试效果:
在这里插入图片描述

7 openresty整合lua模板引擎框架

1. 引入lua模板文件
需要引入lua-resty-template-master
https://github.com/bungle/lua-resty-template
将template.lua 放入/usr/local/openresty/lualib/resty
将html.lua 放入/usr/local/openresty/lualib/resty/html
2. 上传html模板文件和lua引用文件
cd /usr/local/openresty/nginx
mkdir template
上传mayikt.html到/usr/local/openresty/nginx/template目录下
mayikt.html

<!DOCTYPE html>
<html>
<body>
    <h1>{{message}}</h1>
</body>
</html>

上传mayikt_template.lua文件到/usr/local/openresty/nginx/lua/目录
mayikt_template.lua

local template = require "resty.template" 

content={
    message ="mayikt"
}     
template.render("mayikt.html",content)

3 配置nginx

## nginx配置指定模版引擎位置
set $template_root /usr/local/openresty/nginx/template;  
location /template {
default_type 'text/html';
content_by_lua_file /usr/local/openresty/nginx/lua/mayikt_template.lua;
}

重启nginx /usr/local/openresty/nginx/sbin/nginx -s reload

测试效果:
在这里插入图片描述

8 使用openresty发送Http请求获取数据

1. 引入lua发送http请求依赖
下载lua-resty-http-master
将http.lua、http_headers.lua 上传到/usr/local/openresty/lualib/resty
2. nginx添加

location /luahttpclient {
    default_type 'text/html';
    content_by_lua_file /usr/local/openresty/nginx/lua/lua_httpclient.lua;
}
  1. lua_httpclient.lua文件上传到/usr/local/openresty/nginx/lua
    lua_httpclient.lua
local uri_args=ngx.req.get_uri_args()
local productId = uri_args["productId"]
local host = {"192.168.149.1:4050/getProductsearchDetails","192.168.149.1:4050/getProductsearchDetails"}
local hash = ngx.crc32_long(productId)
ngx.say(hash)
hash = (hash % 2) + 1
ngx.say(hash)
backend = "http://"..host[hash]
ngx.say(backend)
local requestBody =backend.."?productId="..productId
ngx.say(requestBody)
local http = require("resty.http")
local httpc = http.new()
local resp, err = httpc:request_uri(requestBody ,{
method = "GET"
})
if not resp then
ngx.say("request error :", err)
return
end

ngx.status = resp.status
--ngx.say(resp.status)
--ngx.say(resp.body)
local cjson = require "cjson"
local productJSON = cjson.decode(resp.body);
local data=productJSON.data;
--ngx.say(data.data.name)
local template = require "resty.template" 
content={
    name =data.name,
	mainImage =data.mainImage
}     
template.render("commodity_details.html",content)
  1. commodity_details.html文件上传到/usr/local/openresty/nginx/template
    commodity_details.html
<!DOCTYPE html>
<html>
<body>
	<meta charset="utf-8"/>
	<h1>{{name}}</h1>
	<img src="{{mainImage}}">
</body>
</html>

测试效果:
在这里插入图片描述
查询数据库/ES仅需要替换发送http请求依赖及相应代码即可。

openresty+lua代码优化说明

  1. lua代码发送http请求改为查询es数据,再加缓存,根据url把整个es数据缓存到lua中,这样只有第一次查es后期走缓存。
  2. 商品的价格、库存每次都访问数据源读取最新的数据。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
本文介绍了一个基于Spring Boot、Spring Cloud和Vue前后端分离的项目实战。这个项目是一个简单的在线商城,包含了用户注册、登录、商品展示、购物车、订单管理等功能。通过这个项目,读者可以深入理解前后端分离的架构模式和互联网应用的开发方式。 首先,文章介绍了前后端分离的基本概念和优势。前后端分离是将应用的前端和后端代码分开来开发,使得前端和后端具有独立的开发周期和技术栈,进而提高了开发效率和代码质量。同时,前后端分离还可以提供更好的用户体验和灵活性,对于互联网应用来说尤为重要。 接下来,文章介绍了项目的架构和技术栈。项目采用了Spring Boot和Spring Cloud框架来实现后端代码,采用MyBatis作为ORM框架和Redis作为缓存中间件。同时,项目还采用了Vue.js作为前端框架和Element UI组件库来实现前端页面。通过这些开源框架和组件,可以快速搭建一个前后端分离的互联网应用。 然后,文章介绍了项目的核心功能和代码实现。在用户注册和登录方面,项目采用了Spring Security框架和JWT令牌来实现用户认证和授权,保证了用户信息的安全性。在商品展示和购物车方面,项目采用了Vue.js来实现前端页面和事件处理。在订单管理方面,项目采用了MyBatis Plus来实现订单数据的持久化和分页查询。 最后,文章介绍了项目的测试和优化。通过对项目的压力测试和性能测试,文章发现项目还存在一些性能瓶颈和安全隐患,可以通过优化数据库查询、缓存配置和代码实现来提高应用的性能和安全性。 总之,这篇文章介绍了一个基于Spring Boot、Spring Cloud和Vue前后端分离的项目实战,通过实现一个在线商城的功能,展示了前后端分离的开发模式和互联网应用的开发技术栈。本文可以作为前后端分离开发的入门教程,也可以作为互联网应用开发的参考文档。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值