異步處理ServletRequest引發的血案

我們的APP生產上出了一次比較嚴重的事故,許多用戶投訴登錄後能看到別人的信息,收到投訴後我們就開始查找問題,一般這樣的問題都是線程安全引起的,所以查找原因的思路也是按線程安全的思路去查。

業務場景是這樣的,用戶登錄後,點擊一個頁面查看信息,這個信息顯示了別人的信息。

登錄交易大致流程如下:

//一系列驗證
session.setAttribute("id",id); //證件號放入session
//其他操作

查看信息交易的流程如下:

session = request.getSession();
if(session != null && session.getAttribute("LoginStatus") == True) {
    String id = session.getAttribute("id");
    Information info = queryInfo(id);
    return info;
} else {
    return "not login";
}

通過加日誌等方法,我們確認了是查看信息的時候,從session裏拿出來的證件號是其他人的,但是到底是在什麼時候變化的,沒找到,因爲我們一直順着線程安全的思路,找全局變量這樣的地方。

另外還發現有個地方可疑,就是有一個異步的線程,會驗證用戶證件號,並且重新在session裏放一次。

public void updateId(HttpServletRequest request) {
    HttpSession session = request.getSession();
    String id = validate();
    session.setAttribute("id",id);
}

這個函數是在登錄主交易調起的線程池處理的,看上去其實沒有多大毛病,而且也是老的代碼,很久了。而且我發現了一個規律,被別人看到信息的用戶,登錄交易都觸發了使用新設備登錄,因爲我們加了一個邏輯,對換設備登錄做了驗證,這樣的話需要驗證短信,登錄分兩步了,第一步比較快的返回了,但是異步的更新信息流程還在。

異步處理ServletRequest引發的血案

於是我懷疑是不是因爲Servlet的交易已經返回了,異步的線程雖然拿到了HttpServletRequest,但是這個request已經無效了或者被複用了。

我寫了下面的代碼進行驗證,不停的用curl調用。Http請求很快就返回了,但是我會把HttpServletRequest傳給一個線程池,等5s後纔會去處理這個Request,結果果然是有問題的

異步處理ServletRequest引發的血案

結果果然有問題,大部分情況new_session都是null,但也出現了不是null的情況,這時候發現session不是自己的。
異步處理ServletRequest引發的血案
HttpServletRequest是有生命週期的,當一個http請求過來後,應用服務器解析報文,把各種參數放到一個HttpServletRequest對象中,然後傳遞給Servlet的service函數,service函數根據裏面的方法調用對應的doGet/doPost等方法,而一旦service函數調用結束,HttpServletRequest的生命週期就結束了,再這之後你繼續使用這個對象,產生的結果是不確定的。
異步處理ServletRequest引發的血案
網上遇到這類問題的人不多,我專門找了servlet specification,其中有一章講HttpServletRequest生命週期的。
異步處理ServletRequest引發的血案
從中可以得到如下信息:

(1)三種情況下request有效:service函數內,doFilter函數內,startAsync起的異步線程

(2)在三種情況之外,使用request會產生不確定的結果(indeterminate results)

(3)大部分容器在實現servlet的時候,爲了提高性能,會複用request對象,但這不是規範裏必須的

其中提到的startAsync是servlet 3.0開始有的,它是爲了讓一個工作線程可以在做IO或類似阻塞線程的操作的時候能幹其它的事情,但是它要求異步線程都結束了,纔會將請求返回給客戶端,本質上還是同步的,只是並行了。所以要想異步的處理Request,必須使用servlet自己的異步機制,但是這樣並不能滿足我們的需求,因爲我們就是爲了不讓主線程等待。

用法示例:
異步處理ServletRequest引發的血案

如果使用了這個,那麼客戶端需要等待5s才能拿到結果。

我又看了tomcat的源碼,發現它確實對Request做了複用:
異步處理ServletRequest引發的血案

雖然問題的原因很簡單,但是產生的後果十分嚴重。需要異步處理數據的時候一定要特別小心,此處如果傳Session就沒問題了,但是還是要儘量避免。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章