使用 Go 开发的高性能分段锁机制的轻量级内存缓存 - A high-performance and memory-based cache for Go applications
Apache License 2.0
22
2
4

📜 cachego

Go Doc License License Test

cachego 是一个拥有高性能分段锁机制的轻量级内存缓存,拥有懒清理和哨兵清理两种清理机制,可以应用于所有的 GoLang 应用程序中。

目前已经在多个线上服务中运行良好,也抵御过最高 17w/s qps 的冲击,可以稳定使用!

Read me in English.

🕹 功能特性

  • 以键值对形式缓存数据,极简的 API 设计风格
  • 引入 option function 模式,可定制化各种操作的过程
  • 使用粒度更细的分段锁机制进行设计,具有非常高的并发性能
  • 支持懒清理机制,每一次访问的时候判断是否过期
  • 支持哨兵清理机制,每隔一定的时间间隔进行清理
  • 自带 singleflight 机制,减少缓存穿透的伤害
  • ....

更多功能请参考 _examples。设计信息请参考 introduction.md 文档。

历史版本的特性请查看 HISTORY.md。未来版本的新特性和计划请查看 FUTURE.md

🚀 安装方式

$ go get -u github.com/FishGoddess/cachego

💡 参考案例

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/FishGoddess/cachego"
	"github.com/FishGoddess/cachego/pkg/task"
)

func main() {
	// Create a cache for use.
	// We use option function to customize the creation of cache.
	// WithAutoGC means it will do gc automatically.
	cache := cachego.NewCache(cachego.WithAutoGC(10 * time.Minute))

	// Set a new entry to cache.
	// Both of them are set a key-value with no ttl.
	//cache.Set("key", 666, cachego.WithSetNoTTL())
	cache.Set("key", 666)

	// Get returns the value of this key.
	v, err := cache.Get("key")
	fmt.Println(v, err) // Output: 666 <nil>

	// If you pass a not existed key to of method, nil and errNotFound will be returned.
	v, err = cache.Get("not existed key")
	if cachego.IsNotFound(err) {
		fmt.Println(v, err) // Output: <nil> cachego: key not found
	}

	// SetWithTTL sets an entry with expired time.
	// See more information in example of ttl.
	cache.Set("ttlKey", 123, cachego.WithOpTTL(10*time.Second))

	// Also, you can get value from cache first, then load it to cache if missed.
	// onMissed is usually used to get data from db or somewhere, so you can refresh the value in cache.
	// Notice ctx in onMissed is passed by Get option.
	onMissed := func(ctx context.Context) (data interface{}, err error) {
		return "newValue", nil
	}

	v, err = cache.Get("newKey", cachego.WithOpOnMissed(onMissed), cachego.WithOpTTL(3*time.Second))
	fmt.Println(v, err) // Output: newValue <nil>

	// We provide a way to set data to cache automatically, so you can access some hottest data extremely fast.
	// See pkg/task/Task.
	t := task.Task{
		Before: func(ctx context.Context) {
			cache.Set("before", "value")
		},
		Fn: func(ctx context.Context) {
			cache.Set("fn", "value")
		},
		After: func(ctx context.Context) {
			cache.Set("after", "value")
		},
	}

	// Run this task automatically every second.
	go t.Run(context.Background(), time.Second)
	time.Sleep(5 * time.Second)
}

更多使用案例请查看 _examples 目录。

🔥 性能测试

测试文件:_examples/performance_test.go

$ go test -v ./_examples/performance_test.go

总缓存数据为 100w 条,并发数为 10w,循环测试写入和读取次数为 50 次

测试环境:R7-5800X CPU @ 3.8GHZ GHZ,32 GB RAM,Manjaro21 OS

测试 读取消耗时间 (越小越好) 写入消耗时间 (越小越好) 混合操作消耗时间 (越小越好)
cachego 1092ms 1107ms 1098ms
go-cache 1111ms 3152ms 4738ms
freeCache 1070ms 1123ms 1068ms
ECache 1083ms 1229ms 1121ms

可以看出,由于使用了分段锁机制,读写性能在并发下依然非常高,但是分段锁会多一次定位的操作,如果加锁的消耗小于定位的消耗,那分段锁就不占优势。 这也是为什么 cachego 在写入性能上比 go-cache 强一大截,但是读取性能却没强多少的原因。后续会着重优化读取性能!

👥 贡献者

  • cristiane:提供 hash 算法的优化建议
  • hzy15610046011:提供架构设计文档和图片
  • chen661:提供 segmentSize 设置选项的参数限制想法

如果您觉得 cachego 缺少您需要的功能,请不要犹豫,马上参与进来,发起一个 issue

最后,我想感谢 JetBrains 公司的 free JetBrains Open Source license(s),因为 cachego 是用该计划下的 Idea / GoLand 完成开发的。

Contributors

FishGoddess
hzyrc6011