# 5. redis 實現 cache 系統原理
#### 1. 介紹
cache就是人們所說的緩存。我們這里所說的cache是web上的。對用戶來說,衡量一個網站是否具有良好的體驗,其中一個標準就是響應速度的快慢。可能網站剛上線,功能還較少,數據庫的記錄也不多的情況下,網站可能訪問速度比較快,也不需要優化。但是隨著網站發展起來,功能越來越多,數據庫越來越大的時候,這個時候可能網站的訪問速度就會下降。無論網站剛上線初期還是到一定程度的情況,我們都希望網站具有良好的響應速度。
而要怎樣提高頁面響應速度呢?其中一個方法當然就是本章所說的cache。先來說整個網站的請求響應的過程:用戶從瀏覽器發出一個請求,比如點擊一個按鈕或在瀏覽器輸入網址回車,然后經過層層的網絡最終到達網站的服務器,假設我們用nginx來作為靜態文件服務器,而用unicorn作為運行ruby代碼的容器,先經過nginx處理,nginx一般是處理html,css,js的,它會默認處理帶有htlm,css,js后綴的請求,比如/articles.html,如果發現有它處理不了的,比如/articles/1這樣的請求地址,因為nginx沒有匹配這樣的地址,就會反向代理到unicorn,unicorn是運行ruby代碼的,它會連接數據庫,它可能從數據庫取到一些數據,把數據處理完,再返回給nginx,最nginx返回給用戶。
這里所說的unicorn是運行ruby代碼的應用容器,當然也可以是php或java的容器,比如tomcat等。道理是一樣的。
```
server {
listen 80 default_server;
server_name www.rails365.net;
root /home/yinsigan/rails365/current/public;
...
try_files $uri/index.html $uri @rails365;
location @rails365 {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_pass http://rails365;
}
...
}
```
上面是nginx的配置文件的部分內容,網站根目錄是`/home/yinsigan/rails365/current/public`,具體看`try_files $uri/index.html $uri @rails365;`這一句,當用戶在瀏覽器請求/about.html地址時,nginx會首先看網站根目錄有沒有about.html這個文件,有的話就直接讀出根目錄下的about.html,因為nginx默認會對html后綴的文件作處理的,讀完后直接返回響應給用戶,就完成了這一次請求,沒有的話,就會請求@rails365的內容,也就是反向代理到unicorn服務器。
整個請求過程無論哪一階段都是需要時間的。而最影響速度也最容易產生性能瓶頸的主要有三個地方:
1. nginx處理靜態文件的過程
2. nginx和unicorn交互的過程
3. unicorn和數據庫交互的過程
第一點,nginx處理靜態文件的過程主要指的是處理圖片、javascript、css文件的過程。假如這些文件數量很多,體積又很大,在這一過程是需要耗費很多時間的,但是這些可以通過nginx的gzip來壓縮大小、瀏覽器cache或CDN等技術來解決,這點不在cache的范圍內。
第二點,nginx作為反向代理服務器和應用服務器unicorn的交互也是需要時間的,nginx也作為靜態服務器只能處理一些簡單的靜態服務器,而unicorn可以處理復雜的邏輯,不過最后unicorn還是要轉化成html給nginx,nginx再給用戶,所以整個請求過程如果不到unicorn,由nginx直接返回那就更好了。這樣是能大大增快響應速度的,因為不僅nginx到unicorn那步少了,而unicorn到數據庫這一步也不用了,所以還是有效的,但是它有一些不好的弊端,具體的我們下面會說到。第二點的解決方案是通過nginx的proxy\_cache或者文件cache來解決。
第三點,應用服務器和數據庫的交互,這個數據庫我們一般指的是關系型數據庫,比如MySQL、PostgreSQL等。這個過程是很重要且很常見的,畢竟幾乎所有網站都需要數據庫來存儲數據吧,但數據庫很龐大時或者對數據庫處理不當時,這個交互過程是很耗費時間的,所以我們會在這個過程用redis作為cache來解決。
#### 2. cache的種類
cache有很多種,比如上文提到的文件cache,nginx的proxy\_cache,瀏覽器的cache,html片斷的cache,除此之外,還有代理服務器的cache,CDN的cache,數據庫級的查詢cache。這篇文章我們只會講述文件cache還有用redis來實現cache的情況。
#### 3. 文件cache
文件cache在上文有提到,就是在nginx和unicorn交互的過程中發揮作用的,就是讓請求直接給nginx處理,而不經過unicorn。打個比方,比如要瀏覽一個博客網站的首頁,首頁列出了最近的十篇博文,這些博文肯定是存儲在數據庫中,請求先通過nginx,nginx再通過unicorn,unicorn去數據庫取到這些博文,再組裝成html文件再給nginx,nginx再給用戶,整個過程完成。而文件cache要做的是當unicorn組裝成html給nginx的時候,也順便在網站根目錄下生成.html結尾的文件,這樣下次訪問就不用經過unicorn了,直接在網站根目錄取文件就可以了。這種方式,說白了,就是把動態的查詢靜態化,讓請求過程少了,當數據庫有查詢瓶頸的時候用這種方式是有好處的。但是這種方式有一個弊端,就是有登錄系統的情況,比如一個首頁,同樣是相同的網址,每個人訪問就有不同的頁面,因為是不同的用戶在訪問,比如導航菜單,可以就會出現你的名字。文件cache辦不到這種事,因為它是根據地址來處理的,比如訪問`http://www.example.com/articles.html`,它會找根目錄的articles.html文件,可是這個文件只有一個啊,但我們需要的是有多少個用戶就有不同的頁面,所以它不好判斷,就算判斷了,也失去了本來靜態化的意義。所以文件cache只適合那種展示頁面用的,很多時候并不適用,畢竟現在誰的網站沒個登錄系統呢。
#### 4. 用redis實現cache的原理
上文也有提到文件cache的情況,cache的原理就是用一種訪問更快的介質或更少的請求來提高查詢速度。用redis來實現cache也是一樣的道理。我們先不管redis最終會存儲什么內容,我們有個對比的介質,那就是關系型數據庫,把存到關系型數據庫的內容換到redis來存,那就是用redis來實現的cache。這樣就出現了兩種存儲介質的數據的對比,這個數據的存儲是有時間性的。當第一次瀏覽帶有數據庫查詢的頁面的時候,頁面的數據先從數據庫那里獲取,獲取完之后存一份到redis,比如存成string,`articles_count: 100`,意思是把文章的總數存到redis中。當存到redis時,我們可以設置過期時間,也可以讓它永遠不過期。第二次以及以后的訪問,就會直接到redis去查找key為articles\_count的是不是有值(數據過期就等于沒有值),有的話,直接取,沒有話,還是會取數據庫,然后再放到redis。當你的真實的數據庫記錄更新的時候,比如這個時候增加了一篇文章,除了把這一篇文章存進數據庫外,還要把redis中的對應的值改過來,這樣就能保證取到最新的值的。
#### 5. 頁面哪些地方需要cache
現在以本站作為實例,來考慮下頁面上哪些地方要使用cache。使用cache有個原則,就是這個頁面部分的內容可能是耗時的,也有可能是很少改變的。比如底部,這個部分是基本不變的。

還有首頁的中間部分"最近的文章"、"最熱門的文章"、"所有的分類"部分,這部分的數據是取數據庫的,我讓它隔一段時間變一次或者有更改才變。

還有文章的內容頁,這部分是用markdown寫的,最終要轉成有格式的html,如果先轉好放到redis就能提高性能了。
還有文章詳情頁的"相關推薦"和"標簽“部分也是需要放到cache中的。

#### 6. html片斷cache
我們從數據庫獲取的數據最終還是要和html標簽結合在一起,組裝之后再給瀏覽器客戶端,而這種就叫html片斷,可以存放以string的方式存放在redis中。
完結。