1. 程式人生 > >session原理及實現共享

session原理及實現共享

http協議是無狀態的,即你連續訪問某個網頁100次和訪問1次對伺服器來說是沒有區別對待的,因為它記不住你。

那麼,在一些場合,確實需要伺服器記住當前使用者怎麼辦?比如使用者登入郵箱後,接下來要收郵件、寫郵件,總不能每次操作都讓使用者輸入使用者名稱和密碼吧,為了解決這個問題,session的方案就被提了出來,事實上它並不是什麼新技術,而且也不能脫離http協議以及任何現有的web技術。

原理很簡單,假設你訪問網頁時就像逛澡堂,第一次進去你是沒有鑰匙的,這個時候你交了錢服務檯就分配一把鑰匙給你,你走到哪裡都要帶上,因為這是你身份的唯一標識,接下來你用這把鑰匙可以去開啟一個專有的儲物櫃儲存你的衣物,游完泳,你再用鑰匙去開啟櫃子拿出衣物,最後離開游泳池時,把鑰匙歸還,你的這次游泳的過程就是一次session,或者叫做會話,在這個例子中,鑰匙就是session的key,而儲物櫃可以理解為儲存使用者會話資訊的介質。


那麼在web server中如何實現session呢?想必看了上面的例子你會很容易理解,主要是解決兩個問題,一個是鑰匙的問題,一個是儲存使用者資訊的問題。對於第一個問題,即什麼東西可以讓你每次請求都會自動帶到伺服器呢?如果你比較瞭解http協議,那麼答案一目瞭然,就是cookie,如果你想為使用者建立一次會話,可以在使用者授權成功時給他一個cookie,叫做會話id,它當然是唯一的,比如php就會為建立會話的使用者預設set一個名為phpsessid,值看起來為一個隨機字串的cookie,如果下次發現使用者帶了這個cookie,伺服器就知道,哎呀,剛剛這位顧客來了。

剩下的是解決第二個問題,即如何儲存使用者的資訊,伺服器知道會話id為abc的使用者來了,那abc想儲存自己的私人資訊,比如購物車資訊,如何處理?這個時候可以用記憶體、也可以用檔案,也可以用資料庫了,但有個要求是,資料需要用使用者的會話id即可取到,比如php就預設會把會話id為abc的使用者會話資料儲存到/tmp/phpsess_abc的檔案裡面,每次讀取都要反序列化程式可以理解的資料,寫的時候又需要序列化為持久的資料格式。


較好理解的描述:
session被用於表示一個持續的連線狀態,在網站訪問中一般指代客戶端瀏覽器的程序從開啟到結束的過程。session其實就是網站分析的訪問(visits)度量,表示一個訪問的過程。
session的常見實現形式是會話cookie(session cookie),即未設定過期時間的cookie,這個cookie的預設生命週期為瀏覽器會話期間,只要關閉瀏覽器視窗,cookie就消失了。實現機制是當用戶發起一個請求的時候,伺服器會檢查該請求中是否包含sessionid,如果未包含,則系統會創造一個名為JSESSIONID的輸出 cookie返回給瀏覽器(只放入記憶體,並不存在硬碟中),並將其以HashTable的形式寫到伺服器的記憶體裡面;當已經包含sessionid是,服務端會檢查詢到與該session相匹配的資訊,如果存在則直接使用該sessionid,若不存在則重新生成新的 session。這裡需要注意的是session始終是有服務端建立的,並非瀏覽器自己生成的。 但是瀏覽器的cookie被禁止後session就需要用get方法的URL重寫的機制或使用POST方法提交隱藏表單的形式來實現。

  
二、如何實現session的共享?



首先我們應該明白,為什麼要實現共享,如果你的網站是存放在一個機器上,那麼是不存在這個問題的,因為會話資料就在這臺機器,但是如果你使用了負載均衡把請求分發到不同的機器呢?這個時候會話id在客戶端是沒有問題的,但是如果使用者的兩次請求到了兩臺不同的機器,而它的session資料可能存在其中一臺機器,這個時候就會出現取不到session資料的情況,於是session的共享就成了一個問題。

1.各種web框架早已考慮到這個問題,比如asp.net,是支援通過配置檔案修改session的儲存介質為sql server的,所有機器的會話資料都從同一個資料庫讀,就不會存在不一致的問題;

2.以cookie加密的方式儲存在客戶端.優點是減輕伺服器端的壓力,缺點是受到cookie的大小限制,可能佔用一定頻寬,因為每次請求會在頭部附帶一定大小的cookie資訊,另外這種方式在使用者禁止使用cookie的情況下無效.

3.伺服器間同步。定時同步各個伺服器的session資訊,此方法可能有一定延時,使用者體驗也不是很好。

4.php支援把會話資料儲存到某臺memcache伺服器,你也可以手工把session檔案存放的目錄改為nfs網路檔案系統,從而實現檔案的跨機器共享。

還有一個簡單的辦法可以用於會話資訊不會頻繁變更的情況,在機器a設定使用者會話的時候,把會話資料post到機器b的一個cgi,機器b的cgi把會話資料存下來,這樣機器a和b都會有同一份session資料的拷貝。

》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》

伴隨網站業務規模和訪問量的逐步發展,原本由單臺伺服器、單個域名的迷你網站架構已經無法滿足發展需要。

此時我們可能會購買更多伺服器,並且啟用多個二級子域名以頻道化的方式,根據業務功能將網站分佈部署在獨立的伺服器上;或通過負載均衡技術(如:DNS輪詢、Radware、F5、LVS等)讓多個頻道共享一組伺服器。

OK,頭腦中我們已經構思了這樣的解決方案,不過進入深入開發後新的技術問題又隨之而來:

我們把網站程式分佈部署到多臺伺服器上,而且獨立為幾個二級域名,由於Session受實現原理的侷限(PHP中Session預設以檔案的形式儲存在本地伺服器的硬碟),使得我們的網站使用者不得不經常在幾個頻道間來回輸入使用者名稱、密碼登入,導致使用者體驗大打折扣;另外,原本程式可以直接從使用者Session變數中讀取的資料(如:暱稱、積分、登入時間等),因為無法跨伺服器同步更新Session 變數,迫使開發人員必須實時讀寫資料庫,從而增加了資料庫的負擔。

於是,解決網站跨伺服器之間的Session共享方案需求變得迫切起來,最終催生了多種解決方案,下面列舉4種較為可行的方案進行對比探討:

1. 基於NFS的Session共享

NFS是Net FileSystem的簡稱,最早由Sun公司為解決Unix網路主機間的目錄共享而研發。

這個方案實現最為簡單,無需做過多的二次開發,僅需將共享目錄伺服器mount到各頻道伺服器的本地session目錄即可,缺點是NFS依託於複雜的安全機制和檔案系統,因此併發效率不高,尤其對於session這類高併發讀寫的小檔案,會由於共享目錄伺服器的io-wait過高,最終拖累前端WEB應用程式的執行效率。

2. 基於資料庫的Session共享

首選當然是大名鼎鼎的MySQL資料庫,並且建議使用記憶體表Heap,提高session操作的讀寫效率。這個方案的實用性比較強,相信大家普遍在使用,它的缺點在於session的併發讀寫能力取決於Mysql資料庫的效能,同時需要自己實現session淘汰邏輯,以便定時從資料表中更新、刪除 session記錄,當併發過高時容易出現表鎖,雖然我們可以選擇行級鎖的表引擎,但不得不否認使用資料庫儲存Session還是有些殺雞用牛刀的架勢。

3. 基於Cookie的Session共享

這個方案我們可能比較陌生,但它在大型網站中還是比較普遍被使用。原理是將全站使用者的Session資訊加密、序列化後以Cookie的方式,統一種植在根域名下(如:.host.com),利用瀏覽器訪問該根域名下的所有二級域名站點時,會傳遞與之域名對應的所有Cookie內容的特性,從而實現使用者的Cookie化Session 在多服務間的共享訪問。

這個方案的優點無需額外的伺服器資源;缺點是由於受http協議頭信心長度的限制,僅能夠儲存小部分的使用者資訊,同時Cookie化的 Session內容需要進行安全加解密(如:採用DES、RSA等進行明文加解密;再由MD5、SHA-1等演算法進行防偽認證),另外它也會佔用一定的頻寬資源,因為瀏覽器會在請求當前域名下任何資源時將本地Cookie附加在http頭中傳遞到伺服器。

4. 基於Memcache的Session共享

Memcache由於是一款基於Libevent多路非同步I/O技術的記憶體共享系統,簡單的Key + Value資料儲存模式使得程式碼邏輯小巧高效,因此在併發處理能力上佔據了絕對優勢,目前本人所經歷的專案達到2000/秒 平均查詢,並且伺服器CPU消耗依然不到10%。

另外值得一提的是Memcache的記憶體hash表所特有的Expires資料過期淘汰機制,正好和Session的過期機制不謀而合,降低了過期Session資料刪除的程式碼複雜度,對比“基於資料庫的儲存方案”,僅這塊邏輯就給資料表產生巨大的查詢壓力。