摘要:需要在服務(wù)中存儲(chǔ)更多信息,如果使用的是關(guān)系數(shù)據(jù)庫(kù),那么載入和存儲(chǔ)的的代價(jià)可能會(huì)很高。這次我們使用令牌來(lái)引用關(guān)系數(shù)據(jù)庫(kù)表中負(fù)責(zé)存儲(chǔ)用戶登錄信息的條目。而我們要做的就是適用重新實(shí)現(xiàn)登錄功能,取代由關(guān)系數(shù)據(jù)庫(kù)實(shí)現(xiàn)的登錄功能。
上一篇文章:Python--Redis實(shí)戰(zhàn):第一章:初識(shí)Redis:第三節(jié):你好Redis-文章投票試煉
下一篇文章:Python--Redis實(shí)戰(zhàn):第二章:使用Redis構(gòu)建Web應(yīng)用:第二節(jié):使用Redis實(shí)現(xiàn)購(gòu)物車
從高層次的角度來(lái)看,Web應(yīng)用就是通過(guò)HTTP協(xié)議對(duì)網(wǎng)頁(yè)瀏覽器發(fā)送的請(qǐng)求進(jìn)行響應(yīng)的服務(wù)器或者服務(wù)【service】。一個(gè)Web服務(wù)器對(duì)請(qǐng)求進(jìn)行響應(yīng)的典型步驟如下:
服務(wù)器對(duì)客戶端發(fā)來(lái)的請(qǐng)求【request】進(jìn)行解析。
請(qǐng)求被轉(zhuǎn)發(fā)給一個(gè)預(yù)定義的處理器【handler】
處理器可能會(huì)從數(shù)據(jù)庫(kù)取出數(shù)據(jù)
處理器根據(jù)取出的數(shù)據(jù)對(duì)模板【template】進(jìn)行渲染(render)
處理器向客戶端返回渲染后的內(nèi)容作為對(duì)請(qǐng)求的響應(yīng)【response】
以上列舉的5個(gè)步驟從高層次的角度展示了典型Web服務(wù)器的運(yùn)作方式,這種情況下的Web請(qǐng)求被認(rèn)為是無(wú)狀態(tài)的【stateless】,也就是說(shuō),服務(wù)器本身不會(huì)記錄與過(guò)往有關(guān)的任何信息,這使得失效【fail】的服務(wù)器可以很容易地被替換掉。有不少書籍專門介紹了如何優(yōu)化響應(yīng)過(guò)程的各個(gè)步驟,本章要做的事情也類似,不同之處是,我們將介紹如何使用更快的Redis查詢來(lái)替代傳統(tǒng)的關(guān)系數(shù)據(jù)庫(kù)查詢,已經(jīng)如何使用Redis來(lái)完場(chǎng)一些使用關(guān)系數(shù)據(jù)庫(kù)沒(méi)有辦法高效完場(chǎng)的任務(wù)。
本章的所有內(nèi)容都是圍繞著發(fā)現(xiàn)并解決【Fake Web Retailer】這個(gè)虛構(gòu)的大型網(wǎng)上商店來(lái)展開(kāi)的,這個(gè)商店每天都會(huì)有大約500萬(wàn)名不同的用戶,這些用戶會(huì)給網(wǎng)站帶來(lái)一億次點(diǎn)擊,并從網(wǎng)站購(gòu)買超過(guò)10萬(wàn)件商品。我們之所以將這幾個(gè)數(shù)據(jù)量設(shè)置的特別大,是考慮【如果可以在大數(shù)據(jù)背景下順利解決問(wèn)題,那么解決小數(shù)據(jù)量和中等數(shù)據(jù)量引發(fā)的問(wèn)題就更不在話下】。
登錄和cookie緩存每當(dāng)我們登錄互聯(lián)網(wǎng)服務(wù)的時(shí)候,這些服務(wù)都會(huì)使用cookie來(lái)記錄我們的身份。cookie由少量數(shù)據(jù)組成,網(wǎng)站會(huì)要求我們的瀏覽器存儲(chǔ)這些數(shù)據(jù),并在每次服務(wù)器請(qǐng)求發(fā)送時(shí)將這些數(shù)據(jù)傳回給服務(wù)器。對(duì)于用來(lái)登錄的cookie,有兩種常見(jiàn)的方式可以將登錄信息存儲(chǔ)在cookie里面:
簽名【signed】cookie
令牌【token】cookie
簽名cookie通常會(huì)存儲(chǔ)用戶名,可能還有用戶ID,用戶最后一次登錄成功的時(shí)間,以及網(wǎng)站覺(jué)得有用的其他任何信息。除了用戶的相關(guān)信息之外,簽名cookie還包含了一個(gè)簽名,服務(wù)器可以使用這個(gè)簽名來(lái)驗(yàn)證瀏覽器發(fā)送的消息是否未經(jīng)改動(dòng)(比如將cookie中的登錄用戶名改成另一個(gè)用戶)。
令牌cookie會(huì)在cookie里面存儲(chǔ)一串隨機(jī)字節(jié)作為令牌,服務(wù)器可以根據(jù)令牌在數(shù)據(jù)庫(kù)中查詢令牌的擁有者。隨著時(shí)間的推移,舊令牌會(huì)被新令牌去掉。
cookie類型 | 優(yōu)點(diǎn) | 缺點(diǎn) |
---|---|---|
簽名cookie | 驗(yàn)證cookie所需的一切信息都存儲(chǔ)在cookie里面,cookie可以包含額外的信息,并且對(duì)這些信息進(jìn)行簽名也很容易。 | 正確的處理簽名很難,很容易忘記對(duì)數(shù)據(jù)進(jìn)行簽名,或者忘記驗(yàn)證數(shù)據(jù)的簽名,從而造成安全漏洞。 |
令牌cookie | 添加信息非常容易,cookie的體積非常小,因此移動(dòng)終端和速度較慢的客戶端可以更快地發(fā)送請(qǐng)求。 | 需要在服務(wù)中存儲(chǔ)更多信息,如果使用的是關(guān)系數(shù)據(jù)庫(kù),那么載入和存儲(chǔ)的cookie的代價(jià)可能會(huì)很高。 |
這次我們使用令牌cookie來(lái)引用關(guān)系數(shù)據(jù)庫(kù)表中負(fù)責(zé)存儲(chǔ)用戶登錄信息的條目【entry】。除了用戶登錄信息之外,我們還可以將用戶的訪問(wèn)時(shí)長(zhǎng)和已瀏覽商品的數(shù)量等信息存儲(chǔ)到數(shù)據(jù)庫(kù)里面,這樣便于將來(lái)通過(guò)分析這些信息來(lái)學(xué)習(xí)如果更好得向用戶推銷商品。
一般來(lái)說(shuō),用戶在決定購(gòu)買某個(gè)或某些商品之前,通常都會(huì)先瀏覽多個(gè)不同商品,而記錄用戶瀏覽過(guò)的所有商品以及用戶最后一次訪問(wèn)頁(yè)面的時(shí)間等信息,通常會(huì)導(dǎo)致大量的數(shù)據(jù)庫(kù)寫入。從長(zhǎng)遠(yuǎn)來(lái)看,用戶的這些瀏覽數(shù)據(jù)的確非常有用,但問(wèn)題是,即便經(jīng)過(guò)優(yōu)化,大多數(shù)關(guān)系數(shù)據(jù)庫(kù)在每臺(tái)數(shù)據(jù)庫(kù)服務(wù)器上每秒也只能插入、更細(xì)或者刪除200~2000個(gè)數(shù)據(jù)行。盡量批量插入、批量更新和批量刪除等操作可以更快地速度執(zhí)行,但因?yàn)榭蛻舳嗣看螢g覽網(wǎng)頁(yè)都只更新少數(shù)幾行,所以高速的批量插入在這里并不適用。
我們假設(shè)我們的網(wǎng)站每天的負(fù)載量都比較大:平均每秒大約1200次寫入,高峰時(shí)期每秒接近6000次寫入,所以它必須部署10臺(tái)關(guān)系數(shù)據(jù)服務(wù)器才能應(yīng)對(duì)高峰時(shí)期的負(fù)載量。而我們要做的就是適用Redis重新實(shí)現(xiàn)登錄cookie功能,取代由關(guān)系數(shù)據(jù)庫(kù)實(shí)現(xiàn)的登錄cookie功能。
首先,我們將使用一個(gè)散列來(lái)存儲(chǔ)登錄cookie令牌和已登錄用戶之間的映射。要檢查一個(gè)用戶是否已經(jīng)登錄,需要根據(jù)給定的令牌來(lái)查找與之對(duì)應(yīng)的用戶,并在用戶已經(jīng)登錄的情況下,返回該用戶的ID。
def check_token(conn,token): #嘗試獲取并返回令牌對(duì)應(yīng)的用戶 return conn.hget("login:",token)
對(duì)令牌進(jìn)行檢查并不困難,因?yàn)榇蟛糠謴?fù)雜的工作都是在更新令牌時(shí)完成的:用戶每次瀏覽頁(yè)面時(shí),程序都會(huì)對(duì)用戶存儲(chǔ)在登錄散列里面的信息進(jìn)行更新,并將用戶的令牌和當(dāng)前時(shí)間戳添加到記錄最近登錄用戶的有序集合里面;如果用戶正在瀏覽的是一個(gè)商品頁(yè)面,那么程序還會(huì)將這個(gè)商品添加到記錄這個(gè)用戶最近瀏覽過(guò)的商品的有序集合里面,并在被記錄商品的數(shù)據(jù)超過(guò)25個(gè)時(shí),對(duì)這個(gè)有序集合進(jìn)行修建。
#更新令牌 import time def update_token(conn,token,user,item=None): timestamp=time.time() #h獲取當(dāng)前時(shí)間戳 conn.hset("login:",token,user) #維持令牌與已登陸用戶之間的映射 conn.zadd("recent:",token,timestamp) #記錄領(lǐng)哦哎最后一次出現(xiàn)的時(shí)間 if item: conn.zadd("viewed:"+token,item,timestamp) #記錄用戶瀏覽郭的商品 conn.zremrangebyrank("viewed:"+token,0,-26) #移除舊的記錄,值保留用戶最近瀏覽過(guò)的25個(gè)商品
通過(guò)update_token()函數(shù),我們可以記錄用戶最后一次瀏覽商品的時(shí)間以及用戶最近瀏覽了哪些商品。在一臺(tái)最近幾年生產(chǎn)的服務(wù)器上面,使用update_token()函數(shù)每秒至少記錄20000件商品,這比我們預(yù)估的網(wǎng)站高峰期所需的6000次寫入要高3倍有余。不僅如此,通過(guò)后面介紹的一些方法,我們還可以進(jìn)一步優(yōu)化update_token()函數(shù)的運(yùn)行速度。但在優(yōu)化前,性能也比原有的關(guān)系數(shù)據(jù)庫(kù)性能提升了10~100倍。
因?yàn)榇鎯?chǔ)會(huì)話數(shù)據(jù)所需的內(nèi)存會(huì)隨著時(shí)間的推移而不斷增加,所以我們需要定期清理舊的會(huì)話數(shù)據(jù),為了限制會(huì)話數(shù)據(jù)的數(shù)量,我們決定只保留最新的1000萬(wàn)個(gè)會(huì)話。清理舊會(huì)話的程序由一個(gè)循環(huán)構(gòu)成,這個(gè)循環(huán)每次執(zhí)行的時(shí)候,都會(huì)檢查存儲(chǔ)最新登錄令牌的有序集合大小,如果有序集合的大小超過(guò)了限制,那么程序就會(huì)從有序集合里面移除最多100個(gè)最舊的令牌,并從記錄用戶登錄頁(yè)面的散列表里面,移除被刪除令牌對(duì)應(yīng)的用戶的信息,并對(duì)存儲(chǔ)了這些用戶最近瀏覽商品記錄的有序集合進(jìn)行清理。如果令牌的數(shù)量未超過(guò)限制,那么程序會(huì)休眠1秒,之后再重新進(jìn)行檢查。
#清理舊會(huì)話 import time QUIT=False LIMIT=10,000,000 def clean_sessions(conn): while not QUIT: #目前已有令牌的數(shù)量 size=conn.zcard("recent:") if size<=LIMIT: #令牌數(shù)量未超過(guò)限制,休眠1秒后再重新檢查 time.sleep(1) continue end_index=min(size-LIMIT,100) tokens=conn.zrange("recent:",0,end_index-1) session_keys=[] #為那些將要?jiǎng)h除的令牌構(gòu)建鍵名 for token in tokens: session_keys.append("viewed:"+token) #移除最舊的那些令牌 conn.delete(*session_keys) conn.hdel("login:",*tokens) conn.zrem("recent:",*tokens)
讓我們通過(guò)計(jì)算來(lái)了解一下,這段簡(jiǎn)短的代碼為什么能夠妥善地處理每天500萬(wàn)人次的訪問(wèn):假設(shè)網(wǎng)站每天有500萬(wàn)用戶訪問(wèn),并且每天的用戶都和之前的不一樣,那么只需要兩天,令牌的數(shù)量就會(huì)達(dá)到1000萬(wàn)上限,并將網(wǎng)站的內(nèi)存空間銷毀殆盡,因?yàn)橐惶煊校?4*3600=86400秒,而網(wǎng)站平均每秒產(chǎn)生5 000 000/86400<58個(gè)新會(huì)話,如果清理函數(shù)以每秒的頻率運(yùn)行,那么它每秒需要清理將近60個(gè)令牌,才能防止令牌的數(shù)量過(guò)多的問(wèn)題發(fā)生。但是實(shí)際上,我們定義的令牌清理函數(shù)在通過(guò)網(wǎng)絡(luò)來(lái)運(yùn)行時(shí),每秒能夠清理10 000多個(gè)令牌,在本地運(yùn)行時(shí),每秒能夠清理60 000多個(gè)令牌,這比所需的清理速度快樂(lè)150~1000倍,所以因?yàn)榕f令牌過(guò)多而導(dǎo)致網(wǎng)站空間耗盡的問(wèn)題不會(huì)出現(xiàn)。
熟悉多線程編程或者并發(fā)編程的讀者可能會(huì)發(fā)現(xiàn)上面的清理函數(shù)包含了一個(gè)競(jìng)爭(zhēng)條件【race condition】:如果清理函數(shù)正在刪除某個(gè)用戶的信息,而這個(gè)用戶又在同一時(shí)間訪問(wèn)網(wǎng)站的話,那么競(jìng)爭(zhēng)條件就會(huì)導(dǎo)致用戶的信息被錯(cuò)誤的刪除。目前來(lái)看,這個(gè)競(jìng)爭(zhēng)條件除了會(huì)使得用戶需要重新登錄一次之外,并不會(huì)對(duì)程序記錄的數(shù)據(jù)產(chǎn)生明顯的影響,所以我們暫時(shí)擱置這個(gè)問(wèn)題,之后會(huì)講解防止類似的競(jìng)爭(zhēng)條件發(fā)生的方法。
通過(guò)使用Redis來(lái)記錄用戶信息,我們成功地將每天要對(duì)數(shù)據(jù)庫(kù)執(zhí)行的行寫入操作減少了數(shù)百萬(wàn)次。雖然這非常的了不起,但這只是我們使用Redis構(gòu)建Web應(yīng)用程序的第一步,接下來(lái)我們將展示如何使用Redis來(lái)處理另一種類型的cookie。
上一篇文章:Python--Redis實(shí)戰(zhàn):第一章:初識(shí)Redis:第三節(jié):你好Redis-文章投票試煉
下一篇文章:Python--Redis實(shí)戰(zhàn):第二章:使用Redis構(gòu)建Web應(yīng)用:第二節(jié):使用Redis實(shí)現(xiàn)購(gòu)物車
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/42568.html
摘要:上一篇文章實(shí)戰(zhàn)第二章使用構(gòu)建應(yīng)用第一節(jié)登錄和緩存下一篇文章實(shí)戰(zhàn)第二章使用構(gòu)建應(yīng)用第三節(jié)網(wǎng)頁(yè)緩存網(wǎng)景公司在世紀(jì)年代中期最先在網(wǎng)絡(luò)中使用了,這些最終變成了我們現(xiàn)在使用的。從購(gòu)物車?yán)锩嬉瞥付ㄉ唐穼⒅付ǖ纳唐诽砑拥劫?gòu)物車 上一篇文章: Python--Redis實(shí)戰(zhàn):第二章:使用Redis構(gòu)建Web應(yīng)用:第一節(jié):登錄和cookie緩存下一篇文章:Python--Redis實(shí)戰(zhàn):第二章:使用R...
摘要:為了防止用戶對(duì)同一篇文章進(jìn)行多次投票,網(wǎng)站需要為每一篇文章記錄一個(gè)已投票用戶名單。上一篇文章實(shí)戰(zhàn)第一章初識(shí)第二節(jié)數(shù)據(jù)結(jié)構(gòu)簡(jiǎn)介下一篇文章實(shí)戰(zhàn)第二章使用構(gòu)建應(yīng)用第一節(jié)登錄和緩存 上一篇文章: Python--Redis實(shí)戰(zhàn):第一章:初識(shí)Redis:第二節(jié):Redis數(shù)據(jù)結(jié)構(gòu)簡(jiǎn)介下一篇文章:Python--Redis實(shí)戰(zhàn):第二章:使用Redis構(gòu)建Web應(yīng)用:第一節(jié):登錄和cookie緩存 ...
閱讀 3712·2021-10-12 10:11
閱讀 1978·2019-08-30 15:53
閱讀 1587·2019-08-30 13:15
閱讀 2301·2019-08-30 11:25
閱讀 1796·2019-08-29 11:24
閱讀 1646·2019-08-26 13:53
閱讀 3520·2019-08-26 13:22
閱讀 1746·2019-08-26 10:24