【Flask/React】本人BLOG的緩存實現

最近惡補了HTTP協議原理,發現一年前所做的前後端整個系統的緩存協議思路還是對的,這本質上是一套帶有新鮮度檢測的被動緩存機制,所以放上來或許也沒什麼。想來這些思想本來也很多年沒變過了,現在很多工程化的很多都是經典思想的延伸罷了(當年《計算機網路》這門課還是學到了些皮毛的www至少應用層->傳輸層->網路層->數據鏈路層->物理層的每部分還大概記得(至今記得當年做的數據鏈路層的數據包包轉發FIFO的FPGA模塊(逃

在我的BLOG上觀看獲取最佳體驗。

承接上篇【React/Redux】深入理解React服務端渲染,由於React服務端渲染帶來的開銷,在內存尚可的狀況下使用緩存是個不錯的選擇,而對於這個項目,由於前後端是分離的,所以需要在Node這邊和Flask那邊來協作完成最完備的緩存,本片文章就將記錄一下我的思路。

前端架構在前面兩篇文章均已說清,而後端架構則和這篇差不多(【Flask/React/MongoDB】BlogReworkIII-如何搭建一個動態Blog),沒怎麼改過。


問題

由於前端伺服器實現了服務端渲染,考慮這個博客的文章修改不會很頻繁,其修改的頻率遠遠小於訪問頻率,所以用Cache來減少這個渲染的開銷是合適的。為了設計這個Cache,需要明確現在前後端的設計下一次客戶端請求的完整響應流程:

客戶端發起請求,服務端開始渲染,進入相應組件的生命周期並向後端發起ajax請求,後端響應後進行下一步操作。由於前後端是分離的,Blog內容管理的核心在後端,所以核心就在於判定後端返回什麼狀態時從Cache中拿東西,在什麼狀態時修改Cache,這就要求後端提供給前端一個約定的協議。

後端

在本Blog項目中,Cache這個方面,後端需要提供給前端的實際上是一個狀態,這個狀態表明前端向後端請求的數據是否發生了改動。一個顯而易見的方法是由後端先緩存所喲已經被請求過的數據,在下次請求時再拿從資料庫中得到的新數據來對比,根據比對的結果返回一個狀態碼(比如304),這樣後端響應前端的結果可以表述為:

{ content: {......}, code: 200(304)}

注意這個200和304雖然和HTTP標準中的狀態碼值一致,但卻是兩個東西,在兩種狀態下它們返回的狀態都是200,只不過數據中有個code欄位來標示狀態。

這種做法很直觀,但每次響應除了原先的資料庫查詢操作外,還要多一步對象對比的過程,這顯然是不合適的,而且在這種設計下後端的這個Cache也失去了本來的意義,必須有其他的方案來解決這個問題。這裡可以回顧一下後端的實現:

可以發現,資料庫只會在程序監聽pages文件夾內文件的改動後才會發生,並且這個改動事件是可以被程序捕獲到的,這就提供了另一個解決方案——同時維護一個cache和與其對應的state,cache中存儲響應內容,state中存儲改內容對應的修改狀態,而這個state中的內容除了初始化之外,只能伴隨文章改動的事件而修改。

在這個思想的基礎上,一個抽象的Cache和其成員便可以被構造出來:

class WebCache(): def __init__(self): self._cache = {} self._state = {} @property def flag(self): ...... def updateContent(self, parameters, content): ...... def modifyState(self, parameters): ..... def get(self, parameters): ..... def delete(self, parameters): ..... def has(self, parameters): ...... def is_modified(self, parameters): ......

這是一個Cache的基類,它的作用和後端架構中其他基類基本一致(詳見後端設計的那篇文章),它提供了一系列方法來維護內部的_cache_state,其中updateContentmodifyStatedelete是維護它們的核心方法,其他方法則負責在請求到來時判定狀態和取值,由此,整個設計便完成如下:

文本解釋如下:

  1. 當前端請求到來時,如果cache中沒有請求對應的內容,則從資料庫查出內容並調用updateContent放入緩存,此時_cache將被更新,同時_state中的對應的值將被設為False,表明未被修改,並直接返回一個code為200的響應。
  2. 當前端請求到來時,如果cache中擁有對應的內容,並且_state中對應狀態為False(表明未被修改),則直接返回一個code為304的響應。
  3. 當前端請求到來時,如果cache中擁有對應的內容,並且_state中對應狀態為True(表明已被修改),則重複1。
  4. 當文件監控器發現文件被更改、並進行到寫入資料庫這一步時,直接根據當前寫入的條目判斷該條目是否在cache中,如果在,則調用modifyState方法將_state中對應的值設為True

如此,後端的緩存以及和前端的協議便已完成。

前端

前端伺服器的Cache結構和後端基本一致,但也有一些不同,最明顯在於前端的請求會有分頁這個參數而後端沒有,並且Redux中維護的狀態和最終渲染的頁面也並非對應,比如:

文章列表、也就是/category/Skill/0這種頁面中,其對應Redux的store中的state是一個維護著listpayload的變數,list存儲著所有的文章,payload負責分頁,由於Blog自身的性質,我設計的是在一次用於訪問中只會獲取一次list,分頁通過設置payload中的page加之組件渲染邏輯來實現(本質上就是數組切片)。

在這種情況下,如果兩次請求的路由中分類都是一致的,只是分頁不同,那麼將其作為兩種請求來多做一次服務端渲染顯然是不合適的,因為我們完全可以利用第一次請求的list、加之對payload的修改來直接進行二次渲染,而不必再走一次狀態的初始化,這也就要求將Redux中的store和最終頁面的內容分別進行緩存:

let cacheStore = Immutable.fromJS({});let cachePage = Immutable.fromJS({});

cacheStore用於緩存不同請求下的store,以後端介面url為key;cachePage則用於緩存最終的結果,以客戶端請求url為key。有了Cache基本框架,便可以結合客戶端請求和後端響應來進行最後的設計:

  1. 當一次客戶端請求來到時,先解析出此次請求對應的後端介面url,並向後端發出請求。
  2. 得到後端響應,判斷狀態碼code,如果是200,直接重新走一遍完整的服務端渲染流程,如果是304,則先去查cachePage中有沒有對應的數據,如果有,則直接返回其中的內容。
  3. 如果code是304但cachePage中沒有對應的內容,假如cacheStore中也沒有對應的store,則走一遍完整的服務端渲染流程,如果有,則直接取出store執行相應的store.dispatch修改payload,接著直接進入二次渲染。
  4. 在二次渲染結束後,更新兩個Cache。

改進

Cache帶來的性能提升以內存消耗為代價,典型的空間換時間,所以能減少內存消耗的事情該做的還是要做。這一點也很簡單,考慮到cachePage中存的是最終返回給客戶端的頁面,而這種頁面終歸是要被壓縮的,所以在放入緩存之前就可以直接將其壓縮,這樣最後從裡面取到的也是壓縮後的數據,不但節省了空間還省去了每次的壓縮帶來的損耗:

cachePage = cachePage.set( frontUrl, zlib.gzipSync(page));

推薦閱讀:

使用nginx反向代理到谷歌可行嗎?
Nginx 反向代理為什麼可以提高網站性能?

TAG:前端开发 | 缓存 | 反向代理 |