github.com/angenalZZZ/gofunc@v0.0.0-20210507121333-48ff1be3917b/data/cache/store/nutsdb.go (about) 1 package store 2 3 import ( 4 "errors" 5 "fmt" 6 "path/filepath" 7 "strings" 8 "time" 9 10 "github.com/angenalZZZ/gofunc/f" 11 "github.com/xujiajun/nutsdb" 12 ) 13 14 // Ndb global nutsdb client 15 var Ndb *nutsdb.DB 16 17 const ( 18 // NdbType represents the storage type as a string value 19 NdbType = "ndb" 20 // NdbTagPattern represents the tag pattern to be used as a key in specified storage 21 NdbTagPattern = "gocache_tag_%s" 22 ) 23 24 // NdbStore is a store for nutsdb 25 type NdbStore struct { 26 // ClientInterface represents github.com/xujiajun/nutsdb client 27 client *nutsdb.DB 28 options *Options 29 bucket string 30 } 31 32 // OpenNdb creates a new store to nutsdb client. 33 func OpenNdb(path ...string) (*nutsdb.DB, error) { 34 opt := nutsdb.DefaultOptions 35 36 if len(path) > 0 && path[0] != "" { 37 opt.Dir = path[0] 38 } else { 39 opt.Dir = filepath.Join(f.CurrentDir(), ".nutsdb") 40 } 41 42 // EntryIdxMode 代表索引entry的模式. 选项: HintKeyValAndRAMIdxMode、HintKeyAndRAMIdxMode和HintBPTSparseIdxMode 43 // 其中 HintKeyValAndRAMIdxMode 代表纯内存索引模式(key和value都会被cache) 44 // 其中 HintKeyAndRAMIdxMode 代表内存+磁盘的索引模式(只有key被cache) 45 // 其中 HintBPTSparseIdxMode 是专门节约内存的设计方案,单机10亿条数据,只要80几M内存。但是读性能不高,需要自己加缓存来加速 46 opt.EntryIdxMode = nutsdb.HintKeyValAndRAMIdxMode 47 // RWMode 代表读写模式. RWMode 包括两种选项: FileIO and MMap. FileIO 用标准的 I/O读写。 MMap 代表使用mmap进行读写 48 opt.RWMode = nutsdb.FileIO 49 // SegmentSize 代表数据库的数据单元,每个数据单元(文件)为SegmentSize 50 // 现在默认是8 MB,这个可以自己配置。但是一旦被设置,下次启动数据库也要用这个配置,不然会报错 51 opt.SegmentSize = 8 * 1024 * 1024 52 // NodeNum:1 代表节点的号码,取值范围 [1,1023] 53 opt.NodeNum = 1 54 // SyncEnable:false 写性能会很高,但是如果遇到断电或者系统奔溃,会有数据丢失的风险 55 // SyncEnable:true 写性能会相比false的情况慢很多,但是数据更有保障,每次事务提交成功都会落盘 56 opt.SyncEnable = false // ***此选项与DefaultOptions不同*** 57 // StartFileLoadingMode 代表启动数据库的载入文件的方式。参数选项同RWMode 58 opt.StartFileLoadingMode = nutsdb.MMap 59 60 return nutsdb.Open(opt) 61 } 62 63 // NewNdb creates a new store to nutsdb instance(s) 64 func NewNdb(option *Options, bucketAndPath ...string) *NdbStore { 65 if option == nil { 66 option = &Options{} 67 } 68 69 bucket, l := "default", len(bucketAndPath) 70 if l == 1 { 71 bucket = bucketAndPath[0] 72 } 73 74 dir := "" 75 if l == 2 { 76 dir = bucketAndPath[1] 77 } 78 79 client, err := OpenNdb(dir) 80 if err != nil { 81 return nil 82 } 83 84 return &NdbStore{ 85 client: client, 86 options: option, 87 bucket: bucket, 88 } 89 } 90 91 // Get returns data stored from a given key 92 func (s *NdbStore) Get(key string) (interface{}, error) { 93 return s.GetBy(key, s.bucket) 94 } 95 96 // GetBy returns data stored from a given key 97 func (s *NdbStore) GetBy(key string, bucket string) (interface{}, error) { 98 var data []byte 99 err := s.client.View(func(tx *nutsdb.Tx) error { 100 item, err := tx.Get(bucket, f.Bytes(key)) 101 if err != nil { 102 return err 103 } 104 data = item.Value 105 return nil 106 }) 107 return data, err 108 } 109 110 // TTL returns a expiration time 111 func (s *NdbStore) TTL(key string) (time.Duration, error) { 112 return s.TTLby(key, s.bucket) 113 } 114 115 // TTLby returns a expiration time 116 func (s *NdbStore) TTLby(key string, bucket string) (time.Duration, error) { 117 var expires int64 118 119 _ = s.client.View(func(tx *nutsdb.Tx) error { 120 item, err := tx.Get(bucket, f.Bytes(key)) 121 if err != nil { 122 expires = -2 123 return nil 124 } 125 126 exp := item.Meta.TTL 127 if exp == 0 { 128 expires = -1 129 return nil 130 } 131 132 expires = int64(exp) 133 return nil 134 }) 135 136 if 0 >= expires { 137 return -2, errors.New("unable to retrieve data from nutsdb") 138 } 139 140 return time.Second * time.Duration(expires), nil 141 } 142 143 // Set defines data in nutsdb for given key identifier 144 func (s *NdbStore) Set(key string, value interface{}, options *Options) error { 145 return s.SetBy(key, value, s.bucket, options) 146 } 147 148 // SetBy defines data in nutsdb for given key identifier 149 func (s *NdbStore) SetBy(key string, value interface{}, bucket string, options *Options) error { 150 if options == nil { 151 options = s.options 152 } 153 154 err := s.client.Update(func(tx *nutsdb.Tx) (err error) { 155 if options.Expiration <= 0 { 156 err = tx.Put(bucket, f.Bytes(key), value.([]byte), 0) 157 } else { 158 err = tx.Put(bucket, f.Bytes(key), value.([]byte), uint32(options.Expiration.Seconds())) 159 } 160 return err 161 }) 162 163 if err != nil { 164 return err 165 } 166 167 if tags := options.TagsValue(); len(tags) > 0 { 168 s.setTags(key, bucket, tags) 169 } 170 171 return nil 172 } 173 174 func (s *NdbStore) setTags(key string, bucket string, tags []string) { 175 for _, tag := range tags { 176 var tagKey = fmt.Sprintf(NdbTagPattern, tag) 177 var cacheKeys []string 178 179 if result, err := s.GetBy(tagKey, bucket); err == nil { 180 if bytes, ok := result.([]byte); ok { 181 cacheKeys = strings.Split(string(bytes), ",") 182 } 183 } 184 185 var alreadyInserted = false 186 for _, cacheKey := range cacheKeys { 187 if cacheKey == key { 188 alreadyInserted = true 189 break 190 } 191 } 192 193 if !alreadyInserted { 194 cacheKeys = append(cacheKeys, key) 195 } 196 197 _ = s.SetBy(tagKey, []byte(strings.Join(cacheKeys, ",")), bucket, &Options{ 198 Expiration: 720 * time.Hour, 199 }) 200 } 201 } 202 203 // Delete removes data from nutsdb for given key identifier 204 func (s *NdbStore) Delete(key string) error { 205 return s.DeleteBy(key, s.bucket) 206 } 207 208 // DeleteBy removes data from nutsdb for given key identifier 209 func (s *NdbStore) DeleteBy(key string, bucket string) error { 210 return s.client.Update(func(tx *nutsdb.Tx) error { 211 return tx.Delete(bucket, f.Bytes(key)) 212 }) 213 } 214 215 // Invalidate invalidates some cache data in nutsdb for given options 216 func (s *NdbStore) Invalidate(options InvalidateOptions) error { 217 return s.InvalidateBy(s.bucket, options) 218 } 219 220 // InvalidateBy invalidates some cache data in nutsdb for given options 221 func (s *NdbStore) InvalidateBy(bucket string, option InvalidateOptions) error { 222 if tags := option.TagsValue(); len(tags) > 0 { 223 for _, tag := range tags { 224 var tagKey = fmt.Sprintf(NdbTagPattern, tag) 225 result, err := s.GetBy(tagKey, bucket) 226 if err != nil { 227 return nil 228 } 229 230 var cacheKeys []string 231 if bytes, ok := result.([]byte); ok { 232 cacheKeys = strings.Split(string(bytes), ",") 233 } 234 235 for _, cacheKey := range cacheKeys { 236 _ = s.DeleteBy(cacheKey, bucket) 237 } 238 } 239 } 240 241 return nil 242 } 243 244 // Search keys with prefix and handle them. 245 func (s *NdbStore) Search(prefix string, handle func(key string, value []byte, ttl uint32) error) error { 246 return s.SearchBy(s.bucket, prefix, handle) 247 } 248 249 // SearchBy keys with prefix and handle them. 250 func (s *NdbStore) SearchBy(bucket string, prefix string, handle func(key string, value []byte, ttl uint32) error) error { 251 if prefix == "" || prefix == "*" { 252 return s.client.View(func(tx *nutsdb.Tx) error { 253 items, err := tx.GetAll(bucket) 254 if err != nil { 255 return err 256 } 257 for _, item := range items { 258 err = handle(f.String(item.Key), item.Value, item.Meta.TTL) 259 if err != nil { 260 return err 261 } 262 } 263 return nil 264 }) 265 } 266 return s.client.View(func(tx *nutsdb.Tx) error { 267 items, err := tx.PrefixScan(bucket, f.Bytes(prefix), 2000) 268 if err != nil { 269 return err 270 } 271 for _, item := range items { 272 err = handle(f.String(item.Key), item.Value, item.Meta.TTL) 273 if err != nil { 274 return err 275 } 276 } 277 return nil 278 }) 279 } 280 281 // Clear resets all data in the store. 282 // 随着数据越来越多,特别是一些删除或者过期的数据占据着磁盘, 283 // 清理这些NutsDB提供了db.Merge()方法,这个方法需要自己根据实际情况编写合并策略。 284 // 一旦执行会影响到正常的写请求,所以最好避开高峰期,比如半夜定时执行等。 285 func (s *NdbStore) Clear() error { 286 return s.client.Merge() 287 } 288 289 // Close releases all db resources. 290 func (s *NdbStore) Close() error { 291 return s.client.Close() 292 } 293 294 // Backup backup all data in the dir. 295 // 数据库的备份。这个方法执行的是一个热备份,不会阻塞到数据库其他的只读事务操作,对写事务会有影响。 296 func (s *NdbStore) Backup(dir string) error { 297 return s.client.Backup(dir) 298 } 299 300 // GetType returns the store type 301 func (s *NdbStore) GetType() string { 302 return NdbType 303 }