SESSION在服務端(PHP/JAVA)具體是如何實現的?

通常我們說session可以依託cookie實現,這主要是站在客戶端的角度。我想知道在服務端的存儲情況。session是存儲在內存中,那麼在Java/PHP的後台中,一次網頁請求(JSP/PHP)其中的網頁中的對象/變數的生命周期是怎麼樣的,SESSION相關對象/變數是如何長時間貯存於內存中,而不銷毀的?

PS:web小白


客戶端初次請求時,如果會使用到session,此時服務端通過響應頭中的Set-Cookie這樣一個header項,把sessionId寫回到瀏覽器的cookie中,這種Cookie也叫SessionCookie,在整個瀏覽器關閉之後就被清除了。而再次請求時,瀏覽器發送的請求頭中會包含cookie這樣一項header:即剛才寫回來的sessionId,再次到達伺服器時,根據這個sessionId,配置之前為請求創建的session,從而所有以這個sessionId保存的信息都存了下來。

服務端這個session默認超時時間是30分鐘,就是30分鐘沒有使用就被清除掉了。而如果一直在使用,則每次active時間會被更新,到了30分也不會被清除。

而至於session存在哪裡,只是一種具體的實現形式,比如為了實現session的高可用,可能會把session存到Memcache或者Redis中,甚至存在數據中也是可以的,存取一致即可。


瀉藥

session cookie 就是 cookie 的一種,cookie 就是存在瀏覽器里的,服務端不一定需要有對應的存儲。

而服務端有存儲的 session,一般是指 session id,客戶端可以是通過 cookie 維持,也可以通過其他方式維持,服務端根據 session id 映射到用戶會話數據,這些數據保存在服務端,傳輸時只傳 session id。

服務端具體怎麼存,這就很多種了,有基於文件的,有放在應用內存里的,有存到關係型資料庫的,也有存到 NoSQL 的。

答得比較抽象,但正如很多技術問題一樣,一個術語要描述清楚需要用到別的術語才能嚴謹。詳細答案可以參考以下鏈接。

https://en.m.wikipedia.org/wiki/HTTP_cookie

https://en.m.wikipedia.org/wiki/Session_ID


java中的實現,根據servlet規範,cookie中放一個JSESSIONID

tomcat有一個SessionManager,差不多就是Session session = map.get("JSESSIONID");

然後session有過期機制。

具體看tomcat代碼去。


session就分為存儲和傳遞兩部分。一般情況下儲存是以文件形式存在緩存目錄(tmp)中的,可以修改成多種形式,比如memcache儲存。傳遞方面一般都是通過會話生命周期的cookie來傳遞,當會話結束後cookie就沒了,所以就以為session也沒了,實際上服務端的session還存在。當然傳遞還可以改成其他方式,比如做app介面無法使用cookie傳遞,那麼就使用get或post方式來傳遞session_id,總之了解清楚session原理後,做介面很方便了。


我從代碼的角度,說明session在java+tomcat環境下的創建和持久化。

環境

jdk : 1.7.0

tomcat : 7.0.42

servlet-api : 3.0.1

jsp-api : 2.2

session創建

1. session可以通過以下兩種方式創建

  • servlet
  • jsp

以上的兩種方式,本質上都是通過servlet-api提供的介面,完成相關操作。

web容器(tomcat)會把jsp解析轉換成相應的servlet,在tomcat的work/Catalina目錄下可以看到解析過的jsp-servlet.class

public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response)
throws java.io.IOException, javax.servlet.ServletException
{
//...
response.setContentType("text/html;charset=UTF-8");
pageContext = _jspxFactory.getPageContext(this, request, response,null, true, 8192, true);
//...
}

上面的_jspService方法中通過_jspxFactory構造一個PageContext的實例,其中就包含的一個session創建的過程。

PageContext與jsp頁面中的&<% page ...%&>相對應。

jsp頁面會默認創建session,通過對PageContext的設置,可以要求jsp不創建session。

&<%@ page session="false"%&>

這樣_jspxFactory.getPageContext方法根據參數,最終沒有創建session。

pageContext = _jspxFactory.getPageContext(this, request, response,null, false, 8192, true);

PageContext是jsp-api提供的一個抽象類,和servlet-api一樣,真正的實現(子類)都在web容器的代碼中(tomcat)。

我們現在看一下JspFactory和PageContext的實現。

org.apache.jasper.runtime.JspFactoryImpl

org.apache.jasper.runtime.PageContextImpl

@Override
public PageContext getPageContext(Servlet servlet,ServletRequest request,ServletResponse response, String errorPageURL, boolean needsSession,int bufferSize, boolean autoflush) {

if( Constants.IS_SECURITY_ENABLED ) {
PrivilegedGetPageContext dp = new PrivilegedGetPageContext(this, servlet, request, response, errorPageURL,needsSession, bufferSize, autoflush);
return AccessController.doPrivileged(dp);
} else {
return internalGetPageContext(servlet, request, response,errorPageURL, needsSession,bufferSize, autoflush);
}
}

