Skip to content

Latest commit

 

History

History
327 lines (259 loc) · 8.54 KB

File metadata and controls

327 lines (259 loc) · 8.54 KB

gdcache

gdcache 是一个由 golang 实现的纯非侵入式缓存库,你可以用它来实现你自己的缓存。 英文文档

Go Report Card PkgGoDev codecov

特性

  • 自动缓存 sql
  • 复用 sql 之间的缓存
  • 适配 Xorm 和 Gorm 框架
  • 支持缓存联合key
  • 轻量级
  • 无侵入性
  • 高性能
  • 灵活的

核心原理

gdcache 的核心原理是将 sql 转换成 id 缓存起来 , 并缓存 id 对应的实体。这样每个 sql 有相同的 id 可以复用对应的实体内容了。

img.png

如上图所示,每一段sql都可以转换为对应的sql,底层去复用这些id。如果这些这些id没有被查询到,由于我们不知道到底是因为过期了,还是因为这些值在数据库中不存在,我们都会在数据库中,将这些无法从cache中取的实体从从数据库中再访问一遍获取,如果能够获取到,会进行一次缓存。

节省内存

常规的缓存框架,会缓存结果的内容,但 gdcache 缓存库与之不同,他只会缓存结果的id,并通过id去寻找值。这样的好处是,可以重复利用值,id 对应的值只会被缓存一次。

安装

go get github.com/ulovecode/gdcache

快速开始

  • 要被缓存的类必须要实现 TableName() 方法,并且使用 cache:"id" 来标明被缓存的key,默认是通过 id 来进行缓存,并且 cache 标签的值对应数据库中的字段,通常情况可以忽略。
type User struct {
    Id   uint64 `cache:"id"` // Or omit the tag
    Name string
    Age  int
}

func (u User) TableName() string {
    return "user"
}
  • 如果要是使用联合key,可以为多个字段添加 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 User struct {
    Id   uint64 `cache:"id"` // Or omit the tag
    Name string
    Age  int
}

func (u User) TableName() string {
    return "user"
}
  • If you want to use a joint key, you can add a cache tag to multiple fields
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"
}
  • Implement the ICache interface, you can use redis or gocache as the underlying implementation.
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 接口

xorm 使用

实现 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,
    }
}

原生SQL 使用

实现 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,
    }
}

如何使用

查询单个实体的时候通过实体的 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)

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

贡献

您可以帮助提供更好的 gdcahe ,通过提交 pr 的方式。

许可证

© Jovanzhu, 2021~time.Now

发布在 MIT License 之下