golang高性能无锁缓存插件库otter的使用

Golang高性能无锁缓存插件库Otter的使用

Otter Logo

内存缓存库

Otter旨在提供出色的开发者体验,同时保持高性能。它解决了其前身的缺点,并借鉴了其他语言中高性能库(如Caffeine)的设计原则。

✨ 特性

性能方面,Otter提供:

  • 通过自适应W-TinyLFU算法,在各种工作负载类型下实现高命中率
  • 在大多数工作负载类型下,高并发时具有出色的吞吐量
  • 在所有缓存容量中内存开销最低
  • 基于争用/并行性和工作负载模式的自动数据结构配置

Otter还提供高度可配置的缓存API,支持以下可选功能:

  • 超过最大值时基于大小的驱逐
  • 基于时间的条目过期(自上次访问或上次写入后计算)
  • 自动加载条目到缓存中
  • 当首次请求过期条目时异步刷新
  • 将写入传播到外部资源
  • 累积缓存访问统计信息
  • 将缓存保存到文件并从文件加载缓存

📚 使用

📋 要求

Otter需要Go 1.24或更高版本。

🛠️ 安装

使用v1

go get -u github.com/maypok86/otter

使用v2

go get -u github.com/maypok86/otter/v2

✏️ 示例

Otter使用简单的Options结构进行缓存配置。以下是完整的使用示例:

package main

import (
    "context"
    "time"

    "github.com/maypok86/otter/v2"
    "github.com/maypok86/otter/v2/stats"
)

func main() {
    ctx := context.Background()

    // 创建统计计数器来跟踪缓存操作
    counter := stats.NewCounter()

    // 配置缓存:
    // - 容量:10,000个条目
    // - 最后访问后1秒过期
    // - 写入后500ms刷新间隔
    // - 启用统计收集
    cache := otter.Must(&otter.Options[string, string]{
        MaximumSize:       10_000,
        ExpiryCalculator:  otter.ExpiryAccessing[string, string](time.Second),  // 在读/写时重置计时器
        RefreshCalculator: otter.RefreshWriting[string, string](500 * time.Millisecond),  // 写入后刷新
        StatsRecorder:     counter,  // 附加统计收集器
    })

    // 阶段1:测试基本过期
    // -----------------------------
    cache.Set("key", "value")  // 添加初始值

    // 等待过期(1秒)
    time.Sleep(time.Second)

    // 验证条目已过期
    if _, ok := cache.GetIfPresent("key"); ok {
        panic("key shouldn't be found")  // 应该已过期
    }

    // 阶段2:测试缓存雪崩保护
    // --------------------------------------
    loader := func(ctx context.Context, key string) (string, error) {
        time.Sleep(200 * time.Millisecond)  // 模拟慢加载
        return "value1", nil  // 返回新值
    }

    // 并发Get会去重加载器调用
    value, err := cache.Get(ctx, "key", otter.LoaderFunc[string, string](loader))
    if err != nil {
        panic(err)
    }
    if value != "value1" {
        panic("incorrect value")  // 应该获取新加载的值
    }

    // 阶段3:测试后台刷新
    // --------------------------------
    time.Sleep(500 * time.Millisecond)  // 等待直到需要刷新

    // 返回更新值的新加载器
    loader = func(ctx context.Context, key string) (string, error) {
        time.Sleep(100 * time.Millisecond)  // 模拟刷新
        return "value2", nil  // 返回刷新后的值
    }

    // 这会触发异步刷新但返回当前值
    value, err = cache.Get(ctx, "key", otter.LoaderFunc[string, string](loader))
    if err != nil {
        panic(err)
    }
    if value != "value1" {  // 应该在刷新时获取旧值
        panic("loader shouldn't be called during Get")
    }

    // 等待刷新完成
    time.Sleep(110 * time.Millisecond)

    // 验证刷新后的值
    v, ok := cache.GetIfPresent("key")
    if !ok {
        panic("key should be found")  // 应该仍然缓存
    }
    if v != "value2" {  // 现在应该有刷新后的值
        panic("refresh should be completed")
    }
}

📊 性能

🚀 吞吐量

吞吐量基准测试是Caffeine基准测试的Go端口。这个微基准测试比较了在zipf分布下缓存的吞吐量,可以显示实现中的各种低效之处。

🎯 命中率

命中率模拟器在各种跟踪上测试缓存:

  1. 合成的(Zipf分布)
  2. 传统的(在各种项目和论文中广泛使用)

💾 内存消耗

这个基准测试量化了不同缓存容量下的额外内存消耗。

🏗️ 使用Otter的项目

