项目属于课上作业迭代,请不要用于真实开发和调试。
本文非解决方案文章,仅作为记录作业使用,类似于写点心路历程。
作业日期:2020年6月09日,作业09
作业安排:如何进行优化的秒杀
代码详见github:github.com/hitofuncy
零、优化作业的特别说明
在本篇中将详细讲述秒杀系统的具体优化的方法,不涉及到秒杀系统的实现。
一、秒杀系统优化之 页面缓存技术
基本思路:
首先页面优化技术包括1. 页面缓存+URL缓存+对象缓存 2.页面静态化,前后端分离 3.静态资源优化 4.CDN优化
在项目中,我们在 list的controller中添加字段produces参数 ,接着修改方法为String返回的方法、建立一个GoodKey缓存存取的类作为页面的缓存,然后在controller中检查redis是否为空,如果没有空就取缓存,接着我们手动渲染,,使用Thymeleafview Resolver 进行渲染,引入这个自动装配,使用thumeleafviewResolver 的方法get末班endine进行渲染。为了能够实现手动渲染,就要使用一个SpringWebContext 创建一个对象,增加request respones 这两个参数,做好前期的工作了之后基本的渲染就完成了,接着就可以使用已经渲染的内容进行字符串的构建然后返回一个html文本,浏览器就可以进行正常显示。
别忘了,在方法上添加@ResponseBody 要不然会无法显示。
易错的坑:
我在做这个实验的时候,出现了浏览器乱码的情况,期初我以为是thy的问题,因为视频中的thy版本比较老,这样子的话我把版本换回老版本,接着就发现了老版本的情况更为糟糕,老版本的语法限定十分复杂,这样让我改了好一会儿,改好之后还是出现这个乱码情况,我上网查询了一下,期初以为这个问题是tomcat问题,于是我手动把spring的tomcat给更改了,发现不行,接着又调节的IDEA这个字节编码还是不行,在这样的情况下,我甚至检查了html本身的字节编码格式和保存的格式,依然没有解决问题,然后我就看到了一个在google上的解决方案的:
@RequestMapping(value="/to_list", produces="text/html;charset=UTF-8")
这样问题就莫名其妙的解决了,你说好不好玩,改了我一整天。
二、URL缓存以及对象缓存 -- 更细粒度缓存
这个技术就是对象进行缓存,我们不可可能全部页面都缓存,这次的对象缓存很不一样,我们做分布式的时候做了一个token 我们先改造一下这个方法。先写一个取缓存,我们使用redisService进行取缓存,如果取出的数据是空的,我们就去数据库查询,如我们数据库中存在,那么我们更新放入redis中,最后返回一个user.下次访问的时候就从redis中取就快了。
例如我们还存在一个更新密码的方法 参数是id passwordNew ,那我们就需要先取一下id对应的user对象,我们如果没有获取不到,说明了用户不存在,如果取到之后,我们创建一个新的对象,复制源对象进去,然后进行修改密码,修改密码时使用MD5加密,然后使用dao层的update进行更新。一般为了速度,一般不直接修改提交。更新完数据库之后就要更新缓存,我们可以更新掉redisService。建立delete方法,接着修改逻辑完成删除redis的方法。删除MiaoshAUserKey 更新MiaoshaUserKey,然后设置一下user的密码。总体的说就是更新数据库-》更新缓存。其中Dao层的方法可以直接创建以后自行编写@Update。
主要的方法如下:
public MiaoshaUser getById(long id) {
//取缓存
MiaoshaUser user = redisService.get(MiaoshaUserKey.getById, ""+id, MiaoshaUser.class);
if(user != null) {
return user;
}
//取数据库
user = miaoshaUserDao.getById(id);
if(user != null) {
redisService.set(MiaoshaUserKey.getById, ""+id, user);
}
return user;
}
public boolean updatePassword(String token, long id, String formPass) {
//取user
MiaoshaUser user = getById(id);
if(user == null) {
throw new GlobalException(CodeMsg.MOBILE_NOT_EXIST);
}
//更新数据库
MiaoshaUser toBeUpdate = new MiaoshaUser();
toBeUpdate.setId(id);
toBeUpdate.setPassword(MD5Util.formPassToDBPass(formPass, user.getSalt()));
miaoshaUserDao.update(toBeUpdate);
//处理缓存
redisService.delete(MiaoshaUserKey.getById, ""+id);
user.setPassword(toBeUpdate.getPassword());
redisService.set(MiaoshaUserKey.token, token, user);
return true;
}
注意:千万不要调用别人的DAO,所有的操作在service中进行操作。别人可能是有缓存的,不能去覆盖别人的数据。
三、页面静态化:
页面静态化常用技术是AngularJS、Vue.js 这两项技术。
优点:利用浏览器的缓存
项目解析部署:
在项目中,我们看上一次更新密码的方法,必须先更新数据库再更新缓存,不能颠倒,因为修改缓存一旦数据库没有更新成功,那么就会造成数据库的密码不改变,每次是先验证缓存再数据库的,如果缓存中有错误的数据,那么这个数据可能用于攻击,或是在另外的分布式的服务器上用更新的密码无法访问账号,因为在那个redis上没有缓存更新之后的密码。数据不一致。
页面静态化在项目中部署:我们挑选详情页controller ,删除取缓存,手动渲染也需要,获取商品的详情,我们得编写一个网页面传值的VO ,这个VO里存着状态、remainSeconds、商品信息也放进去、user。接着我们new一个VO对象,我们set填写相关的内容,修改方法为Result<>返回,最后返回Result.success(data);
接着改造我们的页面,我们修改商品详情页面的html第33行的
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("客户端请求有误");
}
});
}
接着编写一些获取参数的:
//设定时间格式化函数,使用new Date().format("yyyyMMddhhmmss");
Date.prototype.format = function (format) {
var args = {
"M+": this.getMonth() + 1,
"d+": this.getDate(),
"h+": this.getHours(),
"m+": this.getMinutes(),
"s+": this.getSeconds(),
};
if (/(y+)/.test(format))
format = format.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
for (var i in args) {
var n = args[i];
if (new RegExp("(" + i + ")").test(format))
format = format.replace(RegExp.$1, RegExp.$1.length == 1 ? n : ("00" + n).substr(("" + n).length));
}
return format;
};
继续在获取Datail方法中编写一个变量获取一个userid,接着填写get参数,接着编写成功和失败的处理,错误则打出消息--客户端请求有误,如果正确就回调,如果code == 0 就是成功的,失败则返回消息,接着我们编写出另一个function方法render 的会调方法,详情见以下:
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();
}
引入数据就可以手动填充我们的页面,就如以上填充,接着调用countDown 方法就行了。
坑点:秒杀时间的判断遗漏
这个解决方案就是在countDown方法下新增一个字段$( #miaoshaTip ).html("秒杀倒计时" + 秒);
四、秒杀静态化
同样的,需要对调用的网页进行静态化处理,这里就不多说了,内容大致一样。
修改好页面之后,我们修改@RequestMapping("/do_miaosha")这个controller ,修改的方法同样的是把这个方法变成橙一个返回result.sucess的一个方法,添加@ResponeBody这个标记,修改方法使用post。为什么用post?post是get是幂等 Post是向后端提交的数据。
@RequestMapping(value="/do_miaosha", method=RequestMethod.POST)
@ResponseBody
public Result miaosha(Model model,MiaoshaUser user,
@RequestParam("goodsId")long goodsId) {
model.addAttribute("user", user);
if(user == null) {
return Result.error(CodeMsg.SESSION_ERROR);
}
//判断库存
GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId);//10个商品,req1 req2
int stock = goods.getStockCount();
if(stock <= 0) {
return Result.error(CodeMsg.MIAO_SHA_OVER);
}
//判断是否已经秒杀到了
MiaoshaOrder order = orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId);
if(order != null) {
return Result.error(CodeMsg.REPEATE_MIAOSHA);
}
//减库存 下订单 写入秒杀订单
OrderInfo orderInfo = miaoshaService.miaosha(user, goods);
return Result.success(orderInfo);
}
接着编写html页面添加阿贾克斯的请求获取,如上一个方法一样,这样就可以获得相关的静态页面的要求,这里就不做过多的赘述。
五、订单详情静态化:跟之前的套路也是一样的套路。
建立OrderDetailVO 编写OrderController 添加一个返回Result < MiaoshaUser>的info方法。
具体见下方:
@RequestMapping("/detail")
@ResponseBody
public Result info(Model model, MiaoshaUser user,
@RequestParam("orderId") long orderId) {
if(user == null) {
return Result.error(CodeMsg.SESSION_ERROR);
}
OrderInfo order = orderService.getOrderById(orderId);
if(order == null) {
return Result.error(CodeMsg.ORDER_NOT_EXIST);
}
long goodsId = order.getGoodsId();
GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId);
OrderDetailVo vo = new OrderDetailVo();
vo.setOrder(order);
vo.setGoods(goods);
return Result.success(vo);
}
接着在CodeMsg.java添加订单模块相关的错误码。编写Dao层返回商品数量、页面相关的信息请求完善,创建OrderService层中通过orderid返回OrderInfo信息的方法和getMiaoshaOrderByUserIdGoodsId方法。
经过一系列的调试之后就可以实现了。
六、静态资源优化
静态资源优化包括1. JS/CSS压缩,减少流量 2.多个JS/CSS组合,减少连接数 3. CDN就近访问
以上。