Copy go-cache 學習快取的前置知識
1.什麼是讀鎖,什麼是寫鎖,sync.Mutex 和 sync.RWMutex 的區別
2.go runtime 協程
3. Golang time 和 runtime 包
寫 go-cache 的順序
1.確定資料需要儲存的內容和其過期時間
2.將cache封裝為一個結構體
3.New
4.編寫主方法:
新增,修改,刪除,查詢,過期快取回收
5.測試
6.驗收
開始編寫
1.確定快取結構體
type Item struct {
// 能夠儲存的任何種類的資料
Object interface{}
// 原始碼中過期時間,為什麼是 int64 (因為 time.Duration 的代指就是 int64 )
Expiration int64
}
// 順便設定常量
const (
// 設定永久過期時間為 -1
NoExpiration time.Duration = -1
// 設定預設過期時間為 0
DefaultExpiration time.Duration = 0
)
2.將 cache
封裝為一個結構體
// 該結構體方便於外部呼叫
type Cache struct {
*cache
}
// 2.cache 中儲存的內容:
// 預設過期時間,儲存的 item, 讀寫鎖, janitor
type cache struct {
defaultExpiration time.Duration // 預設過期時間
items map[string]Item // 內容
mu sync.RWMutex // 讀寫鎖
onEvicted func(string, interface{}) // 對刪除資料二次操作
janitor *janitor // gc 垃圾回收
}
3.New
go-cache 中包含 1 個對外的New 和兩個內部建立結構體
// newCache 該方法真正返回 cache 結構體
// de 設定過期時間 ,m 儲存的資料
func newCache(de time.Duration, m map[string]Item) *cache {
// 如果過期時間是0,建立永久時間
if de == 0 {
de = -1
}
return &cache{
defaultExpiration: de,
items: m,
}
}
// 該方法用來執行快取回收
func newCacheWithJanitor(de, ci time.Duration, m map[string]Item) *Cache {
c := newCache(de, m)
C := &Cache{c}
if ci > 0 {
// 執行垃圾回收方法,之後會寫
runJanitor(c,ci)
// 設定垃圾回收,stopJanitor 也是之後寫的停止執行快取回收
runtime.SetFinalizer(C,stopJanitor)
}
return C
}
// New
// defaultExpiration 設定所有快取的預設過期時間
// cleanInterval 設定快取清理間隔時間
func New(defaultExpiration, cleanInterval time.Duration) *Cache {
items := make(map[string]Item)
return newCacheWithJanitor(defaultExpiration, cleanInterval, items)
}
4.編寫主方法
1.獲取
// Get 獲取 該資料為,開啟讀鎖是為了保證讀取資料的原子性
func (c *cache) Get(k string) (interface{}, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
item, found := c.items[k]
if !found {
return nil, false
}
// 如果設定了過期時間,判斷是否過期
if item.Expiration > 0 {
if time.Now().UnixNano() > item.Expiration {
return nil, false
}
}
return item.Object, true
}
// get 不需要讀寫鎖的原因是因為呼叫該方法的前置方法已經開啟了鎖
func (c *cache) get(k string) (interface{}, bool) {
item, found := c.items[k]
if !found {
return nil, false
}
if item.Expiration > 0 { //設定了過期時間,但是過期了
if time.Now().UnixNano() > item.Expiration {
return nil, false
}
}
return item.Object, true
}
2.新增
// Set (新增) 該方法因為在外部層面需要保證資料的一致性,防止多個操作
// 該方法偉倫是否有值,都會重新設定
// k - key , x - value , d - 過期時間
func (c cache) Set(k string, x interface{}, d time.Duration) {
// 最後需要存入的資料內容
var e int64
// 如果為 0,則設定為之前設定的預設過期時間
if d == DefaultExpiration {
d = c.defaultExpiration
}
// 設定過期時間從當前時間開始
if d > 0 {
e = time.Now().Add(d).UnixNano()
}
// 新增寫鎖,保證資料的原子性,並寫入內容
c.mu.Lock()
defer c.mu.Unlock()
c.items[k] = Item{
Object: x, //儲存內容
Expiration: e, //過期時間
}
}
// set 與 Set 相同,因為作用在 Add 中 已經加了鎖,所以不需要加鎖
func (c *cache) set(k string, x interface{}, d time.Duration) {
var e int64
if d == DefaultExpiration {
d = c.defaultExpiration
}
if d > 0 {
e = time.Now().Add(d).UnixNano()
}
c.items[k] = Item{
Object: x,
Expiration: e,
}
}
// Add 如果資料存在則不會進行操作
func (c cache) Add(k string, x interface{}, d time.Duration) error {
c.mu.Lock()
defer c.mu.Unlock()
if _, found := c.get(k); found { // 已經儲存了資料
return fmt.Errorf("Item %s already exists", k)
}
c.set(k, x, d)
return nil
}
3.修改
// Replace 修改
func (c *cache) Replace(k string, x interface{}, d time.Duration) error {
c.mu.Lock()
defer c.mu.Unlock()
if _, found := c.get(k); !found {
return fmt.Errorf("Item %s doesn't exist", k)
}
// 否則進行設定
c.set(k, x, d)
return nil
}
4.刪除
// Delete 刪除快取, evicted 用來儲存驅逐資料
func (c *cache) Delete(k string) {
c.mu.Lock()
defer c.mu.Unlock()
v, evicted := c.delete(k)
// 是否是需要做二次操作的資料
if evicted {
c.onEvicted(k, v)
}
}
// delete 刪除快取
func (c *cache) delete(k string) (interface{}, bool) {
if c.onEvicted != nil {
if v, found := c.items[k]; found {
delete(c.items, k)
return v.Object, true
}
}
delete(c.items, k)
return nil, false
}
5.過期快取回收
type janitor struct {
Interval time.Duration
stop chan bool
}
// Run
// select 作為阻塞程式
func (j *janitor) Run(c *cache) {
// 設定的每過一段時間返回一個 channel 通道欄位
ticker := time.NewTicker(j.Interval)
for {
select {
// 如果返回的是時間,則執行刪除過期快取
case <-ticker.C:
c.DeleteExpired()
// 如果j中確定停止回收
case <-j.stop:
ticker.Stop()
return
go j.Run(c)
}
// 對資料進行二次操作
type keyAndValue struct {
key string
value interface{}
}
// DeleteExpired() 刪除過期快取
func (c *cache) DeleteExpired() {
var evictedItems []keyAndValue
now := time.Now().UnixNano()
c.mu.Lock()
defer c.mu.Unlock()
for k, v := range c.items {
if v.Expiration > 0 && now > v.Expiration {
ov, eveicted := c.delete(k)
// 將需要進行二次操作的資料存入evictedItems 中
if eveicted {
evictedItems = append(evictedItems, keyAndValue{k, ov})
}
}
}
// 將需要二次操作的資料儲存
for _, v := range evictedItems {
c.onEvicted(v.key, v.value)
}
}
// 儲存二次操作的資料
func (c *cache) OnEvicted(f func(string, interface{})) {
c.mu.Lock()
defer c.mu.Unlock()
c.onEvicted = f
}
6.幾個常用方法
// Items 獲取所有資料
func (c *cache) Items() map[string]Item {
c.mu.RLock()
defer c.mu.RUnlock()
m := make(map[string]Item, len(c.items))
now := time.Now().UnixNano()
for k, v := range c.items {
if v.Expiration > 0 && now > v.Expiration {
continue
}
m[k] =v
}
return m
}
// 獲取資料總數
func (c cache) ItemCount() int {
c.mu.RLock()
defer c.mu.RUnlock()
return len(c.items)
}
// Flush 刪除所有
func (c cache) Flush() {
c.mu.Lock()
defer c.mu.Unlock()
c.items = map[string]Item{}
}
5.單元測試
結語
go-cache 其實常用在二次快取,同時go-cahce 不需要新增分散式快取,如果新增了為什麼不直接用redis的分散式快取
我們其實並不需要經常重複造輪子,重複造輪子的目的是為了瞭解成熟框架的執行原理,如果有能力,可以同時進行優化
同時也謝謝能看到最後
本作品採用《CC 協議》,轉載必須註明作者和本文連結