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