上一篇文章,分析了导致GC的原因:内存中持有了大量的session。当时查代码比较粗心,竟没有看到一些显式使用session的地方。业务是这样的:在请求到来时,根据用户请求信息(如ip),获取用户所在地区,然后将该dp信息放到session中,后面Controller直接拿来使用,不必在进行判断。
先看地区Filter:
public class DqFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
final HttpServletRequest request = (HttpServletRequest) servletRequest;
final HttpServletResponse response = (HttpServletResponse) servletResponse;
//...
//获取地区代码
String dq = getDqCode(request);
//将地区信息放到session中
request.getSession().setAttribute("dqs", dq);
//...
filterChain.doFilter(request, response);
}
@Override
public void init(FilterConfig arg0) throws ServletException {
}
/**
*
* @param request
*/
private String getDqCode(HttpServletRequest request) {
String dq = ...;//获取地区信息
return dq;
}
}
再看Controller中:
@Controller
@RequestMapping(value = "/xxx")
@SessionAttributes("dqs")
public class QiuzhiController extends BaseController {
@RequestMapping()
public String execute(... @ModelAttribute("dqs") String dq...)
throws Exception {
//获取dq信息
return "index";
}
}
coding作者的本意,可能是想节省根据ip获取dp信息的开销,因此创建一个session把这个信息放进去。但确实弄巧成拙了,每一次会话,都会往里面放东西,积少成多。
其实还有另外一个“祸手”加重了问题的严重性。我们的系统架构,通过在项目根目录下放置一个ok.html来做心跳检测,访问成功就认为服务在,不通就认为服务死,但这个检测机制有一个大问题,即不是针对工程,而是针对域名。由于该项目是城市站,对应了n个城市二级域名,如bj.demo.com、sh.demo.com......因此访问量相当于自身真实访问量的n倍,具体到我们这里,可能就是100倍,以至于流量的99%就是心跳,每小时上千万的pv。
那这些心跳会导致什么呢?没错,由于Filter的url-pattern配置的是/*,因此ok.html请求也能进来,而这个请求,只是一个curl简单请求,不会携带cookies,亦不会携带jsessionid,好了,每次请求都会创建一个session,并往会话中存放这个地区信息。
鉴于上面的分析,笔者把使用Session的地方都删掉,改用request.setAttribute,其实request也没有必要,因为获取地区信息本身也不太耗费性能。
下面是当前的JVM的GC情况:
没有GC了。
再看下JVM堆内存:
非常平稳了!
总结:
互联网项目不同于普通的后台系统,访问量巨大,即便没有上面的心跳,这个问题尽早也会因为某种原因,如(大量的爬虫抓取行为)而产生恶劣的影响。所以要禁用session。