亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Go使用緩存加速外部資源訪問提高性能效率

 更新時間:2023年10月17日 11:27:43   作者:俞凡  
緩存是架構(gòu)設(shè)計中的常用概念,本文基于Go實(shí)現(xiàn)了一個簡單的緩存組件,支持最基本的緩存操作,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

 翻譯自原文: Implementing Cache With Go

概念簡介

緩存是計算機(jī)科學(xué)中的一個重要概念。設(shè)想某個組件需要訪問外部資源,它向外部源請求資源,接收并使用資源,這些步驟都需要花費(fèi)時間。當(dāng)組件再次需要資源時,可以再次請求資源,但這種方式從時間上考慮是比較低效的。相反,組件可以將請求結(jié)果保存在本地某處,然后再次使用,使用本地數(shù)據(jù)總是比請求外部數(shù)據(jù)要快,這一策略就是緩存的基本概念。我們可以在內(nèi)存、CPU緩存和服務(wù)器緩存(如Redis)中找到這些例子。

不同用例

Web服務(wù)中的緩存用于減少數(shù)據(jù)請求的延遲。Web服務(wù)保存第一次查詢的執(zhí)行結(jié)果,然后在需要的時候再次使用,而不用再次訪問數(shù)據(jù)庫。取決于數(shù)據(jù)的特性,緩存有不同情況,可以有相對靜態(tài)的數(shù)據(jù),如統(tǒng)計數(shù)據(jù)、計算結(jié)果,也有可能是經(jīng)常變化的數(shù)據(jù),如評論區(qū)或SNS。

最好的情況是緩存那些很少變化的數(shù)據(jù)。以月度統(tǒng)計數(shù)據(jù)為例,上個月的數(shù)據(jù)將不會變化,如果對它進(jìn)行緩存,可能就不需要查詢數(shù)據(jù)庫獲取上個月的數(shù)據(jù)了。

對于快速變化的數(shù)據(jù),在存在多個服務(wù)器時最好謹(jǐn)慎些。看看上面的設(shè)計,以評論區(qū)服務(wù)為例,考慮如下場景,用戶A發(fā)表了一些評論,然后A決定刪除評論,用戶B嘗試回復(fù)評論。在某些情況下,A和B向不同的服務(wù)器發(fā)送請求。A的刪除操作可能不會傳播到B的服務(wù)器緩存。結(jié)果會是這樣: 緩存A和緩存B有不同的數(shù)據(jù),數(shù)據(jù)庫不知道哪個才是真實(shí)的,數(shù)據(jù)的完整性被破壞了。

在這種情況下,可以使用單一外部緩存(如上圖所示),多個服務(wù)器只訪問統(tǒng)一的緩存。

限制條件

緩存比數(shù)據(jù)庫要快,但在大小上要小得多。這是因?yàn)閿?shù)據(jù)庫將數(shù)據(jù)存儲在驅(qū)動器中,緩存將數(shù)據(jù)存儲在內(nèi)存中。它們遵循各自相同的特征,同樣也有不同的特點(diǎn),如果主機(jī)停止工作,緩存的所有數(shù)據(jù)都會丟失,但數(shù)據(jù)庫的數(shù)據(jù)不會丟失。

由于緩存位于內(nèi)存中,空間是有限的,需要選擇緩存哪些數(shù)據(jù)。在CS課上,我們會聽到LRU(Least Recently Used,最近最少使用),LFU(Least Frequently Used,最不常用)和FIFO(First In First Out,先入先出)這樣的詞,這些是"選擇哪一個"的標(biāo)準(zhǔn),被稱為驅(qū)逐策略(eviction policy)。

設(shè)計&實(shí)現(xiàn)

需求

  • 鍵值存儲(Key-Value Storage): 緩存既要有輸入鍵、輸出值的讀功能,也要有輸入鍵、值的寫功能。這些函數(shù)應(yīng)該在平均O(logN)時間內(nèi)完成,其中N是鍵的數(shù)量。
  • LRU驅(qū)逐策略: 由于緩存空間有限,如果緩存滿了,一些數(shù)據(jù)應(yīng)該被清除,選擇用LRU算法實(shí)現(xiàn)。
  • TTL (Time To Live): 每個鍵值都有生存時間,如果TTL到期,該鍵值應(yīng)該被驅(qū)逐。

API設(shè)計

鍵值存儲的意思是,如果請求鍵,緩存會返回那些存在的鍵的值,類似于hash-map抽象數(shù)據(jù)類型,以提供以下API概念的應(yīng)用程序?yàn)槔?

func Get(key string) (hit bool, value []byte)
func Put(key string, value []byte) (hit bool)
  • Get: 通過鍵讀取值的API。如果所提供的鍵在緩存中存在,則返回等效值。如果不存在,則返回hit=false。對于LRU策略,鍵將被標(biāo)記為最近被使用,從而使該鍵不會被驅(qū)逐。
  • Put: 通過鍵寫入值的API。如果所提供的鍵存在,則value將被替換為新值。如果不存在,將創(chuàng)建新的鍵值存儲。因?yàn)樵摵瘮?shù)可以添加數(shù)據(jù),其執(zhí)行可能會導(dǎo)致溢出。在這種情況下,根據(jù)LRU策略,最近最少使用的鍵值將被清除。新添加/修改的鍵將被標(biāo)記為最近使用的鍵。

