秒杀系统个人总结
对于高并发的项目,处理原则是尽量在上游处理,优先级:客户端-》后端内存-》redis-》数据库;如果能在客户端处理的,尽量不交给tomcat,能在tomcat处理的,尽量不要交给redis,毕竟tomcat-redis也存在网络交互,能在redis处理的,尽量不交给数据库,一般高并发的瓶颈都是在数据库。
另外可以使用nignx做负载均衡,横向扩展处理服务器,这时session一般就要保存在redis,这个也是分布式session;可以使用rabbitMQ作为订单的的处理,rabbitMQ是分布式消息队列系统,需要安装,个人感觉什么分布式开源系统,原理都是统一处理多个来源信息,支持集群;
在高并发环境下,可以限制用户某时间段内的访问次数,加验证码等手段减轻服务器压力;另外在高并发下也很容易出现数据错误的情况,例如这里的多个用户同时获得剩余商品都是1,然后就出现订单多于总商品的情况,对于这种情况,可以:1,在数据库表中合适的添加唯一值索引,2,利用rabbitMQ来延迟处理订单,rabbitMQ是通过队列的处理消息,成功进入队列的订单给用户返回排队中的提示信息,后面用户方面可以通过轮询的方式去查找数据库,如果数据库存在该用户的订单信息,即提示秒杀成功,如果数据库的商品为0且订单表不存在该用户信息,提示秒杀失败,否则提示排队中
1、页面静态化,页面缓存:
将页面的写成html,所有的数据都通过js异步获取,并且在页面中加入缓存功能,设置缓存时间,springboot提供缓存统一设置:
#static 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/
2、nignx负载均衡
upstream server_pool_miaosha{
server 127.0.0.1:8080 weight=1;
server 127.0.0.1:8081 weight=1;
}
server {
listen 81;
server_name localhost;
location / {
proxy_pass http://server_pool_miaosha;
}
3、分布式session
,随机生成一个token作为key,当前用户user作为value保存在redis中,并且将该token保存在客户端的cookie中,对于客户端每次请求都会自动的将cookie信息传递到服务器,这时我们就可以通过CooKi_NAME-TOKEN获取token,进而去redis获取用户信息
redisService.set(MiaoshaUserKey.token, token, user); Cookie cookie = new Cookie(COOKI_NAME_TOKEN, token); cookie.setMaxAge(MiaoshaUserKey.token.expireSeconds()); cookie.setPath("/"); response.addCookie(cookie);
@Override public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception { HttpServletRequest request = nativeWebRequest.getNativeRequest(HttpServletRequest.class); HttpServletResponse response = nativeWebRequest.getNativeResponse(HttpServletResponse.class); String paramToken = request.getParameter(MiaoshaUserService.COOKI_NAME_TOKEN); String cookieToken = getCookieValue(request, MiaoshaUserService.COOKI_NAME_TOKEN); if(StringUtils.isEmpty(cookieToken) && StringUtils.isEmpty(paramToken)) { return null; } String token = StringUtils.isEmpty(paramToken)?cookieToken:paramToken; return userService.getByToken(response, token); } private String getCookieValue(HttpServletRequest request, String cookiName) { Cookie[] cookies = request.getCookies(); if(cookies == null || cookies.length <=0){ return null; } for(Cookie cookie : cookies) { if(cookie.getName().equals(cookiName)) { return cookie.getValue(); } } return null; }4、隐藏秒杀地址
主要是通过客户端在调用秒杀接口前要先到服务器获取一个随机数,这个随机数会保存在redis中,然后在访问地址中带上这个随机数
@RequestMapping(value="/{path}/do_miaosha", method= RequestMethod.POST) @ResponseBody public Result<Integer> miaosha(Model model, MiaoshaUser user, @RequestParam("goodsId")long goodsId, @PathVariable("path") String path) {
客户端调用秒杀接口前:
$.ajax({ url:"/miaosha/path", type:"GET", data:{ goodsId:goodsId, verifyCode:$("#verifyCode").val() }, success:function(data){ if(data.code == 0){ var path = data.data; console.info("path:"+path); doMiaosha(path); }else{ layer.msg(data.msg); } },
客户端调用秒杀接口:
$.ajax({ url:"/miaosha/"+path+"/do_miaosha", type:"POST", data:{ goodsId:$("#goodsId").val(), }, success:function(data){ if(data.code == 0){ getMiaoshaResult($("#goodsId").val()); //window.location.href="/order_detail.htm?orderId="+data.data.id; }else{ layer.msg(data.msg); } },
这个即能达到隐藏接口的功能
5、接口限流:
public class AccessInterceptor extends HandlerInterceptorAdapter{ @Autowired MiaoshaUserService userService; @Autowired RedisService redisService; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if(handler instanceof HandlerMethod) { MiaoshaUser user = getUser(request, response); UserContext.setUser(user); HandlerMethod hm = (HandlerMethod)handler; AccessLimit accessLimit = hm.getMethodAnnotation(AccessLimit.class); if(accessLimit == null) { return true; } int seconds = accessLimit.seconds(); int maxCount = accessLimit.maxCount(); boolean needLogin = accessLimit.needLogin(); String key = request.getRequestURI();
通过自定义注解,添加拦截器,redis记录通过设置超时时间来限制该用户的某时间段内的访问次数
6、jmeter工具压测
一个完整的jmeter压测包括线程组,HTTP请求默认值,HTTP请求,CSV Data Set Config ,聚合报告