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  }