對于REST中無狀態(tài)(stateless)的一點(diǎn)認(rèn)識
今天早上在Yahoo的郵件列表里看到一篇頗有意思的討論,標(biāo)題為RESTful vs. unRESTful: Session IDs and Authentication(51CTO編者注:意為REST對非REST,Session ID與驗證)。文中讓發(fā)起討論的朋友大惑不解的是這樣一個問題:為什么在請求中傳遞SessionID被普遍認(rèn)為是unRESTful的,而將用戶的credentials包含在每個請求里又是一種非常RESTful的做法。看了他接下來對于REST架構(gòu)風(fēng)格中"statelessness"屬性的理解后,我覺得有必要對這個經(jīng)常會被人誤解詞匯以及相關(guān)概念做一個簡要的整理,希望能夠通過這篇隨筆解釋清楚什么是狀態(tài),為什么要實現(xiàn)無狀態(tài),以及REST風(fēng)格架構(gòu)中的兩種狀態(tài)的區(qū)別,***我會從我的理解出發(fā)來回答作者提出的這個問題。
首先,一個Web應(yīng)用程序協(xié)議的“狀態(tài)”在通常指的是為兩個相互關(guān)聯(lián)的用戶交互操作保留的某種公共信息,它們常常被用來存儲工作流或用戶狀態(tài)信息等數(shù)據(jù)。這些信息可以被指定不同的作用域如page,request,session或全局作用域,而存儲他們的責(zé)任也同樣可以由Client端或Server端負(fù)責(zé)。雖然存儲狀態(tài)為企業(yè)軟件開發(fā)帶來了諸多便利,但是它也給分布式系統(tǒng)的其他方面帶來了許多限制,比如在負(fù)載均衡方面,在有狀態(tài)的模式下,一個用戶的請求必須被提交到保存有其相關(guān)狀態(tài)信息的服務(wù)器上,否則這些請求可能無法被理解,這也就意味著在此模式下服務(wù)器端無法對用戶請求進(jìn)行自由調(diào)度。于此相關(guān)的另一個問題是容錯性,倘若保有用戶信息的服務(wù)器宕機(jī),那么該用戶最近的所有交互操作將無法被透明地移送至備用服務(wù)器上,除非該服務(wù)器時刻與主服務(wù)器同步全部用戶的狀態(tài)信息。此外,由于HTTP本身不是一個有狀態(tài)的協(xié)議,開發(fā)人員必須通過模擬實現(xiàn)狀態(tài)的鈍化與激活等。于是為了克服這些不足,無狀態(tài)(Statelessness)架構(gòu)風(fēng)格屬性受到了廣泛關(guān)注。
無狀態(tài)指的是任意一個Web請求必須完全與其他請求隔離,當(dāng)請求端提出請求時,請求本身包含了相應(yīng)端為相應(yīng)這一請求所需的全部信息。這一約束的出現(xiàn)改善了分布式系統(tǒng)的可見性、可靠性以及可伸縮性,具體的介紹可以參考Roy T. Fielding博士的論文,這里就不哆嗦了。這些從整個系統(tǒng)角度來看無狀態(tài)似乎過于抽象,那么對于用戶來說,怎么感覺的有狀態(tài)與無狀態(tài)的差別呢。簡單的方法是瀏覽器的后退按鈕,如果一個網(wǎng)站期望用戶以A->B->C的流程來交互,而在執(zhí)行至B時回退的話,那么系統(tǒng)很有可能不是按照其所期望的方式運(yùn)行,因為用戶的狀態(tài)可能被不可逆地修改了。反過來,搜索引擎(我指的是普通意義上的搜索引擎,而不是根據(jù)用戶搜索歷史個性化了的)是一個無狀態(tài)架構(gòu)的范例。任何用戶可以在瀏覽器地址欄中輸入http://www.google.com/search?q=RESTful&start=100來獲得從***百條開始的關(guān)于RESTful的記錄,并且當(dāng)Google摩洛哥服務(wù)器癱瘓時,相關(guān)用戶請求會被透明地移送至其他服務(wù)器。
一切似乎很明了,那么是什么導(dǎo)致了那位朋友的誤解呢,答案是RESTful架構(gòu)對于state的兩個不同的解釋: 應(yīng)用狀態(tài)(Application State)和資源狀態(tài)(Resource State)。應(yīng)用狀態(tài)指的是與某一特定請求相關(guān)的狀態(tài)信息,而資源狀態(tài)則反映了某一存儲在服務(wù)器端資源在某一時刻的特定狀態(tài),該狀態(tài)不會因為用戶請求而改變,任何用戶在同一時刻對該資源的請求都會獲得這一狀態(tài)的表現(xiàn)(Representation)。RESTful架構(gòu)要求服務(wù)器端不保有任何與特定HTTP請求相關(guān)的資源,所以應(yīng)用狀態(tài)必須由請求方在請求過程中提供。那么再回到那個郵件列表中的問題,為什么傳遞一個session ID是違背REST架構(gòu)風(fēng)格而傳遞user credentials卻不是。我想作者的疑惑源于他沒有分清什么是有狀態(tài)和無狀態(tài)的架構(gòu)屬性,而認(rèn)為“傳遞某種表示狀態(tài)的信息”到服務(wù)器便是“有狀態(tài)”的表現(xiàn)。其實有狀態(tài)和無狀態(tài)與請求本身沒有多大關(guān)聯(lián),重要的是狀態(tài)信息是由請求方還是響應(yīng)方負(fù)責(zé)保存。在Session ID可以被認(rèn)為是一個用來標(biāo)識某一會話狀態(tài)的Key,將其傳遞給服務(wù)器端意味著這樣一個請求:“請幫我取出這個狀態(tài)信息”,也就是說這個請求假設(shè)響應(yīng)方保有著狀態(tài)信息。由于與某一特定請求相關(guān)的狀態(tài)屬于應(yīng)用狀態(tài),而RESTful架構(gòu)要求任何此類狀態(tài)由請求方負(fù)責(zé)提供,所以傳遞Session ID被認(rèn)為是unRESTful的做法。反過來,user credential作為一種應(yīng)用狀態(tài),是被期望由請求方提供的,所以在請求中傳遞user credentials(姑且忽略安全性問題)是符合RESTful架構(gòu)規(guī)范的。
這篇隨筆或多或少散發(fā)著某種純粹主義的味道,但我覺得有些概念是值得玩味的。任何一種架構(gòu)風(fēng)格的出現(xiàn)都有其期望的,對現(xiàn)有方案的改進(jìn)或期望克服的問題。作為REST來說,它所期望的是組件的可伸縮性,組件的獨(dú)立部署,接口統(tǒng)一等特性,而無狀態(tài)作為實現(xiàn)這組需求的一個特性,個人認(rèn)為是有必要清楚了解并實際開發(fā)過程中落實的。
【編輯推薦】