github.com/angenalZZZ/gofunc@v0.0.0-20210507121333-48ff1be3917b/data/cache/store/badger.go (about) 1 package store 2 3 import ( 4 "errors" 5 "fmt" 6 "os" 7 "path/filepath" 8 "strings" 9 "time" 10 11 "github.com/angenalZZZ/gofunc/f" 12 "github.com/dgraph-io/badger/v2" 13 "github.com/dgraph-io/badger/v2/options" 14 ) 15 16 const ( 17 // BadgerType represents the storage type as a string value 18 BadgerType = "badger" 19 // BadgerTagPattern represents the tag pattern to be used as a key in specified storage 20 BadgerTagPattern = "gocache_tag_%s" 21 ) 22 23 // BadgerStore is a store for Redis 24 type BadgerStore struct { 25 // BadgerClientInterface represents a dgraph-io/badger client 26 client *badger.DB 27 options *Options 28 } 29 30 // OpenBadger creates a new store to Badger client. 31 func OpenBadger(path ...string) (*badger.DB, error) { 32 var opt badger.Options 33 if len(path) > 0 && path[0] != "" { 34 opt = badger.DefaultOptions(path[0]) 35 opt.Truncate = true 36 opt.SyncWrites = false 37 opt.TableLoadingMode = options.MemoryMap 38 opt.ValueLogLoadingMode = options.FileIO 39 //opt.ValueThreshold = 1 << 20 // 阈值 1 MB 40 opt.ValueThreshold = 1 // 阈值 默认 32 41 opt.NumMemtables = 2 42 opt.NumLevelZeroTables = 2 43 opt.MaxTableSize = 16 << 20 44 } else { 45 opt = badger.DefaultOptions("").WithInMemory(true) 46 } 47 48 return badger.Open(opt) 49 } 50 51 // NewBadger creates a new store to Badger instance(s) 52 func NewBadger(option *Options, path ...string) *BadgerStore { 53 if option == nil { 54 option = &Options{} 55 } 56 57 client, err := OpenBadger(path...) 58 if err != nil { 59 return nil 60 } 61 62 if len(path) > 0 && path[0] != "" { 63 go (func() { 64 for client.RunValueLogGC(0.5) == nil { 65 // cleaning ... 66 } 67 })() 68 } 69 70 return &BadgerStore{ 71 client: client, 72 options: option, 73 } 74 } 75 76 // Get returns data stored from a given key 77 func (s *BadgerStore) Get(key string) (interface{}, error) { 78 var data []byte 79 err := s.client.View(func(txn *badger.Txn) error { 80 item, err := txn.Get(f.Bytes(key)) 81 if err != nil { 82 return err 83 } 84 85 data, err = item.ValueCopy(nil) 86 return err 87 }) 88 return data, err 89 } 90 91 // TTL returns a expiration time 92 func (s *BadgerStore) TTL(key string) (time.Duration, error) { 93 var expires int64 94 95 _ = s.client.View(func(txn *badger.Txn) error { 96 item, err := txn.Get(f.Bytes(key)) 97 if err != nil { 98 expires = -2 99 return nil 100 } 101 102 exp := item.ExpiresAt() 103 if exp == 0 { 104 expires = -1 105 return nil 106 } 107 108 expires = int64(exp) 109 return nil 110 }) 111 112 if expires == -2 { 113 return -2, errors.New("unable to retrieve data from badger") 114 } 115 116 if expires == -1 { 117 return -1, errors.New("unable to retrieve data from badger") 118 } 119 120 now := time.Now().Unix() 121 122 if now >= expires { 123 return -2, errors.New("unable to retrieve data from badger") 124 } 125 126 return time.Second * time.Duration((expires-now)/int64(time.Second)), nil 127 } 128 129 // Set defines data in Redis for given key identifier 130 func (s *BadgerStore) Set(key string, value interface{}, options *Options) error { 131 if options == nil { 132 options = s.options 133 } 134 135 err := s.client.Update(func(txn *badger.Txn) (err error) { 136 if options.Expiration <= 0 { 137 err = txn.Set(f.Bytes(key), value.([]byte)) 138 } else { 139 err = txn.SetEntry(&badger.Entry{ 140 Key: f.Bytes(key), 141 Value: value.([]byte), 142 ExpiresAt: uint64(time.Now().Add(options.Expiration).Unix()), 143 }) 144 } 145 return err 146 }) 147 148 if err != nil { 149 return err 150 } 151 152 if tags := options.TagsValue(); len(tags) > 0 { 153 s.setTags(key, tags) 154 } 155 156 return nil 157 } 158 159 func (s *BadgerStore) setTags(key string, tags []string) { 160 for _, tag := range tags { 161 var tagKey = fmt.Sprintf(BadgerTagPattern, tag) 162 var cacheKeys []string 163 164 if result, err := s.Get(tagKey); err == nil { 165 if bytes, ok := result.([]byte); ok { 166 cacheKeys = strings.Split(string(bytes), ",") 167 } 168 } 169 170 var alreadyInserted = false 171 for _, cacheKey := range cacheKeys { 172 if cacheKey == key { 173 alreadyInserted = true 174 break 175 } 176 } 177 178 if !alreadyInserted { 179 cacheKeys = append(cacheKeys, key) 180 } 181 182 _ = s.Set(tagKey, []byte(strings.Join(cacheKeys, ",")), &Options{ 183 Expiration: 720 * time.Hour, 184 }) 185 } 186 } 187 188 // Delete removes data from Redis for given key identifier 189 func (s *BadgerStore) Delete(key string) error { 190 return s.client.Update(func(txn *badger.Txn) error { 191 return txn.Delete(f.Bytes(key)) 192 }) 193 } 194 195 // Invalidate invalidates some cache data in Redis for given options 196 func (s *BadgerStore) Invalidate(options InvalidateOptions) error { 197 if tags := options.TagsValue(); len(tags) > 0 { 198 for _, tag := range tags { 199 var tagKey = fmt.Sprintf(BadgerTagPattern, tag) 200 result, err := s.Get(tagKey) 201 if err != nil { 202 return nil 203 } 204 205 var cacheKeys []string 206 if bytes, ok := result.([]byte); ok { 207 cacheKeys = strings.Split(string(bytes), ",") 208 } 209 210 for _, cacheKey := range cacheKeys { 211 _ = s.Delete(cacheKey) 212 } 213 } 214 } 215 216 return nil 217 } 218 219 // Clear resets all data in the store. 220 func (s *BadgerStore) Clear() error { 221 return s.client.DropAll() 222 } 223 224 // Close releases all db resources. 225 func (s *BadgerStore) Close() error { 226 return s.client.Close() 227 } 228 229 // Backup backup all data in the dir. 230 func (s *BadgerStore) Backup(dir string) error { 231 p := filepath.Join(dir, fmt.Sprintf("%s.bak", f.TimeFrom(time.Now(), true))) 232 w, err := os.Open(p) 233 if err != nil { 234 return err 235 } 236 _, err = s.client.Backup(w, 0) 237 return err 238 } 239 240 // GetType returns the store type 241 func (s *BadgerStore) GetType() string { 242 return BadgerType 243 }