文章目录

  • 前言
  • 页面缓存+URL缓存+对象缓存
    • 页面缓存
      • 取缓存
      • 手动渲染
    • URL缓存
    • 对象缓存
  • 页面静态化,前后端分离(常用)
    • GET POST区别
    • 如何解决超卖?重复卖?(简单版)
  • 静态资源优化
    • 多个JS/CSS组合,减少连接数
    • CDN优化
  • 总结

前言

本篇重点学习页面缓存,页面静态化,剩下的了解即可。

页面缓存+URL缓存+对象缓存

业务的瓶颈就是数据库,而这些页面(大粒度),对象(小粒度)都需要查询数据库 所以最大力度加缓存。

页面缓存

什么是页面缓存?我们访问一个页面的时候,不是让服务端渲染而是从缓存里边去,如果找到返回给客户端,如果没有再让服务端渲染并返回给客户端,同时保存到redis中。
与之前没有页面缓存相比,省时省力在哪?
首先,之前的方法是我们每次请求一个页面,服务端都需要从数据库中查询相关数据并渲染到页面中然后返回给客户端
页面缓存如何改进的? 直接从缓存中获取页面的信息并直接返回给客户端。 省去查询数据库和渲染页面。什么是渲染页面?在服务端渲染页面时从接受请求开始,查询数据库,然后加载JS/CSS文件再将动态数据添加到页面中,最后返回一个完整的html页面的过程

以商品列表为例
如何实现?1取缓存 2缓存未命中的话手动渲染页面并返回

取缓存

//如果缓存中有页面且TTL没有过期
String html = redisService.get(GoodsKey.getGoodsList, "", String.class);if(!StringUtils.isEmpty(html)) {return html;}

手动渲染

		String html = redisService.get(GoodsKey.getGoodsList, "", String.class);if(!StringUtils.isEmpty(html)) {return html;}//如果没有先查询数据库List<GoodsVo> goodsList = goodsService.listGoodsVo();//添加到页面中model.addAttribute("goodsList", goodsList);SpringWebContext ctx = new SpringWebContext(request,response,request.getServletContext(),request.getLocale(), model.asMap(), applicationContext );//手动渲染,goods_list是具体html文件,要不然thymeleafViewResolver也不知道页面长啥样啊html = thymeleafViewResolver.getTemplateEngine().process("goods_list", ctx);//存储到缓存中if(!StringUtils.isEmpty(html)) {redisService.set(GoodsKey.getGoodsList, "", html);}return html;

这里需要注意的是,我们将页面缓存到redis时都会设置有效期,过了有效期我们就需要重新从数据库中加载

URL缓存

