level cache是一個golang編寫支持分片存儲的多級的緩存庫。整體設計上在保障性能夠用的前提下盡可能的通過設計方案簡化代碼實現,便於維護和二次開發。該庫能夠管理多種速度價格不同的設備進行對象分片的功能,實現性能和價格的平衡。level cache以分片為粒度進行數據的存儲和熱點管理,方便類似視頻的數據進行分段緩存。
項目地址
github: https://github.com/GhostZCH/levelcache
文檔: http://www.zhaoch.top/項目/level_cache.html
特色功能
- 支持多級緩存,自動將熱點分片移動到高級別的設備中,並優先讀取
- 任何可以掛載為linux目錄的設備都可以被使用,例如內存可以使用/dev/shm/作為緩存路徑
- 以分片為粒度存儲,方便只緩存部分數據后將對象的一部分放到更高級別的設備中
- 調用者可以實現Auxiliary接口來存儲附加對象信息,這些信息能夠方便調用者進行更多的判斷和統計,同時通過Auxiliary可以實現自定義批量刪除,諸如按照類型,正則表達式,分組,前后綴的刪除都是可以實現的
- 塊文件在初始化時通過mmap加載,合理使用可減少內存拷貝
接口
Cache操作
NewCache
初始化一個新的緩存對象
Close()
// devices: 設備配置,級別依次升高,基本越高的設備速度應該越快 func NewCache(conf Config, devices []DevConf) *Cache
Close()
關閉,保存文件,關閉句柄
Dump()
保存元數據,建議周期性的調用,防止程序意外退出造成較大損失
示例:
conf := cache.Config{ MetaDir: "/tmp/cache/meta/", ActionParallel: 4, AuxFactory: NewHttpAux} devices := [3]cache.DevConf{ cache.DevConf{ Name: "hdd", Dir: "/tmp/cache/hdd/", Capacity: 1000 * 1024 * 1024}, cache.DevConf{ Name: "ssd", Dir: "/tmp/cache/ssd/", Capacity: 100 * 1024 * 1024}, cache.DevConf{ Name: "mem", Dir: "/tmp/cache/mem/", //實際可以使用而 /dev/shm/xxxx/ Capacity: 10 * 1024 * 1024}, } c := cache.NewCache(conf, devices[:]) defer c.Close() go func() { time.Sleep(time.Minute) c.Dump() }()
對象操作
Get
// 獲得緩存對象某一部分的數據,end = -1 時表示獲取到數據文件末尾,每次get后相應的數據分片可能會被調整到速度更快的緩存設備中 // dataList: 數據分片的列表 // hitDevs: 命中的 // missSegments: 示缺失的數據分片,每個元素[2]int的內容是對應分片的start與end func (c *Cache) Get(key Hash, start int, end int) (dataList [][]byte, hitDevs []string, missSegments [][2]int)
AddItem
// 增加一個緩存對象,只包含基礎信息,不包含緩存數據和分片 func (c *Cache) AddItem(key Hash, expire, size int64, auxData interface{})
AddSegment
// 添加一個分片 func (c *Cache) AddSegment(key Hash, start int, data []byte)
Del
// 刪除一個對象,包含所屬的分片 func (c *Cache) Del(k Hash)
DelBatch
// 批量刪除,根據設置的並行數n, 每次讀鎖定n個桶進行匹配返回key,之后加寫鎖對這些key進行單個刪除 func (c *Cache) DelBatch(m Matcher)
示例
pngkey := md5.Sum([]byte("http://www.test.com/123/456/1.png")) png := []byte("this is 1.png") c.AddItem(pngkey, time.Now().Unix()+3600, int64(len(jpg)), httpAuxData{ fileType: crc32.ChecksumIEEE([]byte("png")), rawKey: []byte("http://www.test.com/123/456/1.png")}) c.AddSegment(pngkey, 0, png) c.Del(pngkey) c.DelBatch(func(aux cache.Auxiliary) []cache.Hash { keys := make([]cache.Hash, 0) for k, v := range aux.(*httpAux).datas { if v.fileType == crc32.ChecksumIEEE([]byte("jpg")) { keys = append(keys, k) } } return keys })
類型定義
// 存儲key type Hash [md5.Size]byte // 批量刪除的回調函數,用於批量刪除時在用戶自定義數據中查找需要刪除的數據,每個桶執行一次,不需要調用者加鎖 type Matcher func(aux Auxiliary) []Hash // 設備初始化配置 type DevConf struct { Name string Dir string Capacity int } // 緩存庫公共配置 type Config struct { MetaDir string // 元數據文件存放的目錄建議放在非易失存儲上 ActionParallel int // 批量刪除和備份元數據等批量耗時操作的並發數,建議設置為cpu數的一半 AuxFactory AuxFactory // 產生附加數據的工廠函數,meta中的每個桶會包含一個自定義附加數據,用於方便刪除 } // 產生附加數據的工廠函數,用作新建緩存的參數 type AuxFactory func(idx int) Auxiliary // 調用者附加數據接口,由調用者根據業務情況設計數據結構實現相關的方法,不需要調用者加鎖 type Auxiliary interface { Add(key Hash, auxItem interface{}) Get(key Hash) interface{} Del(key Hash) Load(path string) // 從指定文件加載內容,NewCache時觸發,每個桶觸發一次,path不同 Dump(path string) // 向指定文件持久化數據, }
示例
詳見example/main.gotype httpAuxData struct { fileType uint32 etagCRC uint32 rawKey []byte } type httpAux struct { datas map[cache.Hash]httpAuxData } func (aux *httpAux) Add(key cache.Hash, auxItem interface{}) { aux.datas[key] = auxItem.(httpAuxData) } func (aux *httpAux) Get(key cache.Hash) interface{} { data, _ := aux.datas[key] return data } func (aux *httpAux) Del(key cache.Hash) { delete(aux.datas, key) } func (aux *httpAux) Load(path string) { return } func (aux *httpAux) Dump(path string) { return } func NewHttpAux(idx int) cache.Auxiliary { return &httpAux{datas: make(map[cache.Hash]httpAuxData)} } func main() { // 初始化存儲 conf := cache.Config{ MetaDir: "/tmp/cache/meta/", ActionParallel: 4, AuxFactory: NewHttpAux} // 大容量存儲在前,快速存儲在后,最低級存儲建議用非易失存儲 devices := [3]cache.DevConf{ cache.DevConf{ Name: "hdd", Dir: "/tmp/cache/hdd/", Capacity: 1000 * 1024 * 1024}, cache.DevConf{ Name: "ssd", Dir: "/tmp/cache/ssd/", Capacity: 100 * 1024 * 1024}, cache.DevConf{ Name: "mem", Dir: "/tmp/cache/mem/", //實際可以使用而 /dev/shm/xxxx/ Capacity: 10 * 1024 * 1024}, } c := cache.NewCache(conf, devices[:]) defer c.Close() go func() { time.Sleep(time.Minute) c.Dump() }() // 添加一個對象 fmt.Println("add jpg") jpgkey := md5.Sum([]byte("http://www.test.com/123/456/1.jpg")) jpg := []byte("this is 1.jpg") c.AddItem(jpgkey, time.Now().Unix()+3600, int64(len(jpg)), httpAuxData{ fileType: crc32.ChecksumIEEE([]byte("jpg")), rawKey: []byte("http://www.test.com/123/456/1.jpg")}) c.AddSegment(jpgkey, 0, jpg) // 熱點數據升級到更快的存儲中 fmt.Println(c.Get(jpgkey, 0, -1)) // hdd fmt.Println(c.Get(jpgkey, 0, -1)) // ssd fmt.Println(c.Get(jpgkey, 0, -1)) // mem // 添加另一個對象 fmt.Println("add png") pngkey := md5.Sum([]byte("http://www.test.com/123/456/1.png")) png := []byte("this is 1.png") c.AddItem(pngkey, time.Now().Unix()+3600, int64(len(jpg)), httpAuxData{ fileType: crc32.ChecksumIEEE([]byte("png")), rawKey: []byte("http://www.test.com/123/456/1.png")}) c.AddSegment(pngkey, 0, png) fmt.Println(c.Get(jpgkey, 0, -1)) // 刪除類型為jpg的文件 fmt.Println("Del jpg") c.DelBatch(func(aux cache.Auxiliary) []cache.Hash { keys := make([]cache.Hash, 0) for k, v := range aux.(*httpAux).datas { if v.fileType == crc32.ChecksumIEEE([]byte("jpg")) { keys = append(keys, k) } } return keys }) // 按照正則刪除文件 fmt.Println("Del regex") r := regexp.MustCompile(`http://www.test.com/123/.*png`) c.DelBatch(func(aux cache.Auxiliary) []cache.Hash { keys := make([]cache.Hash, 0) for k, v := range aux.(*httpAux).datas { if r.Match(v.rawKey) { fmt.Println("match", string(v.rawKey)) keys = append(keys, k) } } return keys }) }
設計
主要對象的結構如下
- Cache包含Meta和一個Device組成的數組
- Meta用於存儲item的基礎信息和Aux信息,Meta被分成256個桶,對象按照key的第一個byte判斷在哪個桶中。每個桶各自獨立,有獨立的鎖,每次只鎖定1/256的數據,減少批量刪除和持久化操作對整個系統的影響
- Device數組按照存儲級別由低到高設置,高級別時更快的設備。每個設備主要包含兩部分內容,store用於管理存儲空間分配和回收。buckets用於管理segment的元數據信息,也采用了和Meta類似的分桶邏輯減少鎖對系統平穩的影響。為了減少代碼量,所有的Device配置都使用linux目錄方式,也就是不論內存、ssd、hdd或者是nfs,都可以復用同一套管理代碼,只要可以被掛載為linux目錄即可
- Auxiliary是一個由使用者實現的借口,方便調用者按照自身的業務邏輯進行批量刪除等操作,每MetaBucket會實例化一個,由庫保證線程安全

device的內部結構如上圖,只以HDD為例畫出一個,所有device具有相同的管理邏輯
- device內主要由segments(處於buckets內)和store組成,
- Store采用FIFO大文件塊隊列的方式進行內容存儲,每個塊存儲若干文件,當空間不夠時,會新增一個文件塊並刪除一個舊塊,新增加的存儲位於文件塊的末尾
for spiders
www.zhaoch.top
https://github.com/GhostZCH/levelcache
http://www.zhaoch.top/項目/level_cache.html