private PageContext internalGetPageContext(Servlet servlet,ServletRequest request,ServletResponse response, String errorPageURL, boolean needsSession,int bufferSize, boolean autoflush) {

try {
PageContext pc;
if (USE_POOL) {
PageContextPool pool = localPool.get();
if (pool == null) {
pool = new PageContextPool();
localPool.set(pool);
}
pc = pool.get();
if (pc == null) {
pc = new PageContextImpl();
}
} else {
pc = new PageContextImpl();
}
pc.initialize(servlet, request, response, errorPageURL,
needsSession, bufferSize, autoflush);
return pc;
} catch (Throwable ex) {
ExceptionUtils.handleThrowable(ex);
if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
log.fatal("Exception initializing page context", ex);
return null;
}
}

我們可以看到getPageContext方法中調用到internalGetPageContext方法,而在internalGetPageContext方法中創建了PageContextImple實例,並調用了實例的initialize初始化方法,initialize初始化方法包含了我們上面提到的session的創建。

// Setup session (if required)
if (request instanceof HttpServletRequest needsSession)
this.session = ((HttpServletRequest) request).getSession();
if (needsSession session == null)
throw new IllegalStateException("Page needs a session and none is available");

在這裡我們知道,jsp也是經過一系列的調用,最用還是通過HttpServletRequest(servlet)的getSession方法來創建Session。

上面的代碼表明了,jsp和servlet一樣,最終還是通過getSession方法來創建Session,下面我們來看一下getSession在伺服器端的真正實現。

(伺服器端代碼實現和web容器有直接關係,此處是tomcat的代碼實現)

在類圖關係中,我們著重看org.apache.catalina.connector.Request類,它也是HttpServletRequest在tomcat中的實現。

Request類中doGetSession方法通過調用Session管理類(Manager,ManagerBase)創建真正的Session(StarndSession,StanrdSessionFacade),並返回給getSession的調用端。

篇幅所限,僅以圖片和文字說明一下Session的創建過程。Request的doGetSession方法首先會判斷返回當前session

// Return the current session if it exists and is valid
if ((session != null) !session.isValid()) {
session = null;
}
if (session != null) {
return (session);
}

或者根據requestSessionId要求Manage查詢一個已存在的session

if (requestedSessionId != null) {
try {
session = manager.findSession(requestedSessionId);
} catch (IOException e) {
session = null;
}
if ((session != null) !session.isValid()) {
session = null;
}
if (session != null) {
session.access();
return (session);
}
}

當前session和requestSessionId都不存在,我們就要Session管理類(Manager)創建一個新的session

// Attempt to reuse session id if one was submitted in a cookie
// Do not reuse the session id if it is from a URL, to prevent possible
// phishing attacks
// Use the SSL session ID if one is present.
if (("/".equals(context.getSessionCookiePath()) isRequestedSessionIdFromCookie()) || requestedSessionSSL ) {
session = manager.createSession(getRequestedSessionId());
} else {
session = manager.createSession(null);
}

在Tomcat代碼中,org.apache.catalina.session專門用於Session的管理和持久化。

ManagerBase類用於Session的操作處理(創建,查詢,清除,過期)。

/**
* The set of currently active Sessions for this Manager, keyed by
* session identifier.
*/
protected Map& sessions = new ConcurrentHashMap&();

ManagerBase的sessions是一個Session的集合類,伺服器端(Tomcat)的Session就保存在這裡。

ManagerBase的getNewSession方法創建一個StandardSession實例,這個就是伺服器端的Session實現。

StandardSession

/**
* The collection of user data attributes associated with this Session.
*/
protected Map& attributes = new ConcurrentHashMap&();

StandardSession的attributes是一個集合類,我們setAttribute、getAttribute方法操作的對象就保存這裡。

session持久化

//TODO 2016-11-17


session 在服務端和瀏覽器都有儲存的內容

類似於銀行取錢

瀏覽器存的叫做session_id 類似於銀行卡號

伺服器存的就是你的會話信息

當瀏覽器訪問伺服器時,會把這個數字帶給瀏覽器。伺服器會根據這個數字提取相應的信息,所以說session_id很重要,可能會被不法分子利用

所以有的時候你去支付某些東西的時候,還需在進一步驗證。

如果你關閉瀏覽器,服務端,這些信息也不會丟,只是你下一次打開瀏覽器,生成session_id和上一次不一樣了。

難道這些信息會永久保存嗎?

不,這些信息會過期,過期服務端就將這些信息丟棄,所以一段時間後,即使,你一直沒關閉瀏覽器,也要重新登錄。就像銀行資料庫信息丟失,你有卡也沒用。

根據服務端的配置不同,這些信息丟失的時間也不同。

上述是java tomcat伺服器的邏輯實現方式


相信直到目前,還有很多的PHP系統,仍然使用$_SESSION來作為Session的方法。我們先簡單介紹一下Session的原理。

