Tomcat Request Cookie 丟失問(wèn)題解決
一、問(wèn)題描述
生產(chǎn)環(huán)境偶爾(涉及到多線程處理)出現(xiàn)"前端傳遞`Cookie為空"的告警,導(dǎo)致前端請(qǐng)求丟失,出現(xiàn)請(qǐng)求失敗問(wèn)題。告警內(nèi)容如下
前端傳遞Cookie為空 告警內(nèi)容:服務(wù)端獲取request Cookie為空,請(qǐng)盡快處理?。?! AppId:xxxxxx ip:xx.xx.xxx.xx 告警事件:2024-03-15
背景:為什么要加
Cookie
告警:項(xiàng)目出海,需要保證多語(yǔ)言,語(yǔ)言信息從Cookie
中獲取,所以添加了Cookie
告警,告警后發(fā)到工作群中,但是相關(guān)開(kāi)發(fā)人員告知自己能夠正常訪問(wèn),沒(méi)有問(wèn)題,因?yàn)檎弥芪?,自己覺(jué)得偶發(fā)性肯定和并發(fā)相關(guān),所以周末研究了下代碼,發(fā)現(xiàn)和Tomcat Rquest
復(fù)用機(jī)制和ThreadLocal
的使用存在缺陷,導(dǎo)致這個(gè)偶發(fā)性問(wèn)題
在分析原因前,先需要搞懂一個(gè)概念:request
在tomcat
里面是循環(huán)使用的
二、Tomcat 中 Reqeust 復(fù)用機(jī)制
Request
對(duì)象的復(fù)用機(jī)制是為了提高性能和減少垃圾收集壓力而設(shè)計(jì)的。Tomcat
使用了一種對(duì)象池的機(jī)制來(lái)管理Request
對(duì)象和Response
對(duì)象。通過(guò)復(fù)用這些對(duì)象,Tomcat
可以避免頻繁地創(chuàng)建和銷(xiāo)毀對(duì)象,從而提高系統(tǒng)的效率。
復(fù)用機(jī)制的工作原理【1】對(duì)象池:Tomcat
維護(hù)一個(gè)對(duì)象池,用于存儲(chǔ)Request
對(duì)象和Response
對(duì)象。當(dāng)一個(gè)新的HTTP
請(qǐng)求到達(dá)時(shí),Tomcat
從對(duì)象池中獲取一個(gè)空閑的Request
對(duì)象和Response
對(duì)象。如果對(duì)象池中沒(méi)有空閑的對(duì)象,Tomcat
會(huì)創(chuàng)建新的對(duì)象。簡(jiǎn)單看個(gè)案例:
public class RequestPool { private Stack<Request> pool = new Stack<>(); // 獲取對(duì)象:getRequest 方法從對(duì)象池中獲取一個(gè) Request 對(duì)象。如果對(duì)象池為空,則創(chuàng)建一個(gè)新的 Request 對(duì)象。 public Request getRequest() { if (pool.isEmpty()) { return new Request(); } else { return pool.pop(); } } // 釋放對(duì)象:releaseRequest 方法將 Request 對(duì)象重置(調(diào)用 recycle 方法)并放回對(duì)象池中。 public void releaseRequest(Request request) { request.recycle(); pool.push(request); } }
【2】對(duì)象重置:當(dāng)一個(gè)請(qǐng)求處理完畢后,Request
對(duì)象會(huì)被重置(通過(guò)調(diào)用recycle
方法),以清除上一次請(qǐng)求的狀態(tài),使其可以安全地用于下一個(gè)請(qǐng)求。以下是org.apache.catalina.connector.Request
類(lèi)中recycle
方法的簡(jiǎn)化源碼和解釋?zhuān)?/p>
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 對(duì)象的基本屬性(如 protocol、method、requestURI 等)重置為初始狀態(tài)(通常為 null 或默認(rèn)值)。 // 清空集合和數(shù)組:attributes 和 parameters 集合被清空,以確保沒(méi)有殘留的請(qǐng)求數(shù)據(jù)。cookies 數(shù)組也被重置為 null。 // 重置流和讀者:inputStream、reader、servletInputStream 和 bufferedReader 被重置為 null,以確保沒(méi)有殘留的輸入流和讀者對(duì)象。 // 重置會(huì)話:session 被重置為 null,以確保沒(méi)有殘留的會(huì)話信息。 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í)行的時(shí)機(jī): recycle
方法在Tomcat
源碼中的調(diào)用時(shí)機(jī)主要是在請(qǐng)求處理完畢之后,Request
對(duì)象被返回到對(duì)象池之前。具體來(lái)說(shuō),recycle
方法通常在以下幾個(gè)場(chǎng)景中被調(diào)用:
【1】請(qǐng)求處理完畢后:在Tomcat
的org.apache.coyote.Request
類(lèi)中,recycle
方法通常在請(qǐng)求處理完畢后被調(diào)用。例如,在AbstractProcessorLight
類(lèi)中處理請(qǐng)求和響應(yīng)的邏輯中,recycle
方法被調(diào)用來(lái)重置Request
對(duì)象。
// 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)閉時(shí):在Tomcat
的org.apache.coyote.http11.Http11Processor
類(lèi)中,當(dāng)連接關(guān)閉時(shí),recycle
方法也會(huì)被調(diào)用。例如,當(dāng)處理完一個(gè)請(qǐng)求并決定關(guān)閉連接時(shí),會(huì)調(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】異常處理:在處理請(qǐng)求的過(guò)程中,如果發(fā)生異常,Tomcat
也會(huì)確保調(diào)用recycle
方法來(lái)重置Request
對(duì)象。例如:
// 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
是一個(gè)包裝類(lèi)Facade
,用于保護(hù)底層的Request
對(duì)象,確保應(yīng)用程序無(wú)法直接訪問(wèn)和修改內(nèi)部實(shí)現(xiàn)細(xì)節(jié)。
【1】Request
類(lèi): Request
類(lèi)是Tomcat
內(nèi)部用來(lái)表示HTTP
請(qǐng)求的類(lèi),包含了請(qǐng)求的所有詳細(xì)信息。該類(lèi)提供了許多方法來(lái)訪問(wèn)和操作請(qǐng)求的各個(gè)部分,例如請(qǐng)求頭、請(qǐng)求參數(shù)、輸入流等。
【2】RequestFacade
類(lèi): RequestFacade
類(lèi)是一個(gè)包裝器,用于保護(hù)Request
對(duì)象。它實(shí)現(xiàn)了javax.servlet.http.HttpServletRequest
接口,并將方法調(diào)用委托給內(nèi)部的Request
對(duì)象。通過(guò)使用RequestFacade
,Tomcat
確保了應(yīng)用程序只能通過(guò)標(biāo)準(zhǔn)的HttpServletRequest
接口訪問(wèn)請(qǐng)求數(shù)據(jù),而不能直接訪問(wèn)或修改Request
對(duì)象的內(nèi)部實(shí)現(xiàn)。
具體實(shí)現(xiàn):在Tomcat
中,RequestFacade
類(lèi)通常包含一個(gè)Request
對(duì)象的引用,并將所有的接口方法調(diào)用委托給這個(gè)內(nèi)部的Request
對(duì)象。例如:
// 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 }
使用場(chǎng)景:在Tomcat
處理請(qǐng)求的過(guò)程中,當(dāng)需要將HttpServletRequest
對(duì)象傳遞給應(yīng)用程序時(shí),Tomcat
會(huì)創(chuàng)建一個(gè)RequestFacade
實(shí)例,并將內(nèi)部的Request
對(duì)象傳遞給它。例如
// 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】第一次請(qǐng)求由線程A
正常執(zhí)行,執(zhí)行完成后執(zhí)行recycle
方法,將RequestFacade
中的屬性修改為null
,準(zhǔn)備下次復(fù)用,但是當(dāng)前線程的ThreadLocal
沒(méi)有被清理。
【2】第二次請(qǐng)求恰好也由線程A
執(zhí)行(這也是偶發(fā)的原因),通過(guò)ThreadLocal
獲取RequestFacade
對(duì)象,并通過(guò)getCookies
獲取Cookie
,因?yàn)榈谝淮握?qǐng)求結(jié)束后將Cookie
置為null
并將cookiesParsed
修改為了false
,但是這次請(qǐng)求再次調(diào)用getCookies
的時(shí)候,將cookiesParsed
修改為了true
。用來(lái)表示RequestFacade A
的Cookies
已經(jīng)被解析過(guò)了。同時(shí)需要注意,此時(shí)第一次請(qǐng)求的生命周期已經(jīng)結(jié)束了,所以重置cookiesParsed
的操作就不復(fù)存在了,Tomcat
重新復(fù)用RequestFacade A
的時(shí)候Cookies
就會(huì)獲取到一個(gè)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】第三次請(qǐng)求時(shí),Tomcat
復(fù)用了RequestFacade A
,當(dāng)正常解析Cookies
的時(shí)候發(fā)現(xiàn)cookiesParsed
為true
就跳過(guò)了正確解析的環(huán)節(jié),當(dāng)需要使用Cookie
的時(shí)候發(fā)現(xiàn)為空,本次請(qǐng)求直接被中止。(靈異事件)
解決方案:
【1】ThreadLocal
使用完后一定需要clean
;
【2】不要在跨線程中使用request
對(duì)象。可以使用-Dorg.apache.catalina.connector.RECYCLE_FACADES=true
禁止復(fù)用。在項(xiàng)目的extraenv.sh
中設(shè)置參數(shù)后,如果有訪問(wèn)已經(jīng)被回收的request
對(duì)象,就會(huì)拋出The request object has been recycled and is no longer associated with this facade
異常,以此就能定位到問(wèn)題
到此這篇關(guān)于Tomcat Request Cookie 丟失問(wèn)題解決的文章就介紹到這了,更多相關(guān)Tomcat Request Cookie 丟失內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
tomcat部署jenkins項(xiàng)目的實(shí)現(xiàn)示例
Jenkins自動(dòng)化部署可以解決集成、測(cè)試、部署等重復(fù)性的工作,本文主要介紹了tomcat部署jenkins項(xiàng)目,具有一定的參考價(jià)值,感興趣的可以了解一下2023-11-11Java Tomcat 啟動(dòng)閃退問(wèn)題解決集
Tomcat 啟動(dòng)時(shí)出現(xiàn)黑屏一閃而過(guò)的現(xiàn)象原因有很多,這篇文章就詳細(xì)介紹了tomcat啟動(dòng)閃退問(wèn)題的一些解決方法,感興趣的同學(xué)可以仔細(xì)閱讀2023-03-03tomcat服務(wù)安裝步驟及詳細(xì)配置實(shí)戰(zhàn)教程
Tomcat是由Apache開(kāi)發(fā)的一個(gè)開(kāi)源Java WEB應(yīng)用服務(wù)器,下面這篇文章主要給大家介紹了關(guān)于tomcat服務(wù)安裝步驟及詳細(xì)配置實(shí)戰(zhàn)教程,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-12-12Tomcat 類(lèi)加載器的實(shí)現(xiàn)方法及實(shí)例代碼
這篇文章主要介紹了Tomcat 類(lèi)加載器的實(shí)現(xiàn)方法及實(shí)例代碼,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值 ,需要的朋友可以參考下2019-05-05解決Tomcat?Caused?by:?java.lang.ClassNotFoundException:?ja
這篇文章主要給大家介紹了如何解決Tomcat?Caused?by:?java.lang.ClassNotFoundException:?java.util.logging.Logger的問(wèn)題,文中有詳細(xì)的原因分析及解決方法,需要的朋友可以參考下2023-10-10Tomcat+Mysql高并發(fā)配置優(yōu)化講解
今天小編就為大家分享一篇關(guān)于Tomcat+Mysql高并發(fā)配置優(yōu)化講解,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-03-03