golang实现纯非侵入式分布式缓存插件库gdcache的使用

golang实现纯非侵入式分布式缓存插件库gdcache的使用

gdcache简介

gdcache是一个用Golang实现的纯非侵入式缓存库,你可以用它来实现自己的缓存功能。

Go Report Card PkgGoDev codecov

特性

  • 自动缓存SQL查询结果
  • 复用ID缓存
  • 适配Xorm和Gorm框架
  • 支持联合键缓存
  • 轻量级
  • 非侵入式
  • 高性能
  • 灵活

核心原理

gdcache的核心原理是将SQL转换为ID并缓存,同时缓存ID对应的实体。这样,相同的SQL可以复用对应的实体内容。

流程图

如上图所示,每条SQL都可以转换为对应的SQL,底层会复用这些ID。如果这些ID没有被查询过,我们会从数据库中获取这些无法从缓存中检索到的实体,如果能获取到,就会缓存一次。

节省内存

传统的缓存框架会缓存结果的内容,但gdcache缓存库不同,它只会缓存结果的ID,并通过ID查找值。这样做的优点是可以重用值,ID对应的值只会被缓存一次。

安装

go get github.com/ulovecode/gdcache

快速开始

基本使用

要被缓存的类必须实现TableName()方法,并使用cache:"id"来指示缓存的键。默认是按id缓存,cache标签的值对应数据库中的字段,通常可以忽略。

type User struct {
    Id   uint64 `cache:"id"` // 或者省略标签
    Name string
    Age  int
}

func (u User) TableName() string {
    return "user"
}

联合键使用

如果想使用联合键,可以在多个字段上添加cache标签

type PublicRelations struct {
    RelatedId uint64 `cache:"related_id"`
    RelatedType string
    SourceId uint64 `cache:"source_id"`
    SourceType string
}

func (u PublicRelations) TableName() string {
    return "public_relations"
}

实现ICache接口

你可以使用redis或gocache作为底层实现。

type MemoryCacheHandler struct {
    data map[string][]byte
}

func (m MemoryCacheHandler) StoreAll(keyValues ...gdcache.KeyValue) (err error) {
    for _, keyValue := range keyValues {
        m.data[keyValue.Key] = keyValue.Value
    }
    return nil
}

func (m MemoryCacheHandler) Get(key string) (data []byte, has bool, err error) {
    bytes, has := m.data[key]
    return bytes, has, nil
}

func (m MemoryCacheHandler) GetAll(keys schemas.PK) (data []gdcache.ReturnKeyValue, err error) {
    returnKeyValues := make([]gdcache.ReturnKeyValue, 0)
    for _, key := range keys {
        bytes, has := m.data[key]
        returnKeyValues = append(returnKeyValues, gdcache.ReturnKeyValue{
            KeyValue: gdcache.KeyValue{
                Key:   key,
                Value: bytes,
            },
            Has: has,
        })
    }
    return returnKeyValues, nil
}

func (m MemoryCacheHandler) DeleteAll(keys schemas.PK) error {
    for _, k := range keys {
        delete(m.data, k)
    }
    return nil
}

func NewMemoryCacheHandler() *MemoryCacheHandler {
    return &MemoryCacheHandler{
        data: make(map[string][]byte, 0),
    }
}

Gorm使用示例

实现IDB接口

type GormDB struct {
    db *gorm.DB
}

func (g GormDB) GetEntries(entries interface{}, sql string) error {
    tx := g.db.Raw(sql).Find(entries)
    return tx.Error
}

func (g GormDB) GetEntry(entry interface{}, sql string) (bool, error) {
    tx := g.db.Raw(sql).Take(entry)
    if gorm.ErrRecordNotFound == tx.Error {
        return false, nil
    }
    return tx.Error != gorm.ErrRecordNotFound, tx.Error
}

func NewGormCacheHandler() *gdcache.CacheHandler {
    return gdcache.NewCacheHandler(NewMemoryCacheHandler(), NewGormDd())
}

func NewGormDd() gdcache.IDB {
    db, err := gorm.Open(mysql.Open("root:root@tcp(127.0.0.1:3306)/test?charset=utf8&parseTime=True&loc=Local"), &gorm.Config{})
    if err != nil {
        panic(err)
    }
    return GormDB{
        db: db,
    }
}

Xorm使用示例

实现IDB接口

type XormDB struct {
    db *xorm.Engine
}

func (g XormDB) GetEntries(entries interface{}, sql string) ( error) {
    err := g.db.SQL(sql).Find(entries)
    return err
}

