1. 程式人生 > >golang資料快取設計與原始碼實現

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