我正在用 golang 编写一个应用程序。它根据请求在内存中为数据库条目生成数据结构。通常,当请求一个条目时,会多次请求它,因此我想将条目缓存在内存中,以避免多次调用数据库(主要是出于延迟原因)。
是否可以让内存中的缓存在内存中动态扩展,直到遇到内存压力(即 malloc 失败),然后释放一些缓存?
在 Redis 或类似设备中进行缓存会使部署变得复杂。如果这是唯一的其他选项,我宁愿在运行时指定静态缓存大小。
我不反对使用
C.malloc
我想,但我不知道它如何与 Go 内存管理交互(如果我分配一块内存,然后 go 运行时为 goroutine 堆栈分配一块块或堆顶部的东西,那么我无法将内存释放给操作系统,直到顶部的内容被释放为止)。另外,到目前为止我正在不使用 cgo 进行编译,如果能继续这样做就好了。
我希望
debug
或 runtime
包中的某些内容可能暗示系统面临内存压力,以便我可以动态调整缓存大小并将程序保留在纯 Go 中。
非常感谢任何帮助或见解。
这个答案有一个在运行时获取内存分配的解决方案。
这是使用该代码的并发安全缓存的起点:
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
}