如何在 Golang 中设置可释放的缓存(即当检测到内存压力时可以释放内存)?

问题描述 投票:0回答:1

我正在用 golang 编写一个应用程序。它根据请求在内存中为数据库条目生成数据结构。通常,当请求一个条目时,会多次请求它,因此我想将条目缓存在内存中,以避免多次调用数据库(主要是出于延迟原因)。

是否可以让内存中的缓存在内存中动态扩展,直到遇到内存压力(即 malloc 失败),然后释放一些缓存?

在 Redis 或类似设备中进行缓存会使部署变得复杂。如果这是唯一的其他选项,我宁愿在运行时指定静态缓存大小。

我不反对使用

C.malloc
我想,但我不知道它如何与 Go 内存管理交互(如果我分配一块内存,然后 go 运行时为 goroutine 堆栈分配一块块或堆顶部的东西,那么我无法将内存释放给操作系统,直到顶部的内容被释放为止)。另外,到目前为止我正在不使用 cgo 进行编译,如果能继续这样做就好了。

我希望

debug
runtime
包中的某些内容可能暗示系统面临内存压力,以便我可以动态调整缓存大小并将程序保留在纯 Go 中。

非常感谢任何帮助或见解。

go caching memory-management
1个回答
0
投票

这个答案有一个在运行时获取内存分配的解决方案。

这是使用该代码的并发安全缓存的起点:

package main

import (
    "bufio"
    "os"
    "strconv"
    "strings"
    "sync"
)

type Memory struct {
    MemTotal     int
    MemFree      int
    MemAvailable int
}

type Cache[T any] struct {
    MinMemFree int // Min number of free bytes
    chunkSize  int // Number of key/value pairs removed from data when MinMemFree is reached
    mu         sync.Mutex
    data       map[string]T
    order      []string // Keeps track of the order of keys added to data
}

func NewCache[T any](minMemFree int, chunkSize int) *Cache[T] {
    return &Cache[T]{
        MinMemFree: minMemFree,
        chunkSize:  chunkSize,
        data:       make(map[string]T),
        order:      []string{},
    }
}

func (c *Cache[T]) Get(key string) T {
    c.mu.Lock()
    defer c.mu.Unlock()

    return c.data[key]
}

func (c *Cache[T]) Set(key string, value T) int {
    c.mu.Lock()
    defer c.mu.Unlock()

    c.data[key] = value
    c.order = append(c.order, key)

    if c.minSizeReached() {
        return c.freeOldestChunk()
    }
    return 0
}

// Free oldest items in the cache, and return the number removed
func (c *Cache[T]) freeOldestChunk() int {
    count := 0
    for i := 1; i <= c.chunkSize; i++ {
        key := c.shiftOrder()
        if key == "" {
            break
        }
        delete(c.data, key)
        count++
    }
    return count
}

func (c *Cache[T]) shiftOrder() string {
    if len(c.order) == 0 {
        return ""
    }
    key := c.order[0]
    c.order = c.order[1:]
    return key
}

func (c *Cache[T]) minSizeReached() bool {
    return ReadMemoryStats().MemFree <= c.MinMemFree
}

func ReadMemoryStats() Memory {
    file, err := os.Open("/proc/meminfo")
    if err != nil {
        panic(err)
    }
    defer file.Close()
    bufio.NewScanner(file)
    scanner := bufio.NewScanner(file)
    res := Memory{}
    for scanner.Scan() {
        key, value := parseLine(scanner.Text())
        switch key {
        case "MemTotal":
            res.MemTotal = value
        case "MemFree":
            res.MemFree = value
        case "MemAvailable":
            res.MemAvailable = value
        }
    }
    return res
}

func parseLine(raw string) (key string, value int) {
    text := strings.ReplaceAll(raw[:len(raw)-2], " ", "")
    keyValue := strings.Split(text, ":")
    return keyValue[0], toInt(keyValue[1])
}

func toInt(raw string) int {
    if raw == "" {
        return 0
    }
    res, err := strconv.Atoi(raw)
    if err != nil {
        panic(err)
    }
    return res
}
© www.soinside.com 2019 - 2024. All rights reserved.