Session的本質是:

Cookie(key)+伺服器存儲環境(Value)。

即,通過Cookie(有的時候是url參數),作為一個KEY,通過這個KEY找到伺服器對應的數據,這個數據可以使:File(默認),也可以是Mysql、Memcache等。這其中,分散式場景下,MC的使用頻次最高。

那麼,既然要教唆大家放棄使用傳統的這種方式,我們就需要挑出那些MC不好的毛病。

1. 並發大的時候,佔用資源嚴重(每個登錄的用戶,都需要進行一次登記)

2. 並發大的時候,用戶被踢出的概率增加(內存不足的情況下,MC會啟用LRU演算法來進行內存清洗,導致一部分用戶被提出)

3. 資源浪費率較高(需要額外的機器 並且 用戶登錄一次之後 仍然需要保存一段時間才能消失)

4. MC伺服器並發的管理和機器的擴容等問題(雖然一致性哈希當前這種場景常見的解決方案,但是仍然不是完美的方案)

挑出了這麼多的刺之後,我們放棄了MC(其實部分原因是:窮),轉而使用了一種更低成本、簡單的方案:可逆加密演算法。

目前,我們使用的是AES演算法來完成我們的工作的。

加密演算法如下:

傳入待加密的數據和加密因子,返回加密後的字元串。這裡同時也支持了進行base64加密的要求。

相應的解密演算法如下:

在兩個方法中,我們使用了序列化和反序列化,從而可以對數組進行支持,使得可以對數組進行加密。

數據的加密,我們選擇了PHP自帶的serialize,相比Json,速度會更快,加密後的結果會更小。

【那麼,使用可逆加密演算法到底有什麼好處呢?】

1. 無需額外伺服器進行數據提取,性能更好,穩定性更好

不需要額外的服務,本地就可以完成,減少了socket的請求和對服務的依賴,因此性能和穩定性更好。

2. 便宜

不用說了吧。

3. 無限擴容,不存在一致性問題,分散式場景更適合

顯然隨便擴容么,沒有存儲,因此也沒有一致性問題。

4. 適用範圍更廣,除了PC,還可以支持 APP,並且可以自由定製過期時間

Session一般都是有時間限制的,APP的免登陸,一般的MC的方案無法滿足。

因此,我們適用了可逆加密演算法。

【那麼,可逆加密演算法的問題在哪裡呢?】

1. 秘鑰一旦丟失(或者被破譯),怎麼辦?

這種情況下,只能更換秘鑰,代價是所有系統的用戶需要重新登陸一遍。這個代價勉強可以接受。

2. 數據量保存大小的限制,不能加密太多的數據

嗯,一般情況下,只對核心的數據進行加密,如:用戶的ID、過期時間等。過多的數據,對帶寬是浪費。

總體來看,我們覺得風險可控並且代價可接受。

【改進方案】

對於上面的演算法,我們在後面也會存在一些改進的思路,主要思路是增加破解的難度和降低升級的複雜度。目前,手段上我們集中在二次加密上,通過少量的工作量,就使得破譯者需要做大量的工作。

雲筆,專註於互聯網技術深度研發。歡迎關注「雲筆技術博客」

http://weixin.qq.com/r/XEykvKfEqfrWrePf9xlO (二維碼自動識別)


瀏覽器的php程序目錄下有文件保存session信息,文件流也是php默認保存session的方式。而目前類似於百度這樣的高並髮網站一般都用memcache甚至其他更高科技的手段保存session數據。

伺服器端的session用session_id這種類似於資料庫外鍵的東西和客戶端cookie裡面的PHPSESSION欄位做聯繫。


session在服務端的存儲情況:

當客戶端訪問伺服器端應用的第一個頁面的時候,伺服器為客戶端創建頁面的同時也為客戶端創建獨一無二的號碼sessionID,接下來會在伺服器端的內存中開闢一塊內存叫session(它可以以名值對的形式存儲信息),並且把sessionID賦值給session。當客戶端訪問對應應用的第二個頁面的時候,此頁面會根據cookies或者URL地址中攜帶過來的sessionID尋找對於的session,根據session上記錄的信息做出響應。

session的自動過期退出時間:

如果伺服器端創建的所以session都保留的話不合理,這樣伺服器端的內存很快會用完,所以session在一定時間後會自動過期退出,從LastAccessTime開始計時,默認是30min。session的自動過期退出時間自己可以在tomcat/conf/web.xml裡面設置,如下:

&
&
&

&
&30&
&


通過 用戶ID 取用戶信息 你會

通過 SESSIONID 取 SESSION信息 你就不懂了?

←_← 有啥區別?


推薦閱讀:

PHP該怎麼學?
PHP 初學者應該用哪種框架比較好?
感覺技術提高很慢,怎麼安撫浮躁的情緒?

TAG:Web開發 | session | PHP開發 | JavaEE | JavaWeb |