golang資料快取設計與原始碼實現
1. 實際需求
資料快取是指將一些資料儲存到快取中,以調高提高系統讀效能。使用時再從快取中取回。 它也是更高階快取特性的基礎,對於讀多寫少的應用場景,我們經常使用快取來進行優化。
1.1 應用場景
例如對於使用者的餘額資訊表account(uid, money),業務上的需求是:
(1)查詢使用者的餘額,SELECT money FROM account WHERE uid=XXX,佔99%的請求
(2)更改使用者餘額,UPDATE account SET money=XXX WHERE uid=XXX,佔1%的請求
由於大部分的請求是查詢,我們在快取中建立uid到money的鍵值對,能夠極大降低資料庫的壓力。
讀操作流程
有了資料庫和快取兩個地方存放資料之後(uid->money),每當需要讀取相關資料時(money),操作流程一般是這樣的:
(1)讀取快取中是否有相關資料,uid->money
(2)如果快取中有相關資料money,則返回【這就是所謂的資料命中“hit”】
(3)如果快取中沒有相關資料money,則從資料庫讀取相關資料money【這就是所謂的資料未命中“miss”】,放入快取中uid->money,再返回
快取的命中率 = 命中快取請求個數/總快取訪問請求個數 = hit/(hit+miss)
上面舉例的餘額場景,99%的讀,1%的寫,這個快取的命中率是非常高的,會在95%以上。
2. 實現分析
當資料money發生變化的時候:
(1)是更新快取中的資料,還是淘汰快取中的資料呢?
(2)是先操縱資料庫中的資料再操縱快取中的資料,還是先操縱快取中的資料再操縱資料庫中的資料呢?
(3)快取與資料庫的操作,在架構上是否有優化的空間呢?
2.1 更新快取 VS 淘汰快取
更新快取:資料不但寫入資料庫,還會寫入快取
淘汰快取:資料只會寫入資料庫,同時刪除快取中原有資料
更新快取的優點:快取不會增加一次miss,命中率高
淘汰快取的優點:資料庫資料更新時,直接刪除快取資料,下次訪問直接去用新資料
2.2 快取實現需求
需求:1 一個支援讀寫,並且寫入支援過期的快取設計,演算法需求儘可能簡單避免反覆的查詢。 所以提前設計一個LRU cache作為我們的基礎快取池。
LRU(Least recently used,最近最少使用)演算法根據資料的歷史訪問記錄來進行淘汰資料,如果資料最近被訪問過,那麼將來被訪問的機率也更高。依據此原理,可以設計微服務的二級快取結構,用於儲存經常訪問的熱資料,實現避免頻繁訪問後端資料庫的相同資料邏輯。當使用該快取當做二級快取時,需要保證快取中資料和後端資料的一致性。
詳細設計理念和程式碼可以參考:LRU快取設計
需求:2 能夠實現快取的增刪改查介面,並且介面需要支援快取裡沒資料時自動從資料庫獲取資料更新到快取然後返回給使用者
需求:3 支援使用者自定義的資料庫資料獲取方式,以使程式碼能夠使用不同的後端資料庫需求
需求:4 能夠支援大併發的安全讀寫操作,效能瓶頸是底層實現的LRU快取+上層的快取命中率
3 Craw快取設計與實現
craw實現了上述需求的資料二級快取,使用K-V結構儲存,用於快取網路訪問後的資料以便於二次訪問,可用於redis,mysql等資料的二級快取:
3.1 基本使用
import (
"github.com/wonderivan/craw"
)
// 使用者需要實現自定義的CrawInterface包含的四個方法才可以進行craw初始化
type benchCraw struct {
}
func (this *benchCraw) Init() error {
// 建立快取時,在建立完成後會執行使用者自定義的初始化函式,如果不需要初始化其他項,可以直接返回nil
return nil
}
func (this *benchCraw) CustomGet(key string) (data interface{}, expired time.Duration, err error) {
// 當呼叫craw獲取遠端資料時,內部會呼叫該方式實現,使用者需要指定快取資料過期時間,0為馬上過期
return key, -1, nil
}
func (this *benchCraw) CustomSet(key string, data interface{}) error {
// 當呼叫craw設定遠端資料時,內部會呼叫該方式實現,不需要設定,可以直接返回nil
return nil
}
func (this *benchCraw) Destroy() {
// 銷燬快取時,會執行使用者自定義的銷燬方法,如果不需要銷燬其他項,該方法可以為空
}
// 建立快取,使用預設配置
craw := NewCraw("mytest1", new(benchCraw))
// 使用完後銷燬快取
defer craw.Destroy()
// 設定快取資料 key:"name",value:"Lily", 過期時間-1,不過期
craw.SetCraw("name","Lily", -1)
// 獲取快取資料
craw.GetData("name")
// 獲取快取資料命中率
craw.HitRate()
3.2 Craw支援方法列表
// 建立一個craw
// 引數 crawName:快取名稱,config快取配置, dispose快取資料處理方法
func NewCraw(crawName string, dispose CrawInterface, config ...string) *Craw
// config格式
{
"low": 858993459, // 快取壓縮最低閾值限制,預設為800 MB
"high": 1073741824, // 快取觸發清理最高閾值,預設為1 GB,當快取資料大於1G時,開始清理,詳見LruCache說明
"interval": 3600 // 快取有效資料檢查間隔,預設為1天
}
// 銷燬craw
func (dc *Craw) Destroy()
// 獲取快取資料,如果不存在則從遠端獲取資料並更新到craw然後返回
//
// 引數 key:要查詢的craw資料的key
// 返回值 interface{}:找到的資料 error:成功為nil
func (dc *Craw) GetData(key string) (interface{}, error)
// 強制從遠端獲取資料,並更新到craw
//
// 引數 key:要查詢的遠端資料的key
// 返回值 interface{}:更新到craw的資料
// 返回值 成功為nil
func (dc *Craw) UpdateData(key string) (data interface{}, err error)
// 刪除craw快取資料
//
// 引數 key:要查詢的遠端資料的key
// 引數可選 delay:延遲刪除資料的時間 單位(s)
// 返回值 成功為nil
func (dc *Craw) DeleteData(key string, delay ...time.Duration) (err error)
// 儲存資料到遠端,並刪除craw中已有的快取值
//
// 引數 key:要儲存到遠端的資料的key data:要儲存到遠端的資料
// 返回值 error:成功為nil
func (dc *Craw) SetData(key string, data interface{}) (err error)
// 清空craw的所有資料
func (dc *Craw) ClearAll() error
// 清除craw中所有包含字首prefix的key的資料
func (dc *Craw) ClearPrefixKeys(Prefix string) error
// 獲取當前craw快取命中率
//
// 返回值 float64:計算的結果,XX.XXXXX%
func (dc *Craw) HitRate()
// 獲取當前craw快取命中率並重置命中率為0
//
// 返回值 float64:計算的結果,XX.XXXXX%
func (dc *Craw) ResetHitRate() float64
// 設定craw快取資料,不更新遠端
//
// 引數 key:要儲存的資料的key data:要儲存的資料,expired要儲存的資料的過期時間,<0不過期
// 返回值 error:成功為nil
func (dc *Craw) SetCraw(key string, data interface{}, expired time.Duration) error
// 查詢craw中指定的key是否存在
func (dc *Craw) IsExist(key string) (bool, error)
3.3 效能測試
3.3.1 效能圖示
針對當前設計的二級快取進行精簡的K-V讀寫壓力測試 測試分為1W,5W,10W,50W,100W,200W, 在不觸發GC時,分別設定命中率為0%,10%,30%,50%,70%,100%進行讀寫測試, 然後統計總用時,每條讀寫耗時,每秒讀寫條數
3.3.2 實測資料
測試量 | 讀取命中率 | 總用時 | 每條讀取耗時 | 每秒讀取條數 |
---|---|---|---|---|
10000 | 0.00% | 8.681537ms | 868.15ns/op | 1151869.77op |
10000 | 10.00% | 6.025206ms | 602.52ns/op | 1659694.29op |
10000 | 30.00% | 5.374784ms | 537.48ns/op | 1860539.88op |
10000 | 50.00% | 3.643583ms | 364.36ns/op | 2744551.17op |
10000 | 70.00% | 2.648619ms | 264.86ns/op | 3775552.47op |
10000 | 100.00% | 1.210079ms | 121.01ns/op | 8263923.26op |
50000 | 0.00% | 45.364929ms | 907.30ns/op | 1102173.00op |
50000 | 10.00% | 38.790983ms | 775.82ns/op | 1288959.34op |
50000 | 30.00% | 29.191049ms | 583.82ns/op | 1712853.83op |
50000 | 50.00% | 24.245481ms | 484.91ns/op | 2062239.97op |
50000 | 70.00% | 13.359667ms | 267.19ns/op | 3742608.26op |
50000 | 100.00% | 6.47295ms | 129.46ns/op | 7724453.30op |
100000 | 0.00% | 96.2213ms | 962.21ns/op | 1039270.93op |
100000 | 10.00% | 81.877599ms | 818.78ns/op | 1221335.28op |
100000 | 30.00% | 68.601832ms | 686.02ns/op | 1457687.02op |
100000 | 50.00% | 52.831959ms | 528.32ns/op | 1892793.72op |
100000 | 70.00% | 28.859204ms | 288.59ns/op | 3465099.04op |
100000 | 100.00% | 14.512613ms | 145.13ns/op | 6890557.89op |
500000 | 0.00% | 580.435456ms | 1160.87ns/op | 861422.22op |
500000 | 10.00% | 550.468097ms | 1100.94ns/op | 908317.85op |
500000 | 30.00% | 419.168104ms | 838.34ns/op | 1192838.85op |
500000 | 50.00% | 312.806598ms | 625.61ns/op | 1598431.76op |
500000 | 70.00% | 259.572573ms | 519.15ns/op | 1926243.57op |
500000 | 100.00% | 89.384522ms | 178.77ns/op | 5593809.63op |
1000000 | 0.00% | 1.163256084s | 1163.26ns/op | 859655.94op |
1000000 | 10.00% | 1.070822941s | 1070.82ns/op | 933861.20op |
1000000 | 30.00% | 851.79008ms | 851.79ns/op | 1173998.18op |
1000000 | 50.00% | 647.7622ms | 647.76ns/op | 1543776.40op |
1000000 | 70.00% | 578.194375ms | 578.19ns/op | 1729522.19op |
1000000 | 100.00% | 195.846202ms | 195.85ns/op | 5106047.45op |
2000000 | 0.00% | 2.290312946s | 1145.16ns/op | 873243.11op |
2000000 | 10.00% | 2.440874727s | 1220.44ns/op | 819378.39op |
2000000 | 30.00% | 1.938333138s | 969.17ns/op | 1031814.38op |
2000000 | 50.00% | 1.50874304s | 754.37ns/op | 1325606.78op |
2000000 | 70.00% | 1.248193013s | 624.10ns/op | 1602316.29op |
2000000 | 100.00% | 424.174077ms | 212.09ns/op | 4715045.33op |
測試量 | 寫入命中率 | 總用時 | 每條寫入耗時 | 每秒寫入條數 |
---|---|---|---|---|
10000 | 0.00% | 2.84051ms | 284.05ns/op | 3520494.56op |
10000 | 10.00% | 2.72975ms | 272.98ns/op | 3663339.13op |
10000 | 30.00% | 2.409758ms | 240.98ns/op | 4149794.29op |
10000 | 50.00% | 2.157198ms | 215.72ns/op | 4635643.09op |
10000 | 70.00% | 1.676161ms | 167.62ns/op | 5966014.00op |
10000 | 100.00% | 1.272334ms | 127.23ns/op | 7859571.46op |
50000 | 0.00% | 19.780616ms | 395.61ns/op | 2527727.14op |
50000 | 10.00% | 21.111112ms | 422.22ns/op | 2368420.95op |
50000 | 30.00% | 18.702363ms | 374.05ns/op | 2673458.96op |
50000 | 50.00% | 15.233516ms | 304.67ns/op | 3282236.35op |
50000 | 70.00% | 10.959622ms | 219.19ns/op | 4562201.14op |
50000 | 100.00% | 8.132663ms | 162.65ns/op | 6148047.69op |
100000 | 0.00% | 40.368559ms | 403.69ns/op | 2477175.37op |
100000 | 10.00% | 41.886854ms | 418.87ns/op | 2387383.88op |
100000 | 30.00% | 39.782839ms | 397.83ns/op | 2513646.65op |
100000 | 50.00% | 39.339891ms | 393.40ns/op | 2541949.09op |
100000 | 70.00% | 36.249307ms | 362.49ns/op | 2758673.43op |
100000 | 100.00% | 16.909974ms | 169.10ns/op | 5913669.65op |
500000 | 0.00% | 290.534367ms | 581.07ns/op | 1720966.80op |
500000 | 10.00% | 294.119801ms | 588.24ns/op | 1699987.55op |
500000 | 30.00% | 301.406549ms | 602.81ns/op | 1658888.97op |
500000 | 50.00% | 240.751022ms | 481.50ns/op | 2076834.38op |
500000 | 70.00% | 232.707867ms | 465.42ns/op | 2148616.66op |
500000 | 100.00% | 103.425529ms | 206.85ns/op | 4834396.35op |
1000000 | 0.00% | 751.04895ms | 751.05ns/op | 1331471.14op |
1000000 | 10.00% | 763.607503ms | 763.61ns/op | 1309573.30op |
1000000 | 30.00% | 706.018802ms | 706.02ns/op | 1416392.87op |
1000000 | 50.00% | 466.665309ms | 466.67ns/op | 2142863.38op |
1000000 | 70.00% | 494.240314ms | 494.24ns/op | 2023307.23op |
1000000 | 100.00% | 215.737465ms | 215.74ns/op | 4635263.51op |
2000000 | 0.00% | 1.378425505s | 689.21ns/op | 1450930.78op |
2000000 | 10.00% | 1.321529037s | 660.76ns/op | 1513398.45op |
2000000 | 30.00% | 1.314644183s | 657.32ns/op | 1521324.19op |
2000000 | 50.00% | 882.820403ms | 441.41ns/op | 2265466.45op |
2000000 | 70.00% | 1.010007853s | 505.00ns/op | 1980182.62op |
2000000 | 100.00% | 468.810806ms | 234.41ns/op | 4266113.27op |