轻量级、高效、易用:基于TTL的Go Map实现

142 阅读3分钟

前言

在独立开发项目时,我遇到了一个需求:维护一个集合,其中的元素在一段时间内会过期,同时支持用户自定义对集合的增删改查操作。通常,这类需求会使用Redis自带的expire功能来实现。然而,使用Redis会引入额外的运维成本,同时增加接口响应时间,尤其是对于不需要Redis这种额外依赖的场景。

目前市场上许多开源的TTL Map实现都较为复杂,同时缺乏对TTL生命周期的有效管理。经过综合考虑,决定自己实现一个简单且易用的基于TTL的Map,其用法与Golang原生Map类似,以减少开发和维护成本。

TTL Map

此库具有以下显著特点:

  • 在某些场景下,性能几乎与Golang的Map无异。
  • 提供精确的过期时间控制。
  • 支持更符合业务需求的tryDelete操作。
  • 强大的Drain策略,有效清理过期元素。
  • 支持泛型,可以灵活使用不同类型的键值对。

性能测试报告

在性能测试中,ttlmap展示了出色的表现,具体结果如下图所示:

TTL Map Benchmark

TTL Map 设计理念与细节

  1. 底层交由Map处理
    与许多复杂的TTL Map实现不同,本库直接基于Golang原生Map进行操作。许多TTL Map会通过分块等机制优化性能,但经过对比后发现,这样的做法在许多场景下并未带来显著提升,反而会增加实现的复杂性。在不涉及极高并发的场景下,直接使用Golang的Map处理更加高效,且简化了设计。

  2. 更简洁的生命周期管理
    本库内置了协程管理,用户无需显式管理协程的创建与销毁。使用Drain方法来释放所有相关资源,确保TTL Map可以被垃圾回收。此设计简化了内存管理,同时减少了用户出错的可能性。整个设计中,我们采用了nocopycant equal策略,确保了稳定性和性能。

  3. 简单易用的接口设计
    我们的目标是让用户的使用体验与Golang原生Map尽可能一致。因此,ttlmap的接口设计简洁明了,使用非常直观。以下是一个简单示例:

package main

import (
	"fmt"
	"github.com/0xdoomxy/ttlmap"
	"time"
)

func main() {
	// 设置全局过期时间
	var globalTTL = 3 * time.Second
	
	// 创建一个TTL Map实例,设置全局过期时间和刷新时间
	tm := ttlmap.NewTTLMap[string, string](ttlmap.WithTTL[string, string](globalTTL), ttlmap.WithFlushInterval[string, string](1*time.Second))
	
	// 设置带有自定义过期时间的键值对
	tm.SetWithExpire("1", "2", time.Minute)
	
	// 设置键值对
	tm.Set("1", "2")
	
	// 删除键值对
	tm.Delete("1")
	
	// 尝试删除键并返回值
	val, ok := tm.TryDelete("1")
	if ok {
		fmt.Println(val)
	}
	
	// 获取键值
	val = tm.Get("1")
	fmt.Println(val)
	
	// 尝试获取键值
	val, ok = tm.TryGet("1")
	if ok {
		fmt.Println(val)
	}
	
	// 释放资源,确保Map可以被GC回收
	tm.Drain()
}

总结

TTL Map是一个轻量级、高效、易用的解决方案,适用于需要键值对过期管理的应用场景。其简洁的API和高性能表现,使得它成为一个理想的替代方案,特别适用于无需引入额外数据库的轻量级缓存需求。

欢迎大家前往GitHub项目页面获取更多详情和代码示例,欢迎各位大佬提出建议!