會有快取的需求是來自於使用者,對相同資料目標進行大量讀取,為了降低 RMDBS 連線數量與 RMDBS 的實體資源,所產生出來的架構策略。今天我們要來實作 cache,並且分別使用本地記憶體與共用noSQL兩種方式,進行比較其效果與其優劣。
Docker run redis
> docker pull redis
> docker run --name cache -p 6379:6379 -d redis
以下兩個範例都是先取 cache 內資料,若無法取得則向 MySQL 重取資料
請下載完整範例
//嘗試取快取資料
res, err := redis.String(c.Do("GET", ad))
if err != nil && !strings.Contains(err.Error(), "nil returned") {
fmt.Println(err.Error())
return
}
//若無快取更新快取並返回最新值
if res == "" || err != nil {
res = mockMySQL(ad)
if _, err := c.Do("SET", ad, res); err != nil {
fmt.Println("fail", err.Error())
return
}
}
Redis client 用的套件是 redigo@v1.8.2,筆者目前測試這個版本的 WATCH 與 EXEC,似乎無法正確對異動判斷,但不影響本篇範例,大家如果有用到需注意一下。
func localCache() {
adList.lock.RLock()
if res, ok := adList.cache[ad]; ok {
fmt.Println("success", res)
adList.lock.RUnlock()
return
}
adList.lock.RUnlock()
adList.lock.Lock()
res := mockMySQL(ad)
adList.cache[ad] = res
adList.lock.Unlock()
fmt.Println("success", res)
}
在 local memory 操作一個共用的 map struct,需注意 concurrent read/write map 的問題,必須額外加上 mutex lock 處理。
就結果來說,兩個範例都是在取不到快取後,向 MySQL 取資料,最後得到從 MySQL 取回的新資料,並新增快取。但如果我們現在的 server 不是一台是多台的話,使用起來的效果將會有所不同,讓我們列出其效果並比較一下。
看得出來因為 local cache 無法共用快取的情況下,勢必會因為 AP server 數量,而增加對 RMDBS 的讀取次數,但也不是說 local cache 就沒有實作的價值,考量到實際業務需求上存取的頻率,RMDBS 資料如果較少異動時,我們可以拉長 local cache ttl 來減少重取的次數。
使用 Redis 雖然會付出些許的維護成本,但他在使用上不需像 local cache,常需在意 map lock 的問題,僅需注意連線是否正確釋放或複用,便可達成共用快取的效果。兩者各有適合應用的場景與條件,我們需要依照實際業務需求,來決定何者適合甚至是混用。