URL缓存本质上就是页面缓存的一种,只不过需要在地址栏中传参数。区别就是get和set方法都要有路径的参数

    @RequestMapping(value="/to_detail2/{goodsId}",produces="text/html")@ResponseBodypublic String detail2(HttpServletRequest request, HttpServletResponse response, Model model,MiaoshaUser user,@PathVariable("goodsId")long goodsId) {model.addAttribute("user", user);//取缓存String html = redisService.get(GoodsKey.getGoodsDetail, ""+goodsId, String.class);if(!StringUtils.isEmpty(html)) {return html;}//手动渲染GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId);model.addAttribute("goods", goods);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;}model.addAttribute("miaoshaStatus", miaoshaStatus);model.addAttribute("remainSeconds", remainSeconds);
//        return "goods_detail";SpringWebContext ctx = new SpringWebContext(request,response,request.getServletContext(),request.getLocale(), model.asMap(), applicationContext );html = thymeleafViewResolver.getTemplateEngine().process("goods_detail", ctx);if(!StringUtils.isEmpty(html)) {redisService.set(GoodsKey.getGoodsDetail, ""+goodsId, html);}return html;}

对象缓存

像我们之前缓存user一样,每次从缓存中取出对应token的user。但我们这里的有效期是动态的,也就是用户在有效期没过期之前登录了就会自动延长有效期(具体就是从缓存里边查询出来不为空,就延长)。和之前的静态有效期有区别。

页面静态化,前后端分离(常用)

为什么有这个呢?
你如果只做了页面缓存,会发现渲染页面其实本质上还是服务端做
但你做了页面静态化,浏览器会将html页面缓存在客户端,这样页面数据渲染时客户端做,遇到动态的数据再去请求服务端。

前端:对于动态数据我们需要获取,添加以下方法


function render(detail){//获取相关参数var miaoshaStatus = detail.miaoshaStatus;var  remainSeconds = detail.remainSeconds;var goods = detail.goods;var user = detail.user;if(user){$("#userTip").hide();}$("#goodsName").text(goods.goodsName);$("#goodsImg").attr("src", goods.goodsImg);$("#startTime").text(new Date(goods.startDate).format("yyyy-MM-dd hh:mm:ss"));$("#remainSeconds").val(remainSeconds);$("#goodsId").val(goods.id);$("#goodsPrice").text(goods.goodsPrice);$("#miaoshaPrice").text(goods.miaoshaPrice);$("#stockCount").text(goods.stockCount);countDown();
}
$(function(){//countDown();getDetail();
});function getDetail(){var goodsId = g_getQueryString("goodsId");$.ajax({url:"/goods/detail/"+goodsId,type:"GET",success:function(data){if(data.code == 0){render(data.data);}else{layer.msg(data.msg);}},error:function(){layer.msg("客户端请求有误");}});
}function countDown(){
//倒计时函数var remainSeconds = $("#remainSeconds").val();var timeout;if(remainSeconds > 0){//秒杀还没开始,倒计时$("#buyButton").attr("disabled", true);$("#miaoshaTip").html("秒杀倒计时:"+remainSeconds+"秒");timeout = setTimeout(function(){$("#countDown").text(remainSeconds - 1);$("#remainSeconds").val(remainSeconds - 1);countDown();},1000);}else if(remainSeconds == 0){//秒杀进行中$("#buyButton").attr("disabled", false);if(timeout){clearTimeout(timeout);}$("#miaoshaTip").html("秒杀进行中");}else{//秒杀已经结束$("#buyButton").attr("disabled", true);$("#miaoshaTip").html("秒杀已经结束");}
}

后端:需要取出相关数据并返回,这里注意方法要加@ResponseBody 因为是返回数据,不是返回页面了。

    @RequestMapping(value="/detail/{goodsId}")@ResponseBodypublic Result<GoodsDetailVo> detail(HttpServletRequest request, HttpServletResponse response, Model model,MiaoshaUser user,@PathVariable("goodsId")long goodsId) {//查询数据库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;}GoodsDetailVo vo = new GoodsDetailVo();vo.setGoods(goods);vo.setUser(user);vo.setRemainSeconds(remainSeconds);vo.setMiaoshaStatus(miaoshaStatus);返回数据return Result.success(vo);}

这样是不是感觉比页面缓存还慢呀,因为每次访问时确实是浏览器解析页面,但动态数据都需要去加载动态数据,而加载动态数据就需要查询数据库。而页面缓存直接可以从缓存中取。其实这里还会有个配置,当没过期时,浏览器直接加载缓存中的静态页面。无需请求服务端,当过期时,则需要重新访问服务器重新加载动态数据。若在过期时间内,我们更新了数据,客户端不会自动收到提示,我们需要主动提示客户端我们更新数据了(这部分具体实现,之后会说,大家先了解这些原理)。

spring.resources.add-mappings=true
//过期时间
spring.resources.cache-period= 3600
spring.resources.chain.cache=true 
spring.resources.chain.enabled=true
spring.resources.chain.gzipped=true
spring.resources.chain.html-application-cache=true
spring.resources.static-locations=classpath:/static/

当我们点击秒杀时前端:

function doMiaosha(){$.ajax({url:"/miaosha/do_miaosha",type:"POST",data:{goodsId:$("#goodsId").val(),},success:function(data){if(data.code == 0){window.location.href="/order_detail.htm?orderId="+data.data.id;}else{layer.msg(data.msg);}},error:function(){layer.msg("客户端请求有误");}});}

需要将商品id传递过去 ,这里为什么是post(重点)

GET POST区别

这道题面试会经常问,网上答案很多,最重要的区别是以下两点(不要再说查询数据和请求数据了,这俩本质上都能查询和请求)
GET 幂等 无论你get多少次,服务端数据不会变化,且状态不会改变
POST 显然不幂等 你提交数据(新增,添加,修改,删除),服务端数据可能会变化,会改变状态。
改变状态怎么理解?比如登录方法 客户端请求方式为POST ,服务端数据不会发生改变,但是登录完了之后会创建session啊,生成token等等一系列操作。

为什么 <a href="/delete?id=xxx">是很严重的错误?
1 首先a标签是超链接 它是get方法,服务端数据不应该发生变化。
2 其次用户很容伪造一些get方法来删除数据,因为get方法主要通过路径参数来删除数据。

改为post就安全了嘛? 是的 post方法通过请求体传参,而请求体里边可以包含token,我们更容易通过token先校验然后再取数据操作。

如何解决超卖?重复卖?(简单版)

解决超卖
本节课解决超卖的方式很简单,就是将更新数据库时的SQL语句加个判断条件,如果库存大于0则执行更新。为什么在这里加可以,因为数据库系统 会采用MVCC机制(主页里讲过),修改操作(增,删,改)sql语句会加锁,所以多线程并发访问时,一个表只会有一个线程修改,所以我们可以直接在SQL语句中加上判断条件。
解决重复卖?
什么是重复卖?一个用户对于秒杀商品购买了两次
如何解决 为相应的字段的添加唯一索引。比如秒杀商品表中有用户id和商品id字段我们为这两个字段加上唯一索引。这样用户id和商品id的字段值在此表中只能有一个,重复秒杀会报错。
唯一索引并不是指“这张表中只有一个索引”,而是指“这个索引要求字段值唯一”。一张表可以有多个索引,包括多个普通索引和多个唯一索引。

普通索引:允许表中的字段值重复。例如,在 users 表的 username 字段上创建普通索引,可以快速查找 username,但同一个 username 可以出现多次。
唯一索引:要求表中的字段值必须唯一。如果尝试插入或更新违反唯一性约束的数据,数据库会抛出错误。

静态资源优化

这些在我们代码中体现不出来,所以自行search吧 。面试中也不是特别重要

多个JS/CSS组合,减少连接数

CDN优化

总结

并发大的瓶颈 是在于数据库
如何解决?
加缓存,首先用户发起请求,浏览器通过页面静态化,可以直接把页面缓存到浏览器,请求到达服务端之前请求会首先访问CDN节点。通过CDN,继续到服务端之前 还可以加ngix缓存,ngix没有就是服务端的页面缓存,然后在细粒度是对象缓存,最后才是数据库。

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

QCC系列显示交互层的自研技术突破与实践

在音频设备智能化进程中&#xff0c;显示交互的流畅度与兼容性已成为用户体验的核心指标。传统方案中&#xff0c;TFT 彩屏与多语言适配常面临硬件驱动冲突、功耗失控、字符显示错乱等问题。作为高通平台十年级方案商&#xff0c;腾泰技术在 QCC 系列中聚焦显示交互层的自研技术…

JMeter 实现 Protobuf 加密解密

一、 .proto文件编译成.jar文件 相关依赖下载详见&#xff1a;将 message.proto 编译成 .jar文件 1.依赖于java编译环境 2.依赖protoc编译jar包 编译目录 1.创建一个根目录&#xff1a;protobuf 2.在protobuf下创建build、output、lib、src目录 lib&#xff1a;放 protobu…

发票识别在费控系统应用剖析

一、发票识别与费控系统的融合价值1.1 解决传统费控痛点效率瓶颈突破&#xff1a;将人工处理每张发票的5-8分钟缩短至秒级自动识别准确性飞跃&#xff1a;关键字段识别准确率从人工的95%提升至99%以上合规性强化&#xff1a;自动对接税务系统验真&#xff0c;虚假发票识别率提升…

Rust实战:决策树与随机森林实现

基于 Rust 实现决策树(Decision Tree)和随机森林(Random Forest)的实例 Linfa的基本定义 Linfa是意大利语中“淋巴”(lymph)的意思,在医学领域指淋巴系统相关的结构或功能。淋巴系统由淋巴管、淋巴结、脾脏等组成,负责免疫防御和体液平衡。 Linfa在生物学中的作用 …

9. isaacsim4.2教程-ROS加相机/CLOCK

在本示例中&#xff0c;我们将学习如何&#xff1a; 向场景中添加额外的相机并将其安装在机器人上 添加相机发布器&#xff08;Camera Publishers&#xff09; 通过 rostopics 发送真实的合成感知数据&#xff08;ground truth synthetic perception data&#xff09; 前提…

微信小程序171~180

1.封装购物车接口API import http from /utils/httpexport const reqAddCrt ({ goodsId, count, ...data }) > {return http.get(/cart/addToCart/${goodsId}/${count}, data) }export const reqCartList () > {return http.get(/cart/getCartList) }export const reqU…

修改 docker 容器的挂载配置(保持数据不丢的情况)

一、核心原理Docker 容器的运行时配置&#xff08;包括挂载&#xff09;是启动时确定的&#xff0c;一旦启动无法直接修改。因此&#xff0c;需通过以下步骤实现&#xff1a;保存原容器中的数据&#xff08;避免丢失&#xff09;&#xff1b;基于原镜像创建新容器&#xff0c;同…

MVCC(多版本并发控制)介绍及实现原理

一、什么是MVCC&#xff1f; MVCC&#xff08;Multi-Version Concurrency Control&#xff0c;多版本并发控制&#xff09;是数据库中用于解决并发访问问题的一种机制。它通过为数据维护多个版本&#xff0c;让读写操作在不同版本上独立进行&#xff0c;从而避免了传统锁机制中…

密码学基础概念详解:从古典加密到现代密码体系

一、引言&#xff1a;为什么我们需要密码学&#xff1f; 在数字化时代&#xff0c;信息已成为核心生产要素&#xff0c;而信息安全则是保障社会运转的基石。当我们在电商平台输入银行卡密码时&#xff0c;当我们通过即时通讯工具发送私密消息时&#xff0c;当企业在云端存储核心…

小鹏汽车视觉算法面试30问全景精解

小鹏汽车视觉算法面试30问全景精解 ——智能驾驶 车路协同 视觉创新:小鹏汽车视觉算法面试核心考点全览 前言 小鹏汽车作为中国智能电动汽车的创新引领者,致力于通过AI与自动驾驶技术推动智能出行的变革。小鹏视觉算法团队深耕自动驾驶感知、车路协同、智能座舱、3D重建…

程序是如何生成的-以c语言为例

一&#xff0c;序言 从代码到能跑的程序&#xff0c;整个过程就像 “把外文翻译成母语&#xff0c;再组装成能直接用的东西”&#xff0c;一步步来更清楚&#xff1a; 源代码&#xff08;程序员写的代码&#xff0c;如C语言文件&#xff09;↓ 预处理&#xff08;处理#开头的命…

风险识别清单:构建动态化的风险管理体系

在项目管理实践中&#xff0c;风险识别是确保项目成功的关键环节。PMBOK提出的风险提示清单&#xff08;Prompt List&#xff09;为项目团队提供了一个系统化的思考框架&#xff0c;帮助突破个人经验局限&#xff0c;实现更全面的风险覆盖。这一工具的价值不仅在于其提供的标准…

从“点状用例”到“质量生态”:现代软件测试的演进、困局与破局

测试的三次范式跃迁业务高速迭代下的四大困局质量工程化&#xff1a;流程、平台、度量三位一体左移与右移&#xff1a;把缺陷扼杀在摇篮&#xff0c;也把监控铺到坟墓自动化金字塔的再平衡&#xff1a;UI、API、单元、契约、e2e数据驱动测试&#xff1a;从“拍脑袋”到“科学实…

【C++】继承和多态扩展学习

目录 1. 菱形虚拟继承原理剖析 1.1.虚基表 2. 单继承和多继承的虚函数表深入探索 2.1 单继承虚函数表深入探索 2.2 多继承虚函数表深入探索 ​编辑 2.3 菱形继承、菱形虚拟继承 3. 继承和多态考察的一些常见问题 1. 菱形虚拟继承原理剖析 继承的文章中我们讲到C的多继承…

Visual Studio Code 远端云服务器开发使用指南

目录 一、下载安装 1、官方下载 2、下载加速方案 二、基于Ubuntu系统的开发环境搭建方案 1、开发环境配置 2、云服务器架构 3、工作流程关系 4、总结 三、推荐插件 1、免配置插件 1. Remote-SSH - 远程登录Linux服务器 2. C/C - 必备的C/C开发插件 3. C/C Extensi…

技术演进中的开发沉思-41 MFC系列:定制 AppWizard

MFC开发&#xff0c;最为重要的无非就是用“MFC AppWizard” 对话框做开发了&#xff0c;第一次使用感觉像拆收音机的孩子 —— 左边是项目类型选择&#xff0c;右边是一堆打勾的选项&#xff0c;点完 “完成”&#xff0c;屏幕上就冒出了能直接编译运行的窗口程序。那时还不知…

Libevent(3)之使用教程(2)创建事件

Libevent(3)之使用教程(2)创建事件 Author: Once Day Date: 2025年6月29日 一位热衷于Linux学习和开发的菜鸟&#xff0c;试图谱写一场冒险之旅&#xff0c;也许终点只是一场白日梦… 漫漫长路&#xff0c;有人对你微笑过嘛… 本文档翻译于&#xff1a;Fast portable non-bl…

Kotlin 作用域函数 let 的实现原理

Kotlin 中的 let 是一个 标准库扩展函数&#xff0c;它广泛用于作用域函数&#xff08;Scope Functions&#xff09;中&#xff0c;尤其适用于对可空对象&#xff08;nullable&#xff09;做非空判断并执行代码块的场景。 示例代码 val name: String? "123" name?…

从FDTD仿真到光学神经网络:机器学习在光子器件设计中的前沿应用工坊

FDTD仿真与光学神经网络的基础概念 FDTD&#xff08;时域有限差分&#xff09;是一种数值方法&#xff0c;用于求解麦克斯韦方程组&#xff0c;广泛应用于光子器件设计。光学神经网络通过光波导、衍射元件等物理结构实现矩阵运算&#xff0c;具有低能耗、高并行的优势。 机器学…

在Ubutu22系统上面离线安装Go语言环境【教程】

0.引言 Go语言&#xff08;又称Golang&#xff09;是Google开发的一种静态强类型、编译型、并发型编程语言&#xff0c;由Robert Griesemer、Rob Pike和Ken Thompson于2007年开始设计&#xff0c;2009年正式发布。 1.到官网下载压缩包 2.从win10系统离线上传压缩包给ubuntu22…