數(shù)據(jù)結(jié)構(gòu)

我們使用兩種不同的數(shù)據(jù)結(jié)構(gòu): hash-map和雙向鏈表,實(shí)現(xiàn)鍵值讀寫和LRU策略的特性。

  • Hash-map: Hash-map是使用最廣泛的鍵值數(shù)據(jù)結(jié)構(gòu),在Go中是現(xiàn)成的數(shù)據(jù)類型,可以通過map[<type>]<type>定義。
  • 雙向鏈表: LRU緩存可以通過雙向鏈表實(shí)現(xiàn)。

基于這兩種數(shù)據(jù)結(jié)構(gòu)可以同時提供鍵值特性和LRU策略。參考以上設(shè)計概念圖,hash-map的鍵將是字符串鍵,值是指向鏈表節(jié)點(diǎn)的指針,節(jié)點(diǎn)將保存鍵的值。

如果用戶調(diào)用Get(),緩存應(yīng)用程序?qū)⒃趆ash-map中搜索鍵,跟隨指針到達(dá)鏈表中的一個節(jié)點(diǎn),獲取值,完成LRU策略,并將值返回給用戶。

類似的,如果調(diào)用Put(),會在hash-map中搜索鍵,跟蹤指針并替換值,完成LRU策略,或者向hash-map中插入新鍵,并向鏈表中插入新節(jié)點(diǎn)。

并發(fā)控制

由于緩存被設(shè)計為支持頻繁訪問,因此在同一時間會有多個訪問,并且總是存在并發(fā)問題的可能性。

在該設(shè)計中,存在兩種不同的數(shù)據(jù)結(jié)構(gòu),并且并不總是同步的。在執(zhí)行過程中,hash-map的修改和鏈表的修改之間有一個微小的時間間隔,請看下面的例子。

  • 該問題的觸發(fā)條件為: 當(dāng)前緩存已滿,最近最少使用的鍵為1。這意味著,如果添加了新的鍵,鍵1和等效的值將被清除。
  • 用戶A使用新鍵101調(diào)用Put()。hash-map檢查鍵,發(fā)現(xiàn)101不存在,決定清除1并將101添加到緩存中。
  • 同時,用戶B使用鍵1調(diào)用Put()。hash-map確認(rèn)鍵1存在,并決定修改該值。
  • A的調(diào)用繼續(xù)執(zhí)行,從鏈表中刪除節(jié)點(diǎn)1,從hash-map中刪除鍵1。
  • 緊接著,B的調(diào)用試圖訪問節(jié)點(diǎn)1的地址,并發(fā)現(xiàn)該地址已不存在,從而發(fā)生panic并造成應(yīng)用失效。

防止這種情況發(fā)生的最簡單方法是使用互斥(Mutex) ,參考以下代碼。

func (s *CStorage) Get(key string) (data []byte, hit bool) {
  s.mutex.Lock()
  defer s.mutex.Unlock()
  n, ok := s.table[key]
  if !ok {
    return nil, false
  }
  if n.ttl.Before(time.Now()) {
    s.evict(n)
    s.size--
    return nil, false
  }
  return n.data, true
}

這段代碼是Get()的函數(shù)定義,可以看到在第一行中有互斥鎖代碼,在第二行中有defer的互斥鎖解鎖代碼(defer是Go關(guān)鍵字,將行執(zhí)行推遲到函數(shù)的末尾)。這些代碼應(yīng)用于所有其他數(shù)據(jù)存儲訪問功能,如Put、Delete、Clear等。

通過使用互斥鎖,每次執(zhí)行都不會受到其他操作的影響,保證了數(shù)據(jù)訪問的安全性。

生存時間(Time To Live)

目前TTL是采用被動方式實(shí)現(xiàn)的,這意味著如果執(zhí)行了數(shù)據(jù)訪問函數(shù)(Get, Put),它將檢查TTL是否過期并決定是否刪除。這也意味著即使節(jié)點(diǎn)已經(jīng)過期,將仍然存在于數(shù)據(jù)結(jié)構(gòu)中。

這種方法不需要消耗大量CPU時間來定期遍歷所有節(jié)點(diǎn),但是緩存很可能會保存過期的值。

大多數(shù)情況下,這么做沒有問題,因?yàn)檫^期節(jié)點(diǎn)很可能是"最近最少使用"狀態(tài)。但是,如果有函數(shù)通過數(shù)據(jù)結(jié)構(gòu)清除過期節(jié)點(diǎn)就更好了,所以我們使用RemoveExpired()函數(shù)。

func (s *CStorage) RemoveExpired() int64 {
  var count int64 = 0
  for key, value := range s.table {
    if value.ttl.Before(time.Now()) {
      s.Delete(key)
      count++
    }
  }
  return count
}

此函數(shù)將被定期調(diào)用以清除所有過期節(jié)點(diǎn)。

結(jié)果

