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告警:項(xiàng)目出海,需要保證多語言,語言信息從Cookie中獲取,所以添加了Cookie告警,告警后發(fā)到工作群中,但是相關(guān)開發(fā)人員告知自己能夠正常訪問,沒有問題,因?yàn)檎弥芪?,自己覺得偶發(fā)性肯定和并發(fā)相關(guān),所以周末研究了下代碼,發(fā)現(xiàn)和Tomcat Rquest復(fù)用機(jī)制和ThreadLocal的使用存在缺陷,導(dǎo)致這個(gè)偶發(fā)性問題
在分析原因前,先需要搞懂一個(gè)概念:request在tomcat里面是循環(huán)使用的
二、Tomcat 中 Reqeust 復(fù)用機(jī)制
Request對象的復(fù)用機(jī)制是為了提高性能和減少垃圾收集壓力而設(shè)計(jì)的。Tomcat使用了一種對象池的機(jī)制來管理Request對象和Response對象。通過復(fù)用這些對象,Tomcat可以避免頻繁地創(chuàng)建和銷毀對象,從而提高系統(tǒng)的效率。
復(fù)用機(jī)制的工作原理【1】對象池:Tomcat維護(hù)一個(gè)對象池,用于存儲Request對象和Response對象。當(dāng)一個(gè)新的HTTP請求到達(dá)時(shí),Tomcat從對象池中獲取一個(gè)空閑的Request對象和Response對象。如果對象池中沒有空閑的對象,Tomcat會(huì)創(chuàng)建新的對象。簡單看個(gè)案例:
public class RequestPool {
private Stack<Request> pool = new Stack<>();
// 獲取對象:getRequest 方法從對象池中獲取一個(gè) Request 對象。如果對象池為空,則創(chuàng)建一個(gè)新的 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)一個(gè)請求處理完畢后,Request對象會(huì)被重置(通過調(diào)用recycle方法),以清除上一次請求的狀態(tài),使其可以安全地用于下一個(gè)請求。以下是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,以確保沒有殘留的輸入流和讀者對象。
// 重置會(huì)話:session 被重置為 null,以確保沒有殘留的會(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ī)主要是在請求處理完畢之后,Request對象被返回到對象池之前。具體來說,recycle方法通常在以下幾個(gè)場景中被調(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)閉時(shí):在Tomcat的org.apache.coyote.http11.Http11Processor類中,當(dāng)連接關(guān)閉時(shí),recycle方法也會(huì)被調(diào)用。例如,當(dāng)處理完一個(gè)請求并決定關(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】異常處理:在處理請求的過程中,如果發(fā)生異常,Tomcat也會(huì)確保調(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是一個(gè)包裝類Facade,用于保護(hù)底層的Request對象,確保應(yīng)用程序無法直接訪問和修改內(nèi)部實(shí)現(xiàn)細(xì)節(jié)。
【1】Request類: Request類是Tomcat內(nèi)部用來表示HTTP請求的類,包含了請求的所有詳細(xì)信息。該類提供了許多方法來訪問和操作請求的各個(gè)部分,例如請求頭、請求參數(shù)、輸入流等。
【2】RequestFacade 類: RequestFacade類是一個(gè)包裝器,用于保護(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類通常包含一個(gè)Request對象的引用,并將所有的接口方法調(diào)用委托給這個(gè)內(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)用程序時(shí),Tomcat會(huì)創(chuàng)建一個(gè)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,因?yàn)榈谝淮握埱蠼Y(jié)束后將Cookie置為null并將cookiesParsed修改為了false,但是這次請求再次調(diào)用getCookies的時(shí)候,將cookiesParsed修改為了true。用來表示RequestFacade A的Cookies已經(jīng)被解析過了。同時(shí)需要注意,此時(shí)第一次請求的生命周期已經(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】第三次請求時(shí),Tomcat復(fù)用了RequestFacade A,當(dāng)正常解析Cookies的時(shí)候發(fā)現(xiàn)cookiesParsed為true就跳過了正確解析的環(huán)節(jié),當(dāng)需要使用Cookie的時(shí)候發(fā)現(xiàn)為空,本次請求直接被中止。(靈異事件)
解決方案:
【1】ThreadLocal使用完后一定需要clean;
【2】不要在跨線程中使用request對象??梢允褂?code>-Dorg.apache.catalina.connector.RECYCLE_FACADES=true禁止復(fù)用。在項(xiàng)目的extraenv.sh中設(shè)置參數(shù)后,如果有訪問已經(jīng)被回收的request對象,就會(huì)拋出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項(xiàng)目的實(shí)現(xiàn)示例
Jenkins自動(dòng)化部署可以解決集成、測試、部署等重復(fù)性的工作,本文主要介紹了tomcat部署jenkins項(xiàng)目,具有一定的參考價(jià)值,感興趣的可以了解一下2023-11-11
tomcat服務(wù)安裝步驟及詳細(xì)配置實(shí)戰(zhàn)教程
Tomcat是由Apache開發(fā)的一個(gè)開源Java WEB應(yīng)用服務(wù)器,下面這篇文章主要給大家介紹了關(guān)于tomcat服務(wù)安裝步驟及詳細(xì)配置實(shí)戰(zhàn)教程,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-12-12
Tomcat 類加載器的實(shí)現(xiàn)方法及實(shí)例代碼
這篇文章主要介紹了Tomcat 類加載器的實(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的問題,文中有詳細(xì)的原因分析及解決方法,需要的朋友可以參考下2023-10-10
Tomcat+Mysql高并發(fā)配置優(yōu)化講解
今天小編就為大家分享一篇關(guān)于Tomcat+Mysql高并發(fā)配置優(yōu)化講解,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2019-03-03

