一、redis缓存
尝试对前端页面进行相应的优化,比较典型的是缓存,一些东西可以存在浏览器身上或者redis中,提高相应速度,降低后端压力。
1 页面缓存
这里以商品列表页面为例。
原来的商品列表页面是这样写的:
@RequestMapping("to_list")
public String toList(Model model,MiaoshaUser user){
if(user == null)
return "login";
model.addAttribute("user",user);
List<GoodsVo> goodsVoList = goodsService.getGoodsVoList();
model.addAttribute("goodsList",goodsVoList);
return "goods_list";
}
给他添加页面缓存:
@RequestMapping(value="/to_list", produces="text/html")
@ResponseBody
public String list(HttpServletRequest request, HttpServletResponse response, Model model,MiaoshaUser user) {
model.addAttribute("user", user);
//先尝试取缓存
String html = redisService.get(GoodsKey.getGoodsList, "", String.class);
if(!StringUtils.isEmpty(html)) {
return html;
}
List<GoodsVo> goodsList = goodsService.listGoodsVo();
model.addAttribute("goodsList", goodsList);
// return "goods_list";
SpringWebContext ctx = new SpringWebContext(request,response,
request.getServletContext(),request.getLocale(), model.asMap(), applicationContext );
//取不到,手动渲染(不太懂过程),再保存到redis
html = thymeleafViewResolver.getTemplateEngine().process("goods_list", ctx);
if(!StringUtils.isEmpty(html)) {
redisService.set(GoodsKey.getGoodsList, "", html);
}
return html;
}
对于商品详情页面的缓存,原来是这样写的:
@RequestMapping("/to_detail/{goodsId}")
public String toDetail(@PathVariable("goodsId") long goodsId,Model model, MiaoshaUser user){
if(user == null)
return "login";
model.addAttribute("user",user);
GoodsVo goodsVo = goodsService.getGoodsVoByGoodsId(goodsId);
model.addAttribute("goods",goodsVo);
long startAt = goodsVo.getStartDate().getTime();
long endAt = goodsVo.getEndDate().getTime();
long now = System.currentTimeMillis();
int miaoshaStatus = 0;//秒杀活动的状态,0-秒杀前;1-正在秒杀;2-秒杀结束
int remainSeconds = 0;//秒杀活动还剩多少秒
if(now < startAt){
miaoshaStatus = Constants.MiaoshaStatus.BEFORE_START;
remainSeconds = (int)(startAt-now)/1000;
}else if (now > endAt){
miaoshaStatus = Constants.MiaoshaStatus.AFTER_MIAOSHA;
remainSeconds = -1;
}else {
miaoshaStatus = Constants.MiaoshaStatus.ON_MIAOSHA;
remainSeconds = 0;
}
model.addAttribute("miaoshaStatus",miaoshaStatus);
model.addAttribute("remainSeconds",remainSeconds);
return "goods_detail";
}
现在改为如下,以goodsid作为区别:
@RequestMapping(value = "/to_detail/{goodsId}",produces = "text/html")
@ResponseBody
public String toDetail(@PathVariable("goodsId") long goodsId,Model model, MiaoshaUser user, HttpServletRequest request,
HttpServletResponse response) throws IOException{
if(user == null){
response.sendRedirect("/login/to_login");
return null;
}
model.addAttribute("user",user);
//先尝试从缓存中取
String html = redisService.get(GoodsKey.getGoodsDetail,""+goodsId,String.class);
if(!StringUtils.isEmpty(html)){
return html;
}
GoodsVo goodsVo = goodsService.getGoodsVoByGoodsId(goodsId);
model.addAttribute("goods",goodsVo);
long startAt = goodsVo.getStartDate().getTime();
long endAt = goodsVo.getEndDate().getTime();
long now = System.currentTimeMillis();
int miaoshaStatus = 0;//秒杀活动的状态,0-秒杀前;1-正在秒杀;2-秒杀结束
int remainSeconds = 0;//秒杀活动还剩多少秒
if(now < startAt){
miaoshaStatus = Constants.MiaoshaStatus.BEFORE_START;
remainSeconds = (int)(startAt-now)/1000;
}else if (now > endAt){
miaoshaStatus = Constants.MiaoshaStatus.AFTER_MIAOSHA;
remainSeconds = -1;
}else {
miaoshaStatus = Constants.MiaoshaStatus.ON_MIAOSHA;
remainSeconds = 0;
}
model.addAttribute("miaoshaStatus",miaoshaStatus);
model.addAttribute("remainSeconds",remainSeconds);
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;
}
}
2 对象缓存
就是对一个对象进行缓存,比如这里可以对MiaoshaUser这个对象进行缓存
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,""+user.getId(),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;
}
二、 解决卖超
@RequestMapping(value="/do_miaosha", method=RequestMethod.POST)
@ResponseBody
public Result<OrderInfo> 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);
}
通过数据库的方式操作(应该有更好的方法)
1.SQL语句限制
GoodsDao 减库存方法更改如下(防止多个用户同时秒杀到最后一件商品,出现负),本质是利用数据库锁。
@Update("update miaosha_goods set stock_count = stock_count - 1
where goods_id = #{goodsId} and stock_count > 0")
public int reduceStock(MiaoshaGoods g);
2.建立唯一索引(防止一个用户发送多个请求,秒杀到多件同类商品)
给用户建立唯一索引,如果插入几个相同用户,则订单创建失败
@Transactional
public OrderInfo createOrder(MiaoshaUser user, GoodsVo goods) {
OrderInfo orderInfo = new OrderInfo();
orderInfo.setCreateDate(new Date());
orderInfo.setDeliveryAddrId(0L);
orderInfo.setGoodsCount(1);
orderInfo.setGoodsId(goods.getId());
orderInfo.setGoodsName(goods.getGoodsName());
orderInfo.setGoodsPrice(goods.getMiaoshaPrice());
orderInfo.setOrderChannel(1);
orderInfo.setStatus(0);
orderInfo.setUserId(user.getId());
long orderId = orderDao.insert(orderInfo);
MiaoshaOrder miaoshaOrder = new MiaoshaOrder();
miaoshaOrder.setGoodsId(goods.getId());
miaoshaOrder.setOrderId(orderId);
miaoshaOrder.setUserId(user.getId());
orderDao.insertMiaoshaOrder(miaoshaOrder);
//将order存放到redis中
redisService.set(OrderKey.getMiaoshaOrderByUidGid, ""+user.getId()+"_"+goods.getId(), miaoshaOrder);
return orderInfo;
}
public MiaoshaOrder getMiaoshaOrderByUserIdGoodsId(long userId, long goodsId) {
//return orderDao.getMiaoshaOrderByUserIdGoodsId(userId, goodsId);
return redisService.get(OrderKey.getMiaoshaOrderByUidGid, ""+userId+"_"+goodsId, MiaoshaOrder.class);
}
部分参考:http://fossi.oursnail.cn/2019/04/23/miaosha/5.页面级高并发秒杀优化(Redis缓存+静态化分离)/