高效能記憶體快取 ristretto

bupt_xingxin發表於2021-09-03

背景

ristrettodgraph 團隊開源的一款高效能記憶體快取庫,旨在解決高併發場景下的快取效能和吞吐瓶頸。dgraph 專攻的方向是高效能圖資料庫,ristretto 就是其圖資料庫和 KV 資料庫產品的核心依賴。

與 golang 社群常見的其他單程式記憶體快取類庫(groupcachebigcachefastcache 等)相比,ristretto 在快取命中率和讀寫吞吐率上的綜合表現更優。

ristretto 簡介

ristretto 主要有以下優點:

  • 高命中率 - 特殊設計的錄入/驅逐政策
    • 驅逐(SampledLFU):與精確 LRU 相當,但在搜尋和資料跟蹤上有更好的效能
    • 錄入(TinyLFU):以極小的記憶體開銷獲取額外的效能提升
  • 高吞吐率
  • 權重感知的驅逐策略 - 價值權重大的條目可以驅逐多個價值權重小的條目
    • 依託權重可以擴充套件出快取最大記憶體佔用、快取最多條目數等場景
  • 完全併發支援
  • 效能指標 - 吞吐量、命中率及其他統計資料的效能指標
  • 使用者友好的 API 設計
    • 支援指定快取失效時間

ristretto 在 v0.1.0(2021-06-03) 版本釋出時已正式標註為生產可用!

ristretto 使用舉例

構建大小(條目數)受限的快取

讓我們利用 ristretto 構建一個快取條目數最大為 10 的快取試試看:

package main

import (
    "fmt"

    "github.com/dgraph-io/ristretto"
)

func main() {
    cache, err := ristretto.NewCache(&ristretto.Config{
        // num of keys to track frequency, usually 10*MaxCost
        NumCounters: 100,
        // cache size(max num of items)
        MaxCost: 10,
        // number of keys per Get buffer
        BufferItems: 64,
        // !important: always set true if not limiting memory
        IgnoreInternalCost: true,
    })
    if err != nil {
        panic(err)
    }

    // put 20(>10) items to cache
    for i := 0; i < 20; i++ {
        cache.Set(i, i, 1)
    }

    // wait for value to pass through buffers
    cache.Wait()

    cntCacheMiss := 0
    for i := 0; i < 20; i++ {
        if _, ok := cache.Get(i); !ok {
            cntCacheMiss++
        }
    }
    fmt.Printf("%d of 20 items missed\n", cntCacheMiss)
}

執行程式碼可以發現最後只有 10 個條目還儲存在快取中

$ go run main.go
10 of 20 item missed

注:當我們的快取並非限制最大記憶體佔用時,IgnoreInternalCost 一定要設為 true,否則建立出的快取將出現詭異的表現。

測試快取過期時間

還是建立一個簡單的快取,然後存一個過期時間為 1 秒的條目進去,看看接下來的快取讀寫表現:

package main

import (
    "log"
    "time"

    "github.com/dgraph-io/ristretto"
)

func main() {
    cache, err := ristretto.NewCache(&ristretto.Config{
        NumCounters:        100,
        MaxCost:            10,
        BufferItems:        64,
        IgnoreInternalCost: true,
    })
    if err != nil {
        panic(err)
    }

    // set item with 1s ttl
    cache.SetWithTTL("foo", "bar", 1, time.Second)

    // wait for value to pass through buffers
    cache.Wait()

    if val, ok := cache.Get("foo"); !ok {
        log.Printf("cache missing")
    } else {
        log.Printf("got foo: %v", val)
    }

    // sleep longer and try again
    time.Sleep(2 * time.Second)
    if val, ok := cache.Get("foo"); !ok {
        log.Printf("cache missing")
    } else {
        log.Printf("got foo: %v", val)
    }
}

執行程式碼可以發現已過期的條目被正常清除出了快取

$ go run main.go
2021/09/03 14:19:56 got foo: bar
2021/09/03 14:19:58 cache missing

總結

ristretto 是支援高併發高吞吐的記憶體快取庫,尤其適用於資料庫、搜尋引擎、檔案系統等 io 密集場景。需要注意的是 ristretto 只適用於單機單程式的快取方案,更像是 golang 中的 Caffeine (java),並不作為 redis 和 memcache 的替代品。

大家趕快試試吧!

參考資料


歡迎加入 GOLANG 中國社群:https://gocn.vip

更多原創文章乾貨分享,請關注公眾號
  • 高效能記憶體快取 ristretto
  • 加微信實戰群請加微信(註明:實戰群):gocnio

相關文章