您好,登錄后才能下訂單哦!
我們的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);
}
這個函數是在登錄主交易調起的線程池處理的,看上去其實沒有多大毛病,而且也是老的代碼,很久了。而且我發現了一個規律,被別人看到信息的用戶,登錄交易都觸發了使用新設備登錄,因為我們加了一個邏輯,對換設備登錄做了驗證,這樣的話需要驗證短信,登錄分兩步了,第一步比較快的返回了,但是異步的更新信息流程還在。
于是我懷疑是不是因為Servlet的交易已經返回了,異步的線程雖然拿到了HttpServletRequest,但是這個request已經無效了或者被復用了。
我寫了下面的代碼進行驗證,不停的用curl調用。Http請求很快就返回了,但是我會把HttpServletRequest傳給一個線程池,等5s后才會去處理這個Request,結果果然是有問題的
結果果然有問題,大部分情況new_session都是null,但也出現了不是null的情況,這時候發現session不是自己的。
HttpServletRequest是有生命周期的,當一個http請求過來后,應用服務器解析報文,把各種參數放到一個HttpServletRequest對象中,然后傳遞給Servlet的service函數,service函數根據里面的方法調用對應的doGet/doPost等方法,而一旦service函數調用結束,HttpServletRequest的生命周期就結束了,再這之后你繼續使用這個對象,產生的結果是不確定的。
網上遇到這類問題的人不多,我專門找了servlet specification,其中有一章講HttpServletRequest生命周期的。
從中可以得到如下信息:
(1)三種情況下request有效:service函數內,doFilter函數內,startAsync起的異步線程
(2)在三種情況之外,使用request會產生不確定的結果(indeterminate results)
(3)大部分容器在實現servlet的時候,為了提高性能,會復用request對象,但這不是規范里必須的
其中提到的startAsync是servlet 3.0開始有的,它是為了讓一個工作線程可以在做IO或類似阻塞線程的操作的時候能干其它的事情,但是它要求異步線程都結束了,才會將請求返回給客戶端,本質上還是同步的,只是并行了。所以要想異步的處理Request,必須使用servlet自己的異步機制,但是這樣并不能滿足我們的需求,因為我們就是為了不讓主線程等待。
用法示例:
如果使用了這個,那么客戶端需要等待5s才能拿到結果。
我又看了tomcat的源碼,發現它確實對Request做了復用:
雖然問題的原因很簡單,但是產生的后果十分嚴重。需要異步處理數據的時候一定要特別小心,此處如果傳Session就沒問題了,但是還是要盡量避免。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。