Tomcat Request Cookie 丟失問題解決
一、問題描述
生產(chǎn)環(huán)境偶爾(涉及到多線程處理)出現(xiàn)"前端傳遞`Cookie為空"的告警,導(dǎo)致前端請求丟失,出現(xiàn)請求失敗問題。告警內(nèi)容如下
前端傳遞Cookie為空 告警內(nèi)容:服務(wù)端獲取request Cookie為空,請盡快處理!??! AppId:xxxxxx ip:xx.xx.xxx.xx 告警事件:2024-03-15
背景:為什么要加
Cookie
告警:項目出海,需要保證多語言,語言信息從Cookie
中獲取,所以添加了Cookie
告警,告警后發(fā)到工作群中,但是相關(guān)開發(fā)人員告知自己能夠正常訪問,沒有問題,因為正好周五,自己覺得偶發(fā)性肯定和并發(fā)相關(guān),所以周末研究了下代碼,發(fā)現(xiàn)和Tomcat Rquest
復(fù)用機(jī)制和ThreadLocal
的使用存在缺陷,導(dǎo)致這個偶發(fā)性問題
在分析原因前,先需要搞懂一個概念:request
在tomcat
里面是循環(huán)使用的
二、Tomcat 中 Reqeust 復(fù)用機(jī)制
Request
對象的復(fù)用機(jī)制是為了提高性能和減少垃圾收集壓力而設(shè)計的。Tomcat
使用了一種對象池的機(jī)制來管理Request
對象和Response
對象。通過復(fù)用這些對象,Tomcat
可以避免頻繁地創(chuàng)建和銷毀對象,從而提高系統(tǒng)的效率。
復(fù)用機(jī)制的工作原理【1】對象池:Tomcat
維護(hù)一個對象池,用于存儲Request
對象和Response
對象。當(dāng)一個新的HTTP
請求到達(dá)時,Tomcat
從對象池中獲取一個空閑的Request
對象和Response
對象。如果對象池中沒有空閑的對象,Tomcat
會創(chuàng)建新的對象。簡單看個案例:
public class RequestPool { private Stack<Request> pool = new Stack<>(); // 獲取對象:getRequest 方法從對象池中獲取一個 Request 對象。如果對象池為空,則創(chuàng)建一個新的 Request 對象。 public Request getRequest() { if (pool.isEmpty()) { return new Request(); } else { return pool.pop(); } } // 釋放對象:releaseRequest 方法將 Request 對象重置(調(diào)用 recycle 方法)并放回對象池中。 public void releaseRequest(Request request) { request.recycle(); pool.push(request); } }
【2】對象重置:當(dāng)一個請求處理完畢后,Request
對象會被重置(通過調(diào)用recycle
方法),以清除上一次請求的狀態(tài),使其可以安全地用于下一個請求。以下是org.apache.catalina.connector.Request
類中recycle
方法的簡化源碼和解釋:
public class Request { // Various fields representing the state of the request private String protocol; private String method; private String requestURI; private String queryString; private String remoteAddr; private String remoteHost; private String serverName; private int serverPort; private boolean secure; private InputStream inputStream; private Reader reader; private ServletInputStream servletInputStream; private BufferedReader bufferedReader; private Map<String, Object> attributes; private Map<String, String[]> parameters; private Cookie[] cookies; private HttpSession session; // Other fields and methods... /** * Recycle this request object. */ public void recycle() { // Reset the state of the request object // 重置基本屬性:recycle 方法將 Request 對象的基本屬性(如 protocol、method、requestURI 等)重置為初始狀態(tài)(通常為 null 或默認(rèn)值)。 // 清空集合和數(shù)組:attributes 和 parameters 集合被清空,以確保沒有殘留的請求數(shù)據(jù)。cookies 數(shù)組也被重置為 null。 // 重置流和讀者:inputStream、reader、servletInputStream 和 bufferedReader 被重置為 null,以確保沒有殘留的輸入流和讀者對象。 // 重置會話:session 被重置為 null,以確保沒有殘留的會話信息。 protocol = null; method = null; requestURI = null; queryString = null; remoteAddr = null; remoteHost = null; serverName = null; serverPort = 0; secure = false; inputStream = null; reader = null; servletInputStream = null; bufferedReader = null; attributes.clear(); parameters.clear(); cookies = null; session = null; // Other reset logic... } }
recycle
執(zhí)行的時機(jī): recycle
方法在Tomcat
源碼中的調(diào)用時機(jī)主要是在請求處理完畢之后,Request
對象被返回到對象池之前。具體來說,recycle
方法通常在以下幾個場景中被調(diào)用:
【1】請求處理完畢后:在Tomcat
的org.apache.coyote.Request
類中,recycle
方法通常在請求處理完畢后被調(diào)用。例如,在AbstractProcessorLight
類中處理請求和響應(yīng)的邏輯中,recycle
方法被調(diào)用來重置Request
對象。
// org.apache.coyote.AbstractProcessorLight public class AbstractProcessorLight<S> implements Processor { // Various fields and methods... @Override public SocketState process(SocketWrapperBase<S> socketWrapper, SocketEvent status) throws IOException { // Process the request and response try { // Request processing logic... } finally { // Recycle the request and response objects request.recycle(); response.recycle(); } return SocketState.CLOSED; } }
【2】連接關(guān)閉時:在Tomcat
的org.apache.coyote.http11.Http11Processor
類中,當(dāng)連接關(guān)閉時,recycle
方法也會被調(diào)用。例如,當(dāng)處理完一個請求并決定關(guān)閉連接時,會調(diào)用recycle
方法。
// org.apache.coyote.http11.Http11Processor public class Http11Processor extends AbstractProcessorLight<SocketChannel> { // Various fields and methods... @Override public SocketState service(SocketWrapperBase<SocketChannel> socketWrapper) throws IOException { // Service the request and response try { // Request servicing logic... } finally { // Recycle the request and response objects request.recycle(); response.recycle(); } return SocketState.CLOSED; } }
【3】異常處理:在處理請求的過程中,如果發(fā)生異常,Tomcat
也會確保調(diào)用recycle
方法來重置Request
對象。例如:
// org.apache.coyote.http11.Http11Processor public class Http11Processor extends AbstractProcessorLight<SocketChannel> { // Various fields and methods... @Override public SocketState service(SocketWrapperBase<SocketChannel> socketWrapper) throws IOException { try { // Request servicing logic... } catch (Exception e) { // Handle exception and recycle request request.recycle(); response.recycle(); throw e; } } }
后期原因分析中需要使用到RequestFacade
,這里解釋下RequestFacade
與Request
之間的關(guān)系:RequestFacade
是一個包裝類Facade
,用于保護(hù)底層的Request
對象,確保應(yīng)用程序無法直接訪問和修改內(nèi)部實(shí)現(xiàn)細(xì)節(jié)。
【1】Request
類: Request
類是Tomcat
內(nèi)部用來表示HTTP
請求的類,包含了請求的所有詳細(xì)信息。該類提供了許多方法來訪問和操作請求的各個部分,例如請求頭、請求參數(shù)、輸入流等。
【2】RequestFacade
類: RequestFacade
類是一個包裝器,用于保護(hù)Request
對象。它實(shí)現(xiàn)了javax.servlet.http.HttpServletRequest
接口,并將方法調(diào)用委托給內(nèi)部的Request
對象。通過使用RequestFacade
,Tomcat
確保了應(yīng)用程序只能通過標(biāo)準(zhǔn)的HttpServletRequest
接口訪問請求數(shù)據(jù),而不能直接訪問或修改Request
對象的內(nèi)部實(shí)現(xiàn)。
具體實(shí)現(xiàn):在Tomcat
中,RequestFacade
類通常包含一個Request
對象的引用,并將所有的接口方法調(diào)用委托給這個內(nèi)部的Request
對象。例如:
// org.apache.catalina.connector.RequestFacade public class RequestFacade implements HttpServletRequest { private final Request request; public RequestFacade(Request request) { this.request = request; } @Override public String getParameter(String name) { return request.getParameter(name); } // Other methods from HttpServletRequest interface // All methods delegate to the internal Request object }
使用場景:在Tomcat
處理請求的過程中,當(dāng)需要將HttpServletRequest
對象傳遞給應(yīng)用程序時,Tomcat
會創(chuàng)建一個RequestFacade
實(shí)例,并將內(nèi)部的Request
對象傳遞給它。例如
// org.apache.catalina.connector.CoyoteAdapter public class CoyoteAdapter implements Adapter { @Override public void service(org.apache.coyote.Request req, org.apache.coyote.Response res) throws Exception { Request request = (Request) req.getNote(ADAPTER_NOTES); Response response = (Response) res.getNote(ADAPTER_NOTES); // Create a RequestFacade to pass to the application HttpServletRequest requestFacade = request.getRequest(); // Pass the RequestFacade to the application context.getPipeline().getFirst().invoke(requestFacade, response); } }
三、原因分析
【1】第一次請求由線程A
正常執(zhí)行,執(zhí)行完成后執(zhí)行recycle
方法,將RequestFacade
中的屬性修改為null
,準(zhǔn)備下次復(fù)用,但是當(dāng)前線程的ThreadLocal
沒有被清理。
【2】第二次請求恰好也由線程A
執(zhí)行(這也是偶發(fā)的原因),通過ThreadLocal
獲取RequestFacade
對象,并通過getCookies
獲取Cookie
,因為第一次請求結(jié)束后將Cookie
置為null
并將cookiesParsed
修改為了false
,但是這次請求再次調(diào)用getCookies
的時候,將cookiesParsed
修改為了true
。用來表示RequestFacade A
的Cookies
已經(jīng)被解析過了。同時需要注意,此時第一次請求的生命周期已經(jīng)結(jié)束了,所以重置cookiesParsed
的操作就不復(fù)存在了,Tomcat
重新復(fù)用RequestFacade A
的時候Cookies
就會獲取到一個null
。
@Override public Cookie[] getCookies() { if (!cookiesParsed) { parseCookies(); } return cookies; } protected void parseCookies() { cookiesParsed = true; Cookies serverCookies = coyoteRequest.getCookies(); int count = serverCookies.getCookieCount(); if (count <= 0) { returnl } cookies = new Cookie[count]; }
【3】第三次請求時,Tomcat
復(fù)用了RequestFacade A
,當(dāng)正常解析Cookies
的時候發(fā)現(xiàn)cookiesParsed
為true
就跳過了正確解析的環(huán)節(jié),當(dāng)需要使用Cookie
的時候發(fā)現(xiàn)為空,本次請求直接被中止。(靈異事件)
解決方案:
【1】ThreadLocal
使用完后一定需要clean
;
【2】不要在跨線程中使用request
對象??梢允褂?code>-Dorg.apache.catalina.connector.RECYCLE_FACADES=true禁止復(fù)用。在項目的extraenv.sh
中設(shè)置參數(shù)后,如果有訪問已經(jīng)被回收的request
對象,就會拋出The request object has been recycled and is no longer associated with this facade
異常,以此就能定位到問題
到此這篇關(guān)于Tomcat Request Cookie 丟失問題解決的文章就介紹到這了,更多相關(guān)Tomcat Request Cookie 丟失內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
tomcat部署jenkins項目的實(shí)現(xiàn)示例
Jenkins自動化部署可以解決集成、測試、部署等重復(fù)性的工作,本文主要介紹了tomcat部署jenkins項目,具有一定的參考價值,感興趣的可以了解一下2023-11-11tomcat服務(wù)安裝步驟及詳細(xì)配置實(shí)戰(zhàn)教程
Tomcat是由Apache開發(fā)的一個開源Java WEB應(yīng)用服務(wù)器,下面這篇文章主要給大家介紹了關(guān)于tomcat服務(wù)安裝步驟及詳細(xì)配置實(shí)戰(zhàn)教程,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-12-12Tomcat 類加載器的實(shí)現(xiàn)方法及實(shí)例代碼
這篇文章主要介紹了Tomcat 類加載器的實(shí)現(xiàn)方法及實(shí)例代碼,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價值 ,需要的朋友可以參考下2019-05-05解決Tomcat?Caused?by:?java.lang.ClassNotFoundException:?ja
這篇文章主要給大家介紹了如何解決Tomcat?Caused?by:?java.lang.ClassNotFoundException:?java.util.logging.Logger的問題,文中有詳細(xì)的原因分析及解決方法,需要的朋友可以參考下2023-10-10Tomcat+Mysql高并發(fā)配置優(yōu)化講解
今天小編就為大家分享一篇關(guān)于Tomcat+Mysql高并發(fā)配置優(yōu)化講解,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-03-03