func (g XormDB) GetEntry(entry interface{}, sql string) ( bool, error) {
    has, err := g.db.SQL(sql).Get(entry)
    return has, err
}

func NewXormCacheHandler() *gdcache.CacheHandler {
    return gdcache.NewCacheHandler(NewMemoryCacheHandler(), NewXormDd())
}

func NewXormDd() gdcache.IDB {
    db, err := xorm.NewEngine("mysql", "root:root@/test?charset=utf8")
    if err != nil {
        panic(err)
    }
    return XormDB{
        db: db,
    }
}

原生SQL使用示例

实现IDB接口

type MemoryDb struct {
}

func NewMemoryDb() *MemoryDb {
    return &MemoryDb{}
}

func (m MemoryDb) GetEntries(entries interface{}, sql string) error {
    mockEntries := make([]MockEntry, 0)
    mockEntries = append(mockEntries, MockEntry{
        RelateId:   1,
        SourceId:   2,
        PropertyId: 3,
    })
    marshal, _ := json.Marshal(mockEntries)
    json.Unmarshal(marshal, entries)
    return nil
}

func (m MemoryDb) GetEntry(entry interface{}, sql string) (bool, error) {
    mockEntry := &MockEntry{
        RelateId:   1,
        SourceId:   2,
        PropertyId: 3,
    }
    marshal, _ := json.Marshal(mockEntry)
    json.Unmarshal(marshal, entry)
    return true, nil
}

func NewMemoryCache() *gdcache.CacheHandler {
    return gdcache.NewCacheHandler(NewMemoryCacheHandler(), NewMemoryDb())
}

如何使用

查询单个实体时,通过实体的id查询并填充到实体中。获取多个实体时,可以使用任何sql查询,最后填充到实体中。两种方法都必须引入主体的指针。

func TestNewGormCache(t *testing.T) {
    handler := NewGormCacheHandler()

    user := User{
        Id: 1,
    }
    has, err := handler.GetEntry(&user)
    if err != nil {
        t.FailNow()
    }
    if has {
        t.Logf("%v", user)
    }
    
    users := make([]User, 0)
    err = handler.GetEntries(&users, "SELECT * FROM user WHERE name = '33'")
    if err != nil {
        t.FailNow()
    }
    for _, user := range users {
        t.Logf("%v", user)
    }
    
    err = handler.GetEntries(&users, "SELECT * FROM user WHERE id in (3)")
    if err != nil {
        t.FailNow()
    }
    for _, user := range users {
        t.Logf("%v", user)
    }
    
    count, err := handler.GetEntriesAndCount(&users1, "SELECT * FROM user WHERE id in (1,2)")
    if err != nil {
        t.FailNow()
    }
    for _, user := range users1 {
        t.Logf("%v", user)
    }
    t.Log(count)
    
    users3 := make([]User, 0)
    ids := make([]uint64, 0)
    count, err = handler.GetEntriesAndCount(&users3, "SELECT * FROM user WHERE id in ?", ids)
    if err != nil {
        t.FailNow()
    }
    for _, user := range users1 {
        t.Logf("%v", user)
    }
    t.Log(count)
    
    count, err = handler.GetEntriesAndCount(&users1, "SELECT * FROM user WHERE id =  ?", 1)
    if err != nil {
        t.FailNow()
    }
    for _, user := range users1 {
        t.Logf("%v", user)
    }
    t.Log(count)
    
    condition := []User{{Id: 1},{Id: 2},{Id: 3}}
    
    err = handler.GetEntriesByIds(&users1, condition)
    if err != nil {
        t.FailNow()
    }
    for _, user := range users1 {
        t.Logf("%v", user)
    }
    t.Log(count)
}

支持占位符?,替换数组和基本类型。

贡献

你可以通过提交PR来帮助提供更好的gdcache。

许可证

© Jovanzhu, 2021~time.Now

根据MIT许可证发布


更多关于golang实现纯非侵入式分布式缓存插件库gdcache的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang实现纯非侵入式分布式缓存插件库gdcache的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Golang 纯非侵入式分布式缓存插件库 gdcache 使用指南

gdcache 是一个纯非侵入式的 Golang 分布式缓存插件库,它允许开发者在不修改现有代码结构的情况下轻松添加分布式缓存功能。下面我将详细介绍 gdcache 的使用方法。

1. 安装 gdcache

首先,使用 go get 安装 gdcache:

go get github.com/gdcache/gdcache

