go ttlmap实现示例

在某些情况下,您的应用程序并不需要 Redis,内部带有锁和过期机制的内存映射就足以满足需求。

例如,当您已知映射的大小且不需要存储大量数据时。适用场景包括 IP 速率限制或其他短期数据存储。

以下是如何在 Go 中实现这种数据结构,我们称之为 TTLMap:

package ttlmap

import (
	"sync"
	"time"
)

// item 结构体用于保存值和最后访问时间
type item struct {
	value      interface{}
	lastAccess int64
}

// 可以针对整个应用程序使用单个映射,或针对不同用途创建多个映射
type TTLMap struct {
	m map[string]*item // 存储实际数据的映射
	mu sync.Mutex      // 确保线程安全的互斥锁
}

// New 创建指定大小和最大 TTL 的映射
func New(size int, maxTTL int) (m *TTLMap) {
	m = &TTLMap{m: make(map[string]*item, size)}

	// 这个 goroutine 会定期清理映射中的旧条目
	go func() {
		for now := range time.Tick(time.Second) { // 可调整定时器频率
			m.mu.Lock()
			for k, v := range m.m {
				if now.Unix()-v.lastAccess > int64(maxTTL) {
					delete(m.m, k) // 删除过期条目
				}
			}
			m.mu.Unlock()
		}()
	}

	return
}

// Put 添加新条目或更新现有条目
func (m *TTLMap) Put(k string, v interface{}) {
	m.mu.Lock()
	defer m.mu.Unlock()

	it, ok := m.m[k]
	if !ok {
		it = &item{
			value: v,
		}
	}
	it.value = v
	it.lastAccess = time.Now().Unix() // 更新访问时间
	m.m[k] = it
}

// Get 返回指定键的值(如果存在)
func (m *TTLMap) Get(k string) (interface{}, bool) {
	m.mu.Lock()
	defer m.mu.Unlock()

	if it, ok := m.m[k]; ok {
		it.lastAccess = time.Now().Unix() // 更新访问时间
		return it.value, true
	}

	return nil, false
}

// Delete 从映射中移除条目
func (m *TTLMap) Delete(k string) {
	m.mu.Lock()
	defer m.mu.Unlock()

	if _, ok := m.m[k]; ok {
		delete(m.m, k) // 直接删除条目
	}
}

该映射支持并发安全访问,并且每秒会自动清理过期条目(可通过调整 time.Tick 频率改变清理频率)。虽然它明显缺乏 Redis 的诸多特性,但对于简单场景(如 IP 限流)来说已经足够。目前仅提供 Put、Get、Delete 三个基础方法,没有通配符等高级功能。如需更复杂功能,建议使用 Redis 或其他键值存储系统。

使用示例:

// 创建容量为100条目、最大TTL为10秒的映射
m := ttlmap.New(100, 10)

m.Put("key1", "字符串值")
v, ok := m.Get("key1") // v == "字符串值", ok == true

m.Put("key2", 42)

v, ok = m.Get("key3") // v == nil, ok == false
posted @ 2025-05-22 14:02  卓能文  阅读(27)  评论(0)    收藏  举报