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  }