2. 基本使用

2.1 初始化缓存

package main

import (
	"fmt"
	"github.com/gdcache/gdcache"
	"time"
)

func main() {
	// 初始化本地缓存
	localCache := gdcache.NewLocalCache(1024) // 1024 是缓存容量

	// 初始化分布式缓存
	distCache := gdcache.NewDistributedCache(
		"redis://127.0.0.1:6379", // Redis 地址
		gdcache.WithTTL(10*time.Minute), // 默认过期时间
		gdcache.WithPrefix("myapp:"), // 键前缀
	)

	// 组合缓存策略:先查本地,再查分布式
	cache := gdcache.NewTieredCache(localCache, distCache)
}

2.2 基本缓存操作

// 设置缓存
err := cache.Set("user:123", []byte(`{"name":"John","age":30}`))
if err != nil {
	fmt.Println("设置缓存失败:", err)
}

// 获取缓存
data, err := cache.Get("user:123")
if err != nil {
	fmt.Println("获取缓存失败:", err)
} else {
	fmt.Println("获取到缓存:", string(data))
}

// 删除缓存
err = cache.Delete("user:123")
if err != nil {
	fmt.Println("删除缓存失败:", err)
}

3. 非侵入式集成

3.1 使用缓存装饰器

gdcache 的核心特性是可以通过装饰器模式非侵入式地添加缓存:

// 原始函数
func GetUserFromDB(userID string) ([]byte, error) {
	// 模拟数据库查询
	time.Sleep(100 * time.Millisecond)
	return []byte(fmt.Sprintf(`{"id":"%s","name":"User %s"}`, userID, userID)), nil
}

// 创建缓存装饰器
cachedGetUser := gdcache.CacheFunc(cache, "user:", GetUserFromDB)

// 使用带缓存的函数
func main() {
	// 第一次调用会执行原始函数并缓存结果
	data, err := cachedGetUser("123")
	
	// 后续调用会直接从缓存获取
	data, err = cachedGetUser("123")
}

3.2 自动生成缓存键

gdcache 支持自动根据函数参数生成缓存键:

// 原始函数
func GetUserPosts(userID string, page int) ([]byte, error) {
	// 数据库查询逻辑
	return []byte(fmt.Sprintf(`{"user":"%s","page":%d,"posts":[]}`, userID, page)), nil
}

// 创建缓存装饰器
cachedGetUserPosts := gdcache.CacheFunc(cache, "user_posts:", GetUserPosts)

// 使用
data, err := cachedGetUserPosts("123", 1) // 缓存键会自动包含参数

4. 高级特性

4.1 缓存穿透保护

// 启用缓存穿透保护
distCache := gdcache.NewDistributedCache(
	"redis://127.0.0.1:6379",
	gdcache.WithPenetrationProtection(true), // 启用保护
	gdcache.WithEmptyValueTTL(1*time.Minute), // 空值缓存时间
)

// 当原始函数返回空值时,gdcache 会缓存空结果,防止频繁查询数据库

4.2 分布式锁

// 获取分布式锁
lock, err := distCache.AcquireLock("resource:lock", 10*time.Second)
if err != nil {
	fmt.Println("获取锁失败:", err)
	return
}
defer lock.Release() // 确保释放锁

// 执行需要加锁的操作
fmt.Println("成功获取锁,执行关键操作")

4.3 缓存统计

// 获取缓存统计信息
stats := cache.Stats()
fmt.Printf("命中率: %.2f%%, 请求数: %d, 命中数: %d\n",
	stats.HitRate()*100, stats.Requests, stats.Hits)

5. 最佳实践

  1. 键设计:使用有意义的键前缀,如 “user:”, “product:” 等
  2. 过期时间:根据数据更新频率设置合理的 TTL
  3. 缓存粒度:缓存适度大小的数据,避免过大或过小
  4. 错误处理:缓存操作应该优雅降级,不影响主流程
  5. 监控:定期检查缓存命中率和性能指标

6. 清理缓存

// 清除所有缓存(慎用)
err := cache.Clear()
if err != nil {
	fmt.Println("清除缓存失败:", err)
}

// 按前缀清除
err = cache.ClearByPrefix("user:")
if err != nil {
	fmt.Println("按前缀清除缓存失败:", err)
}

gdcache 的设计理念是尽量减少对现有代码的侵入,通过装饰器模式和智能缓存策略来提升应用性能。它特别适合在已有项目中快速添加缓存层,而无需大规模重构代码。

回到顶部