前段時間,校招投了golang崗位,可是沒什麼好的項目往簡歷上寫,因而參考了許多網上資料,作了一個簡單的分佈式緩存項目。
如今閒下來了,打算整理下。html
github項目地址:https://github.com/Jun10ng/Gache
裏面還有我整理的一些面試問題,給顆星吧。java
typora-root-url: ./git
實現一個分佈式緩存,功能有:LRU淘汰策略,http調用,併發緩存,一致性哈希,分佈式節點,防止緩存擊穿github
LRU的數據結構大體以下,上層是一個map
,key是數據對象的key值,而value值則是指向 下層雙向鏈表的節點,在雙向鏈表中,每一個節點存儲的元素是完整的數據對象,包含key值和value。golang
maxSize
具體代碼實現看:https://github.com/Jun10ng/Gache/tree/master/lru面試
定義了三個數據結構redis
Value
是golang中的接口類型,能夠理解爲java中的Object類,是一個能「兜底」全部數據結構的數據類型。數據庫
entry
是一個雙向鏈表存儲的數據結構緩存
Cache
則是lru核心數據結構,包含一個哈希表和一個雙向鏈表數據結構
type Value interface { //返回佔用的內存大小 Len() int } type entry struct { key string value Value } type Cache struct { //容許使用的最大內存 maxBytes int64 //當前已使用的內存 nbytes int64 ll *list.List cache map[string] *list.Element //某條記錄被移除時的回調函數,能夠是nil OnEvicted func(key string, value Value) }
這裏說一下OnEvicted
成員,這是一個函數對象,他的做用是,在緩存中沒有須要的數據對象時,咱們須要去原始數據源獲取,(redis中沒有,就須要去數據庫中獲取),可是數據源不惟一,有時候是數據庫,有時候是磁盤,有時候是表格,他們的獲取方式都不相同,因此OnEvicted
成員傳入的函數,就是自定義的獲取方法。
具體代碼實現:https://github.com/Jun10ng/Gache/blob/master/cache.go
上文實現的LRU數據結構並不支持併發,須要加鎖來實現併發,因此使用sync.Mutex
,在LRU數據結構上封裝,使之實現併發功能。
type cache struct { mu sync.Mutex lru *lru.Cache cacheBytes int64 }
cache並無new方法,由於採用的是延遲初始化 在add方法中,判斷c.lru是否爲nil,若是等於nil再建立 這種方法稱爲延遲初始化,一個對象的延遲初始化意味着該對象的 建立將會延遲至第一次使用該對象時。 這個方法在redis中很常見,由於能必定程度上提升性能
func (c *cache) add(key string, value ByteView){ c.mu.Lock() defer c.mu.Unlock() if c.lru == nil{ c.lru = lru.New(c.cacheBytes,nil) } c.lru.Add(key,value) }
具體代碼實現:https://github.com/Jun10ng/Gache/blob/master/gache.go
本質上是再進行一次封裝
難道一臺機器就只有一個緩存表嗎?你打開redis的可視化工具,能看到redis還有16個池呢,因此咱們要實現多個緩存表。怎麼作?再加一層。試想一下:
//groups 實例集合表 groups = make(map[string]*Group)
咱們要實現的數據結構大體是這樣的,是一個存儲併發cache
的表,這是本項目的核心結構
//這裏的group是實例 type Group struct { name string getter Getter mainCache cache }
具體代碼實現:https://github.com/Jun10ng/Gache/blob/master/http.go
當請求URL具備前綴/_Gache/
時,則認爲該請求爲緩存調用。
約定的請求URL爲:http://XXX.com/_Gache/<groupname>/<key>
groupname
字段爲主體結構中groups
中的某個元素的name
值,由此調用。key
字段爲元素中的元素的key
值,因此最後邏輯爲
groups[groupname][key]
一致性哈希
分佈式節點