<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          在 Go 項目中基于本地內(nèi)存緩存的實現(xiàn)及應(yīng)用

          共 2771字,需瀏覽 6分鐘

           ·

          2021-11-04 16:57


          大家好,我是 Go 學(xué)堂的漁夫子。今天給大家介紹一下在 Go 項目中在數(shù)據(jù)量小、讀取頻繁的場景中如何實現(xiàn)基于本地內(nèi)存緩存的方法以提高系統(tǒng)性能。

          對于緩存,大家都不陌生。百度百科的定義是這樣的:

          緩存是指可以進行高速數(shù)據(jù)交換的存儲器,它先于內(nèi)存與 CPU 交換數(shù)據(jù),因此速率很快。

          由此可知,緩存是用來提高數(shù)據(jù)交換速度的。我們今天要講的緩存不是 CPU 中的緩存,而是在應(yīng)用程序中對數(shù)據(jù)庫的緩存。應(yīng)用程序先于數(shù)據(jù)庫,從緩存中讀取數(shù)據(jù),以降低數(shù)據(jù)庫的壓力,提高應(yīng)用程序的讀取性能。

          在實際項目中,相信大家也都遇到過類似的情景:數(shù)據(jù)量小,但訪問又較頻繁(例如國家標(biāo)準(zhǔn)行政區(qū)域數(shù)據(jù)),想將其完全存放于本地內(nèi)存中。這樣就可以避免直接訪問 mysql 或 redis,減少網(wǎng)絡(luò)傳輸,提高訪問速度。那具體應(yīng)該怎么實現(xiàn)呢?

          本文就介紹一種 Go 項目中經(jīng)常使用到的方法:將數(shù)據(jù)從數(shù)據(jù)庫中加載到本地文件,然后再將文件中的數(shù)據(jù)加載到內(nèi)存中,內(nèi)存中的數(shù)據(jù)直接供應(yīng)用程序使用。如下圖所示:


          本文會忽略數(shù)據(jù)庫到本地文件的過程,因為這個環(huán)節(jié)就是一個文件上傳和下載到本地的過程。所以我們會重點講解如何從本地文件加載數(shù)據(jù)到內(nèi)存中這個環(huán)節(jié)。

          01

          目標(biāo)

          在 Go 語言的項目中,將本地文件的數(shù)據(jù)加載到應(yīng)用程序的內(nèi)存中,以供應(yīng)用程序直接使用。

          我們再將目標(biāo)拆解成兩個目標(biāo):

          1、程序啟動時,將本地文件的數(shù)據(jù)初始化到內(nèi)存中,即冷啟動

          2、程序運行期間,本地文件有更新時,將數(shù)據(jù)更新到內(nèi)存中。

          02

          代碼實現(xiàn)

          本文主要是目的就是給大家講解目標(biāo)的實現(xiàn),所以不會帶大家一步步分析,而是通過講解已實現(xiàn)的代碼來給大家提供一種參考實現(xiàn)。

          所以,我們先給出我們設(shè)計的類圖:



          從類圖中可知,有兩個主要的結(jié)構(gòu)體:FileDoubleBuffer 和 LocalFileLoader。下面我們一一講解這兩個結(jié)構(gòu)體的屬性和方法實現(xiàn)。

          2.1 場景假設(shè)

          我們以城市的天氣狀況為示例,將每個城市的實時溫度和風(fēng)力以 json 格式存儲在文件中,當(dāng)城市的溫度或風(fēng)力有變化時,再更新該文件。如下:

          {
          "beijing": {
          "temperature": 23,
          "wind": 3
          },
          "tianjin": {
          "temperature": 20,
          "wind": 2
          },
          "shanghai": {
          "temperature": 20,
          "wind": 20
          },
          "chongqing": {
          "temperature": 30,
          "wind": 10
          }}

          2.2 main 的調(diào)用

          這里,先給出 main 函數(shù)的調(diào)用示例,根據(jù) main 函數(shù)中的實現(xiàn),我們一步步看圖中兩個主要結(jié)構(gòu)體的實現(xiàn),代碼如下:

          //第一步,定義裝載文件中數(shù)據(jù)的結(jié)構(gòu)體
          type WeatherContainer struct {
          Weathers map[string]*Weather //每個城市對應(yīng)的實況天氣
          }
          //文件數(shù)據(jù)中每個城市的天氣狀況
          type Weather struct {
          Temperature int //當(dāng)前氣溫 `json:"temperature"`
          Wind int //當(dāng)前風(fēng)力 `json:"wind"`
          }
          func main() {
          pwd, _ := os.Getwd()
          //加載的文件路徑
          filename := pwd + "/cache/cache.json"
          //初始化本地文件加載器
          localFileLoader := NewLocalFileLoader(filename)
          //初始化文件緩沖實例,將localFileLoader作為底層的文件緩沖
          fileDoubleBuffer := NewFileDoubleBuffer(localFileLoader)

          // 開始將文件中的內(nèi)容加載到緩沖變量中,本質(zhì)上就是通過load和reload加載文件數(shù)據(jù)
          fileDoubleBuffer.StartFileBuffer()

          //獲取數(shù)據(jù)
          weathersConfig := fileDoubleBuffer.Data().(*WeatherContainer)
          fmt.Println("weathers:", weathersConfig.Weathers["beijing"])

          blockCh := make(chan int)
          //該通道用于阻塞進程不結(jié)束,這樣reload的協(xié)程就可以執(zhí)行了
          <-blockCh
          }

          2.3 FileDoubleBuffer 結(jié)構(gòu)體及實現(xiàn)

          該結(jié)構(gòu)體的作用主要是面向應(yīng)用程序(我們這里是 main 函數(shù)),供應(yīng)用程序直接從內(nèi)存即 bufferData 中獲取數(shù)據(jù)的。該結(jié)構(gòu)體的定義如下:

          // main應(yīng)用主要面向該結(jié)構(gòu)體獲取數(shù)據(jù)
          type FileDoubleBuffer struct {
          Loader *LocalFileLoader
          bufferData []interface{}
          curIndex int32
          mutex sync.Mutex
          }

          首先看該結(jié)構(gòu)體的屬性:

          Loader:是一個 LocalFileLoader 類型(后面會定義該結(jié)構(gòu)體),用于從具體的文件中加載數(shù)據(jù)到 bufferData 中。

          bufferData 切片:接收文件中數(shù)據(jù)的變量。一方面會將文件中的數(shù)據(jù)加載到該變量中。另一方面,應(yīng)用程序直接從該變量中獲取想要的數(shù)據(jù)信息,而非文件或數(shù)據(jù)庫。該變量的數(shù)據(jù)類型是 interface{},說明可以加載任何類型的數(shù)據(jù)結(jié)構(gòu)。另外,我們注意該變量是一個切片,該切片只有 2 個元素,兩個元素具有相同的數(shù)據(jù)結(jié)構(gòu),結(jié)合 curIndex 屬性使用。

          curIndex:該屬性是指定當(dāng)前 bufferData 正在使用哪個索引中的數(shù)據(jù),該屬性的值在 0 和 1 之間循環(huán),用于新老數(shù)據(jù)的切換。例如,當(dāng)前對外使用的是 curIndex=1 這個索引元素的數(shù)據(jù),當(dāng)文件中有新數(shù)據(jù)時,先將文件的數(shù)據(jù)加載到索引 0 這個元素中,當(dāng)將文件的數(shù)據(jù)完全加載完后,再將 curIndex 的值指向 0。這樣,當(dāng)文件中有新數(shù)據(jù)進行刷新內(nèi)存中的數(shù)據(jù)時,不會影響應(yīng)用程序?qū)蠑?shù)據(jù)的使用。

          再來看 FileDoubleBuffer 中的函數(shù):

          Data() 函數(shù)

          應(yīng)用程序通過該函數(shù)來獲取 FileDoubleBuffer 中的 dataBuffer 數(shù)據(jù)。具體實現(xiàn)如下:

          func (buffer *FileDoubleBuffer) Data() interface{} {
          // bufferData實際上存儲了兩個相同結(jié)構(gòu)的元素,用于切換新老數(shù)據(jù)
          index := atomic.LoadInt32(&buffer.curIndex)
          return buffer.bufferData[index]
          }

          load 函數(shù)

          該函數(shù)是用于加載文件中的數(shù)據(jù)到 bufferData 中。代碼實現(xiàn)如下:

          func (buffer *FileDoubleBuffer) load() {
          buffer.mutex.Lock()
          defer buffer.mutex.Unlock()
          //判斷當(dāng)前使用的是bufferData數(shù)組哪個元素
          // 因bufferData中只有兩個元素,所以要么是0,要么是1
          curIndex := 1 - atomic.LoadInt32(&buffer.curIndex)

          err := buffer.Loader.Load(buffer.bufferData[curIndex])
          if err == nil {
          atomic.StoreInt32(&buffer.curIndex, curIndex)
          }
          }

          reload 函數(shù)

          用于從文件中加載新的數(shù)據(jù)到 bufferData 中。實際上是一個 for 循環(huán),每隔一定的時間執(zhí)行一次 load 函數(shù),代碼如下:

          func (buffer *FileDoubleBuffer) reload() {
          for {
          time.Sleep(time.Duration(5) * time.Second)
          fmt.Println("開始加載...")
          buffer.load()
          }
          }

          StartFileBuffer 函數(shù)

          該函數(shù)的作用是啟動數(shù)據(jù)的加載和更新,代碼如下:

          func (buffer *FileDoubleBuffer) StartFileBuffer() {
          buffer.load()
          go buffer.reload()
          }

          NewFileDoubleBuffer(loader *LocalFileLoader) *FileDoubleBuffer 函數(shù)

          該函數(shù)的作用是初始化 FileDoubleBuffer 實例,代碼如下:

          func NewFileDoubleBuffer(loader *LocalFileLoader) *FileDoubleBuffer {
          buffer := &FileDoubleBuffer{
          Loader: loader,
          curIndex: 0,
          }

          //這里分配內(nèi)存空間,以便將文件中的值加載到該變量中,供應(yīng)用程序使用
          buffer.bufferData = append(buffer.bufferData, loader.Alloc(), loader.Alloc())
          return buffer
          }

          2.4 LocalFileLoader 結(jié)構(gòu)體及實現(xiàn)?由于我們是將數(shù)據(jù)先從數(shù)據(jù)庫加載到本地文件上,然后再將文件的數(shù)據(jù)加載到內(nèi)存緩沖區(qū)中,故有了 LocalFileLoader 結(jié)構(gòu)體。該結(jié)構(gòu)體的作用是執(zhí)行具體的文件數(shù)據(jù)加載和檢測文件更新的任務(wù)。LocalFileLoader 的定義如下:

          type LocalFileLoader struct {
          filename string //需要加載的文件,完整路徑
          lastModifyTime int64 //文件最近一次的修改時間
          }

          首先來看該結(jié)構(gòu)體的屬性:

          filename:指定具體的文件名,說明從該文件中加載數(shù)據(jù)

          modifyTime:最后一次加載文件的時間。如果文件的更新時間大于該時間,則說明文件有更新

          再來看 LocalFileLoader 中的函數(shù):

          Load(filename string, i interface) 函數(shù)

          該函數(shù)用于將 filename 文件中的數(shù)據(jù)加載到變量 i 中。該變量 i 實際上是從 FileDoubleBuffer 中傳進來的 bufferData 中的元素,代碼如下:

          // 這里i變量實際上是從FileDoubleBuffer結(jié)構(gòu)的load方法中傳入的dataBuffer中的一個元素
          func (loader *LocalFileLoader) Load(i interface{}) error {
          // WeatherContainer結(jié)構(gòu)體是依據(jù)文件中具體存儲的數(shù)據(jù)定義的,后面會講到
          weatherContainer := i.(*WeatherContainer)
          fileHandler, _ := os.Open(loader.filename)
          defer fileHandler.Close()
          body, _ := ioutil.ReadAll(fileHandler)
          _ := json.Unmarshal(body, &weatherContainer.Weathers)
          // 這里我們省略了那些err的判斷
          return nil
          }

          DetectNewFile() 函數(shù)

          該函數(shù)用于檢測 filename 文件是否有更新,如果文件的修改時間大于 modifyTime,則 FileDoubleBuffer 會將新的數(shù)據(jù)加載到 dataBuffer 中。代碼如下:

          // 該函數(shù)檢查文件是否有更新,如果有更新 則返回true,否則返回false
          func (loader *LocalFileLoader) DetectNewFile() bool {
          fileInfo, _ := os.Stat(loader.filename)
          //文件的修改時間比上次修改時間大,說明文件有更新
          if fileInfo.ModTime().Unix() > loader.lastModifyTime {
          loader.lastModifyTime = fileInfo.ModTime().Unix()
          return true
          }
          return false
          }

          *Alloc() interface{} *

          用于分配具體的變量,以供裝載文件中的數(shù)據(jù)。這里分配的變量最終會存儲到 FileDoubleBuffer 中的 dataBuffer 數(shù)據(jù)中。代碼如下:

          // 分配具體的變量,來承載文件中的具體內(nèi)容,變量結(jié)構(gòu)體需要和文件中的結(jié)構(gòu)體保持一致
          func (loader *LocalFileLoader) Alloc() interface{} {
          return &WeatherContainer{
          Weathers: make(map[string]*Weather),
          }
          }

          同樣需要一個初始化 LocalFileLoader 實例的函數(shù):

          //指定需要加載的文件路徑path
          func NewLocalFileLoader(path string) *LocalFileLoader {
          return &LocalFileLoader{
          filename: path,
          }
          }

          03

          總結(jié)

          這種方式一般適用于數(shù)據(jù)量較小、頻繁讀的場景。在文章開始的圖中我們可以看到,因為是服務(wù)器往往是集群,所以每臺機器上的文件內(nèi)容可能會有短暫的差異,所以該實現(xiàn)也不適用于對數(shù)據(jù)具有強一致要求的場景中。


          想要了解關(guān)于 Go 的更多資訊,還可以通過掃描的方式,進群一起探討哦~

          瀏覽 67
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  鸡巴马上放进去免费视频网站 | 亚洲小说一区二区 | 精品人伦一区二区三电影 - 百度 麻豆网站-麻豆午夜在线-成人AV | 色婷婷黄色无码视频 | 日韩激情一二三 |