本篇文章給大家分享的是有關(guān)如何提高進(jìn)程內(nèi)緩存的并發(fā),小編覺(jué)得挺實(shí)用的,因此分享給大家學(xué)習(xí),希望大家閱讀完這篇文章后可以有所收獲,話不多說(shuō),跟著小編一起來(lái)看看吧。
創(chuàng)新互聯(lián)-成都網(wǎng)站建設(shè)公司,專注成都網(wǎng)站制作、網(wǎng)站設(shè)計(jì)、網(wǎng)站營(yíng)銷(xiāo)推廣,申請(qǐng)域名,雅安服務(wù)器托管,成都網(wǎng)站托管有關(guān)企業(yè)網(wǎng)站制作方案、改版、費(fèi)用等問(wèn)題,請(qǐng)聯(lián)系創(chuàng)新互聯(lián)。
緩存,設(shè)計(jì)的初衷是為了減少繁重的IO操作,增加系統(tǒng)并發(fā)能力。不管是 CPU多級(jí)緩存
,page cache
,還是我們業(yè)務(wù)中熟悉的 redis
緩存,本質(zhì)都是將有限的熱點(diǎn)數(shù)據(jù)存儲(chǔ)在一個(gè)存取更快的存儲(chǔ)介質(zhì)中。
計(jì)算機(jī)本身的緩存設(shè)計(jì)就是 CPU 采取多級(jí)緩存。那對(duì)我們服務(wù)來(lái)說(shuō),我們是不是也可以采用這種多級(jí)緩存的方式來(lái)組織我們的緩存數(shù)據(jù)。同時(shí) redis
的存取都會(huì)經(jīng)過(guò)網(wǎng)絡(luò)IO,那我們能不能把熱點(diǎn)數(shù)據(jù)直接存在本進(jìn)程內(nèi),由進(jìn)程自己緩存一份最近最熱的這批數(shù)據(jù)呢?
這就引出了我們今天探討的:local cache
,本地緩存,也叫進(jìn)程緩存。
作為一個(gè)進(jìn)程存儲(chǔ)設(shè)計(jì),當(dāng)然是 crud
都有的:
我們先初始化 local cache
// 先初始化 local cache cache, err = collection.NewCache(time.Minute, collection.WithLimit(10)) if err != nil { log.Fatal(err) }
其中參數(shù)的含義:
expire
:key統(tǒng)一的過(guò)期時(shí)間
CacheOption
:cache設(shè)置。比如key的上限設(shè)置等
基礎(chǔ)操作緩存
// 1. add/update 增加/修改都是該API cache.Set("first", "first element") // 2. get 獲取key下的value value, ok := cache.Get("first") // 3. del 刪除一個(gè)key cache.Del("first")
Set(key, value)
設(shè)置緩存
value, ok := Get(key)
讀取緩存
Del(key)
刪除緩存
高級(jí)操作
cache.Take("first", func() (interface{}, error) { // 模擬邏輯寫(xiě)入local cache time.Sleep(time.Millisecond * 100) return "first element", nil })
前面的 Set(key, value)
是單純將 <key, value>
加入緩存;Take(key, setFunc)
則是在 key 對(duì)于的 value 不存在時(shí),執(zhí)行傳入的 fetch
方法,將具體讀取邏輯交給開(kāi)發(fā)者實(shí)現(xiàn),并自動(dòng)將結(jié)果放到緩存里。
到這里核心使用代碼基本就講完了,其實(shí)看起來(lái)還是挺簡(jiǎn)單的。也可以到 https://github.com/tal-tech/go-zero/blob/master/core/collection/cache_test.go 去看 test 中的使用。
首先緩存實(shí)質(zhì)是一個(gè)存儲(chǔ)有限熱點(diǎn)數(shù)據(jù)的介質(zhì),面臨以下的這些問(wèn)題:
有限容量
熱點(diǎn)數(shù)據(jù)統(tǒng)計(jì)
多線程存取
下面來(lái)說(shuō)說(shuō)這3個(gè)方面我們的設(shè)計(jì)實(shí)踐。
有限就意味著滿了要淘汰,這個(gè)就涉及到淘汰策略。cache
中使用的是:LRU
(最近最少使用)。
那淘汰怎么發(fā)生呢? 有幾個(gè)選擇:
開(kāi)一個(gè)定時(shí)器,不斷循環(huán)所有key,等到了預(yù)設(shè)過(guò)期時(shí)間,執(zhí)行回調(diào)函數(shù)(這里是刪除map中過(guò)的key)
惰性刪除。訪問(wèn)時(shí)判斷該鍵是否被刪除。缺點(diǎn)是:如果未訪問(wèn)的話,會(huì)加重空間浪費(fèi)。
而 cache
中采取的是第一種 主動(dòng)刪除。但是,主動(dòng)刪除中遇到最大的問(wèn)題是:
不斷循環(huán),空消耗CPU資源,即使在額外的協(xié)程中這么做,也是沒(méi)有必要的。
cache
中采取的是時(shí)間輪記錄額外過(guò)期通知,等過(guò)期 channel
中有通知時(shí),然后觸發(fā)刪除回調(diào)。
> 有關(guān) 時(shí)間輪 更多的設(shè)計(jì)文章:https://go-zero.dev/cn/timing-wheel.html
對(duì)于緩存來(lái)說(shuō),我們需要知道這個(gè)緩存在使用額外空間和代碼的情況下是否有價(jià)值,以及我們想知道需不需要進(jìn)一步優(yōu)化過(guò)期時(shí)間或者緩存大小,所有這些我們就很依賴統(tǒng)計(jì)能力了, go-zero
中 sqlc
和 mongoc
也同樣提供了統(tǒng)計(jì)能力。所以我們?cè)?cache
中也加入的緩存,為開(kāi)發(fā)者提供本地緩存監(jiān)控的特性,在接入 ELK
時(shí)開(kāi)發(fā)者可以更直觀的監(jiān)測(cè)到緩存的分布情況。
而設(shè)計(jì)其實(shí)也很簡(jiǎn)單,就是:Get() 命中,就在統(tǒng)計(jì) count 上加1即可。
func (c *Cache) Get(key string) (interface{}, bool) { value, ok := c.doGet(key) if ok { // 命中hit+1 c.stats.IncrementHit() } else { // 未命中miss+1 c.stats.IncrementMiss() } return value, ok }
當(dāng)多個(gè)協(xié)程并發(fā)存取的時(shí)候,對(duì)于緩存來(lái)說(shuō),涉及的問(wèn)題以下幾個(gè):
寫(xiě)-寫(xiě)沖突
LRU
中元素的移動(dòng)過(guò)程沖突
并發(fā)執(zhí)行寫(xiě)入緩存時(shí),造成流量沖擊或者無(wú)效流量
這種情況下,寫(xiě)沖突好解決,最簡(jiǎn)單的方法就是 加鎖:
// Set(key, value) func (c *Cache) Set(key string, value interface{}) { // 加鎖,然后將 <key, value> 作為鍵值對(duì)寫(xiě)入 cache 中的 map c.lock.Lock() _, ok := c.data[key] c.data[key] = value // lru add key c.lruCache.add(key) c.lock.Unlock() ... } // 還有一個(gè)在操作 LRU 的地方時(shí):Get() func (c *Cache) doGet(key string) (interface{}, bool) { c.lock.Lock() defer c.lock.Unlock() // 當(dāng)key存在時(shí),則調(diào)整 LRU item 中的位置,這個(gè)過(guò)程也是加鎖的 value, ok := c.data[key] if ok { c.lruCache.add(key) } return value, ok }
而并發(fā)執(zhí)行寫(xiě)入邏輯,這個(gè)邏輯主要是開(kāi)發(fā)者自己傳入的。而這個(gè)過(guò)程:
func (c *Cache) Take(key string, fetch func() (interface{}, error)) (interface{}, error) { // 1. 先獲取 doGet() 中的值 if val, ok := c.doGet(key); ok { c.stats.IncrementHit() return val, nil } var fresh bool // 2. 多協(xié)程中通過(guò) sharedCalls 去獲取,一個(gè)協(xié)程獲取多個(gè)協(xié)程共享結(jié)果 val, err := c.barrier.Do(key, func() (interface{}, error) { // double check,防止多次讀取 if val, ok := c.doGet(key); ok { return val, nil } ... // 重點(diǎn)是執(zhí)行了傳入的緩存設(shè)置函數(shù) val, err := fetch() ... c.Set(key, val) }) if err != nil { return nil, err } ... return val, nil }
而 sharedCalls
通過(guò)共享返回結(jié)果,節(jié)省了多次執(zhí)行函數(shù),減少了協(xié)程競(jìng)爭(zhēng)。
以上就是如何提高進(jìn)程內(nèi)緩存的并發(fā),小編相信有部分知識(shí)點(diǎn)可能是我們?nèi)粘9ぷ鲿?huì)見(jiàn)到或用到的。希望你能通過(guò)這篇文章學(xué)到更多知識(shí)。更多詳情敬請(qǐng)關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。
當(dāng)前文章:如何提高進(jìn)程內(nèi)緩存的并發(fā)
標(biāo)題網(wǎng)址:http://sd-ha.com/article12/ghdpdc.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供品牌網(wǎng)站制作、網(wǎng)站制作、自適應(yīng)網(wǎng)站、ChatGPT、定制開(kāi)發(fā)、營(yíng)銷(xiāo)型網(wǎng)站建設(shè)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)