以下是已知使用Otter的项目列表:

  • Grafana:开放和可组合的可观测性和数据可视化平台。
  • Centrifugo:以语言无关的方式实现的可扩展实时消息服务器。
  • FrankenPHP:现代PHP应用服务器
  • Unkey:开源API管理平台

🗃 相关工作

Otter基于以下论文:

  • BP-Wrapper: A Framework Making Any Replacement Algorithms (Almost) Lock Contention Free
  • TinyLFU: A Highly Efficient Cache Admission Policy
  • Adaptive Software Cache Management
  • Denial of Service via Algorithmic Complexity Attack
  • Hashed and Hierarchical Timing Wheels
  • A large scale analysis of hundreds of in-memory cache clusters at Twitter

👏 贡献

欢迎贡献,在提交新的PR之前,请确保打开一个新的问题,以便社区成员可以讨论它。

📄 许可证

该项目采用Apache 2.0许可证。


更多关于golang高性能无锁缓存插件库otter的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang高性能无锁缓存插件库otter的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Otter - Go语言高性能无锁缓存库

Otter是一个Go语言实现的高性能无锁缓存库,它提供了类似Java Caffeine的API设计,具有出色的并发性能。下面我将详细介绍Otter的使用方法。

基本特性

  1. 无锁设计:采用无锁算法,高并发场景下性能优异
  2. 高命中率:使用W-TinyLFU淘汰算法
  3. 丰富API:支持多种缓存操作方式
  4. 指标监控:内置缓存命中率等监控指标

安装

go get github.com/jellydator/otter

基本使用

创建缓存

package main

import (
	"fmt"
	"github.com/jellydator/otter"
	"time"
)

func main() {
	// 创建缓存
	cache, err := otter.MustBuilder[string, string](1000).
		Cost(func(key string, value string) uint32 {
			return 1 // 每个条目成本设为1
		}).
		WithTTL(time.Minute * 10). // 10分钟过期
		Build()
	if err != nil {
		panic(err)
	}

	// 设置值
	cache.Set("key1", "value1")

	// 获取值
	if value, ok := cache.Get("key1"); ok {
		fmt.Println("Got value:", value)
	}

	// 删除键
	cache.Delete("key1")

	// 关闭缓存
	cache.Close()
}

高级配置

cache, err := otter.MustBuilder[string, int](10000).
	Cost(func(key string, value int) uint32 {
		return uint32(value) // 使用值作为成本
	}).
	WithTTL(time.Hour). // 1小时过期
	WithStatsEnabled(). // 启用统计
	Build()

缓存策略

加载策略

cache, _ := otter.MustBuilder[string, string](1000).
	WithLoader(func(key string) (string, error) {
		// 当缓存未命中时自动加载
		return "loaded_" + key, nil
	}).
	Build()

value, _ := cache.Get("some_key") // 自动调用Loader

淘汰策略

Otter使用W-TinyLFU算法自动管理缓存淘汰,你也可以手动控制:

// 手动设置条目的权重
cache, _ := otter.MustBuilder[string, string](1000).
	Cost(func(key string, value string) uint32 {
		if len(value) > 100 {
			return 2 // 大值权重更高
		}
		return 1
	}).
	Build()

批量操作

// 批量设置
cache.SetMany(map[string]string{
	"k1": "v1",
	"k2": "v2",
})

// 批量获取
values := cache.GetMany([]string{"k1", "k2", "k3"})

事件监听

cache, _ := otter.MustBuilder[string, string](1000).
	WithRemovalListener(func(key string, value string, cause otter.RemovalCause) {
		fmt.Printf("Key %s removed, cause: %v\n", key, cause)
	}).
	Build()

性能监控

cache, _ := otter.MustBuilder[string, string](1000).
	WithStatsEnabled().
	Build()

// 获取统计信息
stats := cache.Stats()
fmt.Printf("Hit rate: %.2f%%\n", stats.HitRate()*100)
fmt.Println("Miss count:", stats.MissCount())

最佳实践

  1. 合理设置缓存大小:根据应用内存情况设置
  2. 考虑条目权重:不均匀大小时使用Cost函数
  3. 监控命中率:定期检查优化缓存策略
  4. 合理设置TTL:根据数据更新频率设置

性能对比

Otter在大多数场景下性能优于sync.Map和普通map+mutex的组合,特别是在高并发读多写少的场景下。

注意事项

  1. Otter不保证强一致性,适合最终一致性场景
  2. 大对象缓存需谨慎,可能影响GC性能
  3. 缓存键和值都应该是不可变的

Otter是一个优秀的无锁缓存解决方案,适合需要高性能缓存的Go应用场景。通过合理配置可以显著提升应用性能。

回到顶部