背景
在某些电商促消活动中需要搞活动,对某些页面的访问量(QPS)往往会非常高。如果直接读数据库,肯定DB会承受不住。那比较常见的方案就是让大部分相同信息的请求都尽可能压在cache上来缓解DB的压力,从而尽可能去满足高并发访问的需求
优化:这种缓存技术一般用于不会经常变动信息,并且访问次数较多的页面,这样就不用每次都动态加载。
@ResponseBody
@RequestMapping(value = "/to_list",produces = "text/html")
public String to_list(HttpServletResponse response, HttpServletRequest request,Model model, MiaoshaUser user){//为了方便手机端 移动端,将参数放在请求中
/*页面缓存*/
String html = redisService.get(GoodsKey.getGoodsList,"",String.class);
if (!StringUtils.isEmpty(html)){//从redis 中取, 取得到返回,取不到 手动渲染
return html;
}
model.addAttribute("user",user);
List<GoodsVo> goodsList = goodsService.getGoodsList();
model.addAttribute("goodsList",goodsList);
// return "goods_list";
/*手动渲染 利用Thymeleaf 的 ThymeleafViewResolver*/
SpringWebContext ctx = new SpringWebContext(request,response,request.getServletContext(),request.getLocale(),
model.asMap(), applicationContext); // model 就是将参数存入 ,其中的所有参数 都是为了将页面渲染出来 放入其中,在返回一个静态的html源码
/*利用 getTemplateEngine()方法的process() 方法,需要传入模板名称和context 变量*/
html = thymeleafViewResolver.getTemplateEngine().process("goods_list",ctx);//ctx + 模板 返回源码
/*得到手动渲染的模板*/
if (!StringUtils.isEmpty(html)){ //不是空,存入缓存
redisService.set(GoodsKey.getGoodsList,"",html);
}
return html;
}
当访问list页面的时候,从缓存中取如果取到就返回这个html,(这里方法的返回格式已经设置为text/HTM,这样就是返回html的源代码),如果取不到,利用ThymeleafViewResolver的getTemplateEngine().process和我们获取到的数据,渲染模板
并存入缓存,然后返回给前端。
一般这个页面缓存时间,也不会很长,防止数据的时效性很低。但是可以防止短时间大并发访问。
与页面缓存相似
相比页面缓存是更细粒度缓存 + 缓存 更新。在实际项目中, 不会大规模使用页面缓存,因为涉及到分页,一般只缓存前面1-2页。
对象缓存就是 当用到用户数据的时候,可以从缓存中取出。比如:更新用户密码
/** 将从数据库取对象 利用优化变为从缓存中取 对象数据
/** 将从数据库取对象 利用优化变为从缓存中取 对象数据
*
* @param id
* @return
*/
public MiaoshaUser getById(Long id){
//取缓存
MiaoshaUser user = redisService.get(MiaoshaUserKey.getByName,""+id,MiaoshaUser.class);
if (user != null){//取到返回
return user;
}
//取不到 从数据库里取 再放到 缓存里
user = miaoshaUserDao.getById(id);
if (user !=null){
redisService.set(MiaoshaUserKey.getByName,""+id,user);
}
return user;
}
在进行对象更新时进行数据库加密更新操作后,直接删除redis内的对应用户的redis值,但是token 缓存不能删除,而是应该修改重新设置,不然就无法登陆了(因为我们登陆是从缓存中取),所以要进行更新操作,再将更新后的对象信息存入redis中
/**更新用户密码方法: 涉及到对象缓存 ---若更新对象缓存的相关的数据 要处理缓存
* 同步数据库和缓存的信息,不然会造成数据不一致的情况
* */
public boolean updatePassword(String token,long id,String formPassword){
/*根据id 取出对应用户,更新对应数据*/
MiaoshaUser user = getById(id);
if (user == null){//如果没有该用户,说明 手机号不存在
throw new GlobalException(CodeMsg.LOGIN_ERROR_USER_NOT_ERROR);
}
// 更新数据库 信息
MiaoshaUser updateUser = new MiaoshaUser();
updateUser.setId(id);
/*设置密码 到数据库 ,这时候 应该是formPassword ,更新密码一定是先在前端填入 密码,然后前端做 一次 加密传进来*/
updateUser.setPassword(Md5Util.formPassToDBPass(formPassword,user.getSalt()));
miaoshaUserDao.updatePassword(updateUser);
// 更新完数据库信息,防止缓存中信息不一致,处理缓存 且涉及到所有该对象的缓存都需要处理
// 一个 是 根据 token 获取对象,所以需要更新 token key 的缓存对象数据, 一个是根据id 获取对象,同理
/** 处理缓存:
* 1. 删除相关缓存数据
* 2. 更新相关缓存中的数据
* */
redisService.delete(MiaoshaUserKey.getByName,""+id);//该对象缓存可以直接删,因为没有可以从数据取
//但是token 缓存不能删除,而是应该修改重新设置,不然就无法登陆了(因为我们登陆是从缓存中取)
user.setPassword(updateUser.getPassword());
//将对象 携带新的密码放入缓存
redisService.set(MiaoshaUserKey.token,token,user);
return true;
}
1.更新缓存后保证数据库中数据和缓存数据一致性。 可以选择淘汰缓存和更新缓存
淘汰:性价比高,仅仅会设计一次未命中,再从数据库取
更新:操作复杂,涉及到对应的业务逻辑。
更新缓存很直接,但是涉及到本次更新的数据结果需要一堆数据运算(例如更新用户余额,可能需要先看看有没有优惠券等),复杂度就增加了。而淘汰缓存仅仅会增加一次cache miss,代价可以忽略,所以建议淘汰缓存)
2.若一定要更新,比如登陆状态下,更新密码,那一定要更新缓存,因为 session从缓存中拿,密码不一致,会导致登陆失效
这里先淘汰缓存,在更新数据库信息。(若先更新在淘汰,淘汰失败缓存中有脏数据)
保证数据一致性。