實(shí)現(xiàn)的Go包可以導(dǎo)入其他Go項目。另外,我還做了獨(dú)立的緩存應(yīng)用程序,提供gRPC API,細(xì)節(jié)可以查看這個存儲庫。

結(jié)論

這是個很好的重新審視緩存概念的機(jī)會,并且我們用Go實(shí)現(xiàn)了緩存。緩存是降低組件延遲的好工具,雖然空間受限,但速度更快。

實(shí)現(xiàn)實(shí)際的緩存模塊可以用hash-map和雙向鏈表完成。并發(fā)問題有點(diǎn)棘手,所以不得不使用互斥鎖。此外,我們混合了被動和主動方式來刪除過期數(shù)據(jù)。

以上就是Go使用緩存加速外部資源訪問提高性能效率的詳細(xì)內(nèi)容,更多關(guān)于Go緩存加速訪問的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Golang?Gin框架獲取請求參數(shù)的幾種常見方式

    Golang?Gin框架獲取請求參數(shù)的幾種常見方式

    在我們平常添加路由處理函數(shù)之后,就可以在路由處理函數(shù)中編寫業(yè)務(wù)處理代碼了,但在此之前我們往往需要獲取請求參數(shù),本文就詳細(xì)的講解下gin獲取請求參數(shù)常見的幾種方式,需要的朋友可以參考下
    2024-02-02
  • Go語言中的init函數(shù)特點(diǎn)及用法詳解

    Go語言中的init函數(shù)特點(diǎn)及用法詳解

    在Go語言中,init()函數(shù)是一種特殊的函數(shù),用于在程序啟動時自動執(zhí)行一次。它的存在為我們提供了一種機(jī)制,可以在程序啟動時進(jìn)行一些必要的初始化操作,為程序的正常運(yùn)行做好準(zhǔn)備,在這篇文章中,我們將詳細(xì)探討init()函數(shù)的特點(diǎn)、用途和注意事項
    2023-06-06
  • Go語言使用漏桶算法和令牌桶算法來實(shí)現(xiàn)API限流

    Go語言使用漏桶算法和令牌桶算法來實(shí)現(xiàn)API限流

    為防止服務(wù)器被過多的請求壓垮,限流是一個至關(guān)重要的技術(shù)手段,下面我們就來看看如何使用漏桶算法和令牌桶算法來實(shí)現(xiàn) API 的限流吧
    2024-11-11
  • golang?手寫貪吃蛇示例實(shí)現(xiàn)

    golang?手寫貪吃蛇示例實(shí)現(xiàn)

    這篇文章主要為大家介紹了golang?手寫貪吃蛇示例實(shí)現(xiàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-07-07
  • 使用Go語言編寫一個NTP服務(wù)器的流程步驟

    使用Go語言編寫一個NTP服務(wù)器的流程步驟

    NTP服務(wù)器【Network?Time?Protocol(NTP)】是用來使計算機(jī)時間同步化的一種協(xié)議,為了確保封閉局域網(wǎng)內(nèi)多個服務(wù)器的時間同步,我們計劃部署一個網(wǎng)絡(luò)時間同步服務(wù)器(NTP服務(wù)器),本文給大家介紹了使用Go語言編寫一個NTP服務(wù)器的流程步驟,需要的朋友可以參考下
    2024-11-11
  • Go語言hello world實(shí)例

    Go語言hello world實(shí)例

    這篇文章主要介紹了Go語言hello world實(shí)例,本文先是給出了hello world的代碼實(shí)例,然后對一些知識點(diǎn)和技巧做了解釋,需要的朋友可以參考下
    2014-10-10
  • 特殊字符的json序列化總結(jié)大全

    特殊字符的json序列化總結(jié)大全

    這篇文章主要給大家介紹了關(guān)于特殊字符的json序列化的相關(guān)資料,通過示例代碼分別給大家介紹了關(guān)于python 、 rust 、 java 和golang對特殊字符的json序列化操作,需要的朋友可以參考借鑒,下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2018-09-09
  • 一文詳解Golang連接kafka的基本操作

    一文詳解Golang連接kafka的基本操作

    這篇文章主要為大家詳細(xì)介紹了Golang中連接kafka的基本操作的相關(guān)知識,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2025-03-03
  • go panic時如何讓函數(shù)返回數(shù)據(jù)?

    go panic時如何讓函數(shù)返回數(shù)據(jù)?

    今天小編就為大家分享一篇關(guān)于go panic時如何讓函數(shù)返回數(shù)據(jù)?,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2019-04-04
  • 利用Go實(shí)現(xiàn)一個簡易DAG服務(wù)的示例代碼

    利用Go實(shí)現(xiàn)一個簡易DAG服務(wù)的示例代碼

    DAG的全稱是Directed Acyclic Graph,即有向無環(huán)圖,DAG廣泛應(yīng)用于表示具有方向性依賴關(guān)系的數(shù)據(jù),如任務(wù)調(diào)度、數(shù)據(jù)處理流程、項目管理以及許多其他領(lǐng)域,下面,我將用Go語言示范如何實(shí)現(xiàn)一個簡單的DAG服務(wù),需要的朋友可以參考下
    2024-03-03

最新評論