一个由golang实现的纯粹的非侵入式分布式缓存库

495 阅读3分钟

gdcache

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

功能介绍

  • 自动缓存sql
  • 重复使用id缓存
  • 适应Xorm和Gorm框架
  • 支持缓存联合密钥
  • 轻量级
  • 非侵入性
  • 性能高
  • 灵活

核心原理

gdcache的核心原理是将sql转换为id并进行缓存,同时缓存id对应的实体。这样一来,每个sql都有相同的id,可以重复使用相应的实体内容。

A pure non-intrusive distributed cache library implemented by golang

如上图所示,每一块sql都可以转换为对应的sql,底层会重用这些id。如果这些id没有被查询到,因为我们不知道这些id是否已经过期,或者这些值在数据库中不存在,我们都会在数据库中,访问这些不能从数据库的缓存中获取的实体。得到一次,如果能得到,就会缓存一次。

节省内存

传统的缓存框架会对结果的内容进行缓存,但gdcache缓存库与之不同。它只会缓存结果的id,并通过id找到值。这样做的好处是,值可以被重复使用,而id对应的值只被缓存一次。

安装

go get github.com/ulovecode/gdcache

快速入门

  • 要缓存的类必须实现TableName() 方法,并使用cache:"id" 来表示缓存的键。默认是通过id 来缓存,cache 标签的值对应于数据库中的Fields,通常可以忽略不计:
type User struct {
	Id   uint64 `cache:"id"` // Or omit the tag
	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)
}