gdcache
gdcache是一个由golang实现的纯非侵入式分布式缓存库,你可以用它来实现你自己的分布式缓存。
功能介绍
- 自动缓存sql
- 重复使用id缓存
- 适应Xorm和Gorm框架
- 支持缓存联合密钥
- 轻量级
- 非侵入性
- 性能高
- 灵活
核心原理
gdcache的核心原理是将sql转换为id并进行缓存,同时缓存id对应的实体。这样一来,每个sql都有相同的id,可以重复使用相应的实体内容。

如上图所示,每一块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)
}