HTTP協議與狀態保持
HTTP 協議本身是無狀態的,這與HTTP協議本來的目的是相符的,客戶端只需要簡單的向服務器請求下載某些文件,無論是客戶端還是服務器都沒有必要紀錄彼此過去的行為,每一次請求之間都是獨立的,好比一個顧客和一個自動售貨機或者一個普通的(非會員制)大賣場之間的關系一樣。
-
由于HTTP協議是無狀態的,而出于種種考慮希望HTTP協議之間的通信是有狀態的,
- 比如我希望記錄客戶的購買記錄,有利于數據推送
- 登陸狀態的記錄,小電影的播放記錄等等
這些都需要記錄相應的狀態,是無狀態變成有狀態,為了記錄狀態出現了cookie和session
什么是session?什么又是cookie?他倆有啥聯系和區別?
為什么要在多臺服務器間進行session的共享同步?
以及有哪些方法來實現這個同步?
session和cookie的纏綿與悱惻
cookie官方解釋:
- Cookie,有時也用其復數形式Cookies,指某些網站為了辨別用戶身份、進行session跟蹤而儲存在用戶本地終端上的數據(通常經過加密)。Cookie是由服務器端生成,發送給User-Agent(一般是瀏覽器),瀏覽器會將Cookie的key/value保存到某個目錄下的文本文件內,下次請求同一網站時就發送該Cookie給服務器(前提是瀏覽器設置為啟用cookie)
session官網解釋:
- 在計算機中,尤其是在網絡應用中,稱為“會話控制”。Session對象存儲特定用戶會話所需的屬性及配置信息。這樣,當用戶在應用程序的 Web 頁之間跳轉時,存儲在 Session 對象中的變量將不會丟失,而是在整個用戶會話中一直存在下去。當用戶請求來自應用程序的 Web 頁時,如果該用戶還沒有會話,則 Web 服務器將自動創建一個 Session 對象。當會話過期或被放棄后,服務器將終止該會話。
大家看懂了嘛?我打包票,肯定還是有盆友沒看懂。老王自己是這么來理解他們的:
cookie自己的理解
- 瀏覽器請求服務器,服務器為了區別不同的用戶請求,就需要給他們打上標簽,比如:發放一個訪問令牌(access_token)給客戶端。發放的過程是通過在HTTP請求返回的時候,通過設置HTTP的header:Set-Cookie來實現的。
- 以上就是我請求百度,他給我發放的cookie們。每一個Set-Cookie里一般會含有設置的key=value、過期時間,以及域和路徑。
- 當瀏覽器接收到這樣的返回頭以后,就把他穩穩當當的存起來,以后每次發送請求的時候,就會把他帶上(具體還要看過期時間、作用的域和路徑)。
- 這個cookie看起來像個什么東東呢?像不像有關部門給我們發放的身份證?你去有關部門申請,他就把你的ID、性別、年齡等等信息給你打到一張叫做身份證的東東上,然后發給你。以后你每次去辦點啥關鍵的事情,就需要帶上這些cookie們。
- 一般服務器會在瀏覽器里種上一些類似于訪問令牌(access_token)、用戶ID(user_id)等等的cookie,這樣你一去訪問對應的網站,他就把你認出來了。特別,像java的服務器,還會種一些類似jsession_id的cookie,服務器采用一定的算法(比如隨機算法),生成一個一定長度(比如10字節)的字符串" angOwberup ",然后發放給瀏覽器: Set-Cookie:jssesion_id= angOwberup,當瀏覽器收到這個cookie以后,就跟拿到寶一樣,好好的把這個key和value收藏了起來,以后每次去服務器請求都帶上。
session個人理解
- 與此同時,服務器把這個字符串"angOwberup"作為key,把一個叫做User的類的一個實例user,設置好id、nickname等等信息以后,放入了一個類似于map的容器里:map.put("angOwberup", user)。當瀏覽器請求來的時候,服務器就會getCookie("jsession_id"),把這個種在瀏覽器里的字符串取出來,然后用這個字符串去map里找找,看看有沒有對應的User對象:map.get(sessionId)。如果取到了,說明就找到了這個用戶的id、nickname等等信息,直接就可以在網頁上顯示:“老王你好,歡迎回來!”。如果沒有找到,有可能就跳到登錄頁面,讓用戶做登錄。
- 我們把用戶在一定時間內訪問某個網站時,請求不同頁面的過程叫做一個會話,也就是session。在同一個session里,我們可以記錄用戶訪問的狀態和信息。這樣,那個類似于map的容器就是session管理器。
- 打個形象的比喻,如果cookie是身份證,那session就是你的檔案。你的所有信息都存放在檔案里,有關部門(server)管理著你的檔案。當你要辦重要的事情時,就需要拿著身份證去有關部門提取檔案,有關部門查閱檔案后,再看要不要給你辦事兒。如果你做了壞事,他們就會往你的檔案(session)里寫一些不好的東西;當然,如果你得了什么獎,也會往里面放。
這下,是不是有點清楚cookie和session有什么聯系和區別了呢?再簡要的總結一下:- cookie就是服務器發放給客戶端的一些標識,讓客戶端記住每次請求的時候帶上,以區分不同的用戶;
- session是服務器存放在自己那里的用戶相關的數據,用每次用戶帶來的cookie去提取出來,恢復一個之前訪問的歷史或者相關環境。
好了,有了上面的內容,接下來,我們就需要討論一下那個類似于map的session管理器了。
session的管理
上面說了,服務器用了一個類似于map的容器來管理session。那具體來看,這個map是怎么樣來實現的呢?
不同的服務器、不同的語言框架都有不同的實現。比如java的服務器,有的是用文件方式來存儲的、有的是用內存cache的方式來存儲的。老王還聽說有的語言的服務器將數據做加密,然后設置成cookie,存到了客戶端(瀏覽器)。那這些實現方式都有哪些優缺點呢?我們逐個來分析。(當然,有可能還有其他的實現方法,老王可能不了解,不過大體思路相似,如有遺漏請指正)
文件方式:這種方式,將文件作為一個map,當新增一個數據的時候,就在文件中增加類似這樣的一條數據:
angOwberup =>
data={"user":{"id":1,"nickname":"老王"}};
expiry="2016-10-0100:00:00"
(當然,具體實現的時候有可能是用的二進制方式,而不是字符串)
這種方式的好處,就是能夠存儲大量的用戶session,使得這個session有效期可以比較長(比如:三個月用戶不用登錄)。不過這個方式也有對應的問題,就是文件操作比較麻煩。比如,有一個用戶的session過期了,需要刪掉這條記錄,那這個文件就需要挪動或重寫。cache方式:有好多web端的邏輯服務器都采用這種方式。這種方式好處非常明顯,就是實現起來非常簡單。將所有數據放入到內存cache中。如果有失效,直接內存刪除就可以了。不過帶來的問題也很明顯,當服務器重啟以后,所有session都丟失了。或者當有大量用戶登錄(也有可能是遭受攻擊),就會很快讓cache被充滿,然后大量session被LRU算法淘汰,造成session的大量失效,使得用戶需要反復登錄等操作。
cookie方式:這種方式是最偷懶的方式。就是我服務器任何數據都不存,我把你們所有的客戶端當做我的存儲器,我就需要做一個加密和解密操作。當然這種方式最大的好處就是實現極其簡單(還有其他的好處,稍后再說),不過問題也是很明顯的,就是客戶端要記錄大量信息,同時還要保證加密信息的安全。如果session里要存放大數據,這種方式就不是很適合了。
除了上述說到的優缺點以外,A、B兩種方式還有另外一個問題,就是當我有不止一臺服務器的時候,不同服務器間的session數據共享就成問題了。比如,最初我只有一臺服務器1,他的session里記錄了user-1和user-2的數據。這個時候,我需要增加一臺服務器2。當nginx把用戶的請求轉發到服務器2的時候,他就傻眼了:用戶帶了一個jsession_id=angOwberup這個的cookie過來,而在他的session管理器里卻找不到這樣一個session數據。那該怎么辦?!(苦!惱!啊!)
因此,就出現了我們文章一開始提到的問題:在分布式系統里,用戶session如何才能實現同步?
session的同步
有了上面的情況,我們就必須要去考慮,如何在多個服務器之間實現session同步這個操作。常見的做法有以下幾種,我們逐個來看看:
進程間通信傳遞session數據。
- 這是最容易想到的一個方法。我們在不同的server服務里開一個socket,然后用socket來將相互擁有的session數據進行傳遞。我記得多年以前tomcat就是采用這樣的方式來做的(已經很久沒用過tomcat了,不知道現在是否還在這樣使用)。
這種方式的好處很明顯,就是原理簡單明了;壞處也很明顯,就是同步合并過程復雜,還容易造成同步延遲。比如,某個用戶在server-1登錄了,server-1存儲了這個用戶的session,當正準備將數據同步給server-2的時候,由于用戶訪問實在是太快(飛一般的速度),server-2還沒收到server-1傳來的session數據,用戶訪問就已經來了。這個時候,server-2就不能識別這個用戶,造成用戶需要再次登錄。
而且,當有成千上萬臺服務器的時候,session同步就是一個噩夢:每一個服務器都要將自己擁有的session廣播給其他所有機器,而且還要隨時進行,不能停歇…… (最后這些機器估計都是累死的) -
cookie存儲方式。我們在上面講到了一個很偷懶的方式,就是把session數據做加密,然后存儲到cookie中。用戶請求到了,就直接從cookie讀取,然后做解密。這種方式真是把分布式思想發揮到了一個相當的高度。他把用戶也當做分布式的一員,你要訪問數據,那你就自己攜帶著他,每次到服務器的時候,我們的服務器就只負責解密……
對于session里只存放小數據,并且加密做的比較好(防止碰撞做暴力破解)的系統來講,這是一個比較好的選擇。他實現超級簡單,而且不用考慮數據的同步。
不過如果要往session里存放大數據的情況就不是太好處理。或者安全性要求很高的系統,也不是太好的一個方式(數據有被破解的風險)。 -
cache集群或者數據庫做session管理。我們也可以采用另外一種架構來解決session同步問題,那就是引入統一session接入點。
- 我們session放入到cache集群或者數據庫中,每次請求的時候,都從他們中來獲取。這樣,所有的機器都能獲取到最新的session數據。這種方案也是很多中大型網站采用的解決方案。他實現起來相對簡單(利用cache集群或者主從數據庫自身的管理來實現多機的互備),而且效率很高,安全性也不錯。
- 還有一種方式是從上面這種方式延展出來的,就是提供session服務。這個服務負責管理session,其他服務器每次從這個服務處獲取session數據,從而達到數據的共享。
百度的session同步
- 大家如果仔細觀察一下baidu或者google,你做登錄的時候,他們可能會讓你跳到passport.baidu.com 或者accounts.google.com這兩個域名之下。這兩個就是他們用來做用戶登錄和類似session管理的一個地方(由于之前只呆過baidu,所以google并不是非常清楚)。當一個訪問請求來的時候,server就從cookie里取類似session_id的東東,然后用這個東東去passport服務去請求用戶的session數據。
- 這種方式的好處就在于:
- 可以非常方便的擴展用戶登錄的數量以及存儲數據的大小。當時在x度的時候,N億用戶的session都在這個系統里進行管理;
- 方便做性能優化。如果用cache集群的方案,如果cache有機器壞掉,那么就會造成一部分用戶session失效;
- 如果用數據庫方案,如果量太大,有可能會出現性能問題。而這種方案在實現的時候,可以用cache和數據庫結合的實現方式,保證高效和穩定。同時,針對一些接口,可以做性能的優化,提升查詢效率;
- 對外封閉,保證數據安全。這種方式還有一個好處,就是可以將加密算法、密鑰等封閉在系統內部,對外只暴露接口,使得數據安全性更有保障。(涉及到用戶信息的,都是隱私!)
- 不過,這種方式也有自己的問題,就是運維相對更復雜,有可能需要專門的團隊去管理這些系統。
當然,除了上述的一些方式以外,還有其他的手段(比如,在入口nginx處對用戶cookie做一致Hash,將某一用戶分配到固定機器)。鑒于老王知識有限,且碼字速度有限,就先介紹這些了,不知道你是否看懂了呢?