【问题标题】:Map with TTL option in Go在 Go 中使用 TTL 选项映射
【发布时间】:2014-08-25 10:43:44
【问题描述】:

我需要构建一个这样的数据结构:

map[string]SomeType 

但它必须存储大约 10 分钟的值,然后将其从内存中清除。 第二个条件是记录数量 - 它必须是巨大的。此数据结构必须至少添加 每秒 2-5K 条记录

那么,Go 中最正确的方法是什么?

我正在尝试为每个新元素制作带有超时的 goroutine。以及一个(或多个)带有通道的垃圾收集器 goroutine 来接收超时和清除元素。 但我不确定这是最清楚的方法。有数百万个等待超时的 goroutine 是否可以?

谢谢。

【问题讨论】:

  • 如果你需要这种 TTL 行为,你不会想要使用map,或者至少除了@987654325之外你还需要添加一些其他的数据结构@。您可能想查看最小堆(例如,在 Go 中,有 container/heap)。
  • 谢谢!但是使用 map 是必要的,因为我需要通过它们的 ID 随机访问元素。有什么想法吗?
  • 所以在草图中,我要做的是这个(如果你愿意,我可以更详细地写出来作为答案):拥有一个同时具有映射(用于值)的数据结构和一个堆(用于过期时间)。每次将条目添加到映射时,它都会插入到以其到期时间为键的堆中。每次有人进行某种地图访问操作时,您首先将所有已过期的元素从堆中弹出(基本上,一直持续到过期时间在time.Now() 之后),然后从地图中删除所有元素。然后你执行任何请求的操作。
  • 另外,如果您希望地图项在没有人使用地图的情况下过期(这可能有助于回收程序其他部分使用的内存),您可以运行一个单独的 goroutine只是循环从堆中弹出东西,直到它们的到期时间到来,然后从映射中删除元素(您需要确保堆和映射都正确同步 - 可能使用sync.RWMutex 或类似的东西;这是使用通道进行同步的情况之一。
  • 您可能需要考虑使用github.com/ReneKroon/ttlcache

标签: data-structures go timeout ttl


【解决方案1】:

您必须创建一个结构来保存您的地图并提供自定义 get/put/delete 函数来访问它。

请注意,每秒 2-5k 次访问实际上并没有那么多,因此您不必担心。

这是一个简单的实现:

type item struct {
    value      string
    lastAccess int64
}

type TTLMap struct {
    m map[string]*item
    l sync.Mutex
}

func New(ln int, maxTTL int) (m *TTLMap) {
    m = &TTLMap{m: make(map[string]*item, ln)}
    go func() {
        for now := range time.Tick(time.Second) {
            m.l.Lock()
            for k, v := range m.m {
                if now.Unix() - v.lastAccess > int64(maxTTL) {
                    delete(m.m, k)
                }
            }
            m.l.Unlock()
        }
    }()
    return
}

func (m *TTLMap) Len() int {
    return len(m.m)
}

func (m *TTLMap) Put(k, v string) {
    m.l.Lock()
    it, ok := m.m[k]
    if !ok {
        it = &item{value: v}
        m.m[k] = it
    }
    it.lastAccess = time.Now().Unix()
    m.l.Unlock()
}

func (m *TTLMap) Get(k string) (v string) {
    m.l.Lock()
    if it, ok := m.m[k]; ok {
        v = it.value
        it.lastAccess = time.Now().Unix()
    }
    m.l.Unlock()
    return

}

playground

note(2020-09-23):由于某种原因,当前版本的 Playground 上的时间分辨率相差很远,这很好用,但是要在 Playground 上尝试,您必须将睡眠时间更改为 3-5 秒.

【讨论】:

  • 这看起来像我在寻找!谢谢。
  • 感谢您的代码。几天前我想知道这个确切的问题,我想了一个类似的解决方案。每秒扫描整个地图可能太多了,但我想这取决于应用程序。
  • 另一种解决方案是保留地图和链表。链表包含成对的(键,时间戳)。你从后面推,从前面拿,所以钥匙是按时间排序的,知道哪些钥匙可以被移除是非常快的。如果您设置正确的睡眠持续时间,甚至可能是瞬时的。这会使用更多内存,但速度会更快。
  • @matvey.co 如果答案对您有帮助,您可以通过点击 upvote/downvote 按钮下的小复选标记将其标记为正确答案。 siritinga 很大程度上取决于地图上有多少项目以及每秒获取/设置的数量。
  • @OneOfOne 你是对的。我知道他需要插入后 10 分钟的数据,而不是上次访问后的数据。我想它可以被解释为两种方式。如果已知最大元素数,那么使用切片的循环缓冲区当然比之前建议的列表或堆更好。
【解决方案2】:

看看buntdb

tinykv 不再维护。

为了记录,我遇到了同样的问题并写了tinykv包,它在内部使用了一个地图。

  • 它使用time.Time 堆来进行超时,因此它不会覆盖整个地图。
  • 创建实例时可以设置最大间隔。但检查超时的实际间隔可以是任何大于零且小于最大值的time.Duration 值,具体取决于最后一个超时的项目。
  • 它提供CASTake 功能。
  • 可以设置回调(可选),通知哪个键和值超时。
  • 超时可以是显式的或滑动的。

【讨论】:

  • 我想知道为什么 github.com/workshop-depot/tinykv 存储库被“归档”了?
  • 简而言之(抛开其他原因),在更大的社区中有更好的选择(例如,看看 buntdb)。
【解决方案3】:

我建议使用golang内置包sync的Map,非常好用并且已经处理并发https://golang.org/pkg/sync/#Map

【讨论】:

    猜你喜欢
    • 2021-04-25
    • 2018-12-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-11
    相关资源
    最近更新 更多