上一篇文章,分析了導致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。