cache

package module
v0.0.0-...-f53fec1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 3, 2023 License: BSD-3-Clause Imports: 23 Imported by: 3

README

go-cache - ARCHIVED

This public repository has been forked to BoseCorp/go-cache-lib private repository.

GenericCache allows you to interface with cache via either one write/read connection (pool) or two separate write and read connections (pools). Once initialized the GenericCache will just do the right thing when interacting with the pools, based on what you're trying to do.

GenericCache (implements aninterface) which makes it compatible with persistent caches included in gin-contrib/cache. Also, a GenericCache can also be used to cache results from a Gin application.

The current list of persistent caches from gin-contrib includes:

  • Redis
  • In memory
  • memcached

Redis Pools

This package also includes factories to create Redis pools.

  • InitRedisCache: creates an interface to a Redis master via a Sentinel pool.
  • InitReadOnlyRedisCache: creates an interface to a read-only Redis pool.

Encrypting Cache Entries

The GenericCache supports using symmetrical signatures for cache entry keys and symmetrical encryption for storing/retrieving entry data. Once the cache is initialized, these crypto operations are very transparent, requiring to intervention or knowledge to utilize.

In Memory LRU with Expiry

This package includes InMemoryStore which implements an LRU cache that includes time based expiration of entries. InMemoryStore is built on top of github.com/hashicorp/golang-lru which provides an open source LRU implementation by HashiCorp, and this package adds time based expiration of entries to that implementation.

This type of InMemoryStore exports a prometheus metric gauge for the total number of entries in the store: go_cache_inmemory_cache_total_items_cnt

Installation

$ go get github.com/Bose/go-cache

You'll also want to install gin-contrib/cache If you want to use it with other peristent cache stores (in memory or memcached):

$ go get github.com/gin-contrib/cache

Benchmarks

$ ./run-benchmarks.sh 
goos: darwin
goarch: amd64
pkg: github.com/Bose/go-cache
BenchmarkGetSet1-8                     	    3000	    400840 ns/op
BenchmarkGetSet2-8                     	    5000	    400080 ns/op
BenchmarkGetSet3-8                     	    3000	    398985 ns/op
BenchmarkGetSet10-8                    	    3000	    402394 ns/op
BenchmarkGetSet20-8                    	    5000	    400553 ns/op
BenchmarkGetSet40-8                    	    5000	    415157 ns/op
BenchmarkGetSetEncrypted1-8            	    3000	    415584 ns/op
BenchmarkGetSetEncrypted2-8            	    3000	    411826 ns/op
BenchmarkGetSetEncrypted3-8            	    3000	    419667 ns/op
BenchmarkGetSetEncrypted10-8           	    3000	    421709 ns/op
BenchmarkGetSetEncrypted20-8           	    3000	    421568 ns/op
BenchmarkGetSetEncrypted40-8           	    3000	    425709 ns/op
BenchmarkGetSetInMemory1-8             	  200000	      8972 ns/op
BenchmarkGetSetInMemory2-8             	  200000	      8783 ns/op
BenchmarkGetSetInMemory3-8             	  200000	      8802 ns/op
BenchmarkGetSetInMemory10-8            	  200000	      8840 ns/op
BenchmarkGetSetInMemory20-8            	  200000	      8733 ns/op
BenchmarkGetSetInMemory40-8            	  200000	      8883 ns/op
BenchmarkGetSetEncryptedInMemory1-8    	  100000	     18686 ns/op
BenchmarkGetSetEncryptedInMemory2-8    	  100000	     18812 ns/op
BenchmarkGetSetEncryptedInMemory3-8    	  100000	     18686 ns/op
BenchmarkGetSetEncryptedInMemory10-8   	  100000	     18666 ns/op
BenchmarkGetSetEncryptedInMemory20-8   	  100000	     18646 ns/op
BenchmarkGetSetEncryptedInMemory40-8   	  100000	     18738 ns/op
PASS
ok  	github.com/Bose/go-cache	41.418s

Usage

package main

import (
	"encoding/gob"
	"os"
	"time"

	goCache "github.com/Bose/go-cache"
	ginCache "github.com/Bose/go-cache/galapagos_gin/cache"
	ginprometheus "github.com/zsais/go-gin-prometheus"

	"github.com/gin-contrib/cache/persistence"
	"github.com/gin-gonic/gin"
	"github.com/sirupsen/logrus"
)

// These tests require redis server running on localhost:6379 (the default)
const redisTestServer = "redis://localhost:6379"
const sentinelTestServer = "redis://localhost:26379" // will default to this if not set
const redisMasterIdentifier = "mymaster"             // will default to this if not set
const sharedSecret = "secret-must-be-min-16chars"
const useSentinel = true // if false, the master will default to redis:6379 if running in k8s, other it defaults to localhost:6379
const defExpSeconds = 30
const maxConnectionsAllowed = 50 // max connections allowed to the read-only redis cluster from this service
const maxEntries = 1000          // max entries allowed in the LRU + expiry in memory store

var genericRedisCache interface{}
var genericInMemoryCache interface{}
var lruExpiryCache interface{}

func init() {
	logger := logrus.WithFields(logrus.Fields{
		"requestID": "unknown",
		"method":    "generic_test",
		"path":      "none",
	})
	selectDatabase := 2                                         // select database #2 in the redis - use 0 if you're connected to a sharded cluster
	os.Setenv("REDIS_SENTINEL_ADDRESS", sentinelTestServer)     // used by InitRedisCache for the sentinel address
	os.Setenv("REDIS_MASTER_IDENTIFIER", redisMasterIdentifier) // used by InitRedisCache for the master identifier
	cacheWritePool, err := goCache.InitRedisCache(useSentinel, defExpSeconds, nil, defExpSeconds, selectDatabase, logger)
	if err != nil {
		logger.Fatalf("couldn't connect to redis on %s", redisTestServer)
	}
	logger.Info("cacheWritePool initialized")
	readOnlyPool, err := goCache.InitReadOnlyRedisCache(redisTestServer, "", defExpSeconds, maxConnectionsAllowed, selectDatabase, logger)
	if err != nil {
		logger.Fatalf("couldn't connect to redis on %s", redisTestServer)
	}
	logger.Info("cacheReadPool initialized")
	genericRedisCache = goCache.NewCacheWithMultiPools(cacheWritePool, readOnlyPool, goCache.L2, sharedSecret, defExpSeconds, []byte("test"), true)
	genericRedisCache.(*goCache.GenericCache).Logger = logger

	gob.Register(TestEntry{})

	inMemoryStore := persistence.NewInMemoryStore(defExpSeconds)
	genericInMemoryCache = goCache.NewCacheWithPool(inMemoryStore, goCache.Writable, goCache.L1, sharedSecret, defExpSeconds, []byte("mem"), false)

	inMemoryLRUWExpiryStore, err := goCache.NewInMemoryStore(maxEntries, defExpSeconds, 1*time.Minute)
	if err != nil {
		panic("Unable to allocate LRU w/expiry store")
	}
	lruExpiryCache = goCache.NewCacheWithPool(inMemoryLRUWExpiryStore, goCache.Writable, goCache.L1, sharedSecret, defExpSeconds, []byte("mem"), false)
}
func main() {
	// use the JSON formatter
	// logrus.SetFormatter(&logrus.JSONFormatter{})
	logrus.SetLevel(logrus.DebugLevel)

	r := gin.Default()
	r.Use(gin.Recovery()) // add Recovery middleware
	p := ginprometheus.NewPrometheus("go_cache_example")
	p.Use(r)

	r.GET("/hello", func(c *gin.Context) {
		c.String(200, "Hello world!\n")
	})

	r.GET("/cached-page", ginCache.CachePage(genericRedisCache.(persistence.CacheStore), 5*time.Minute, func(c *gin.Context) {
		c.Writer.Header().Set("x-okay-to-cache", "true") // signal middleware that it's okay to cache this page
		c.String(200, "Cached Hello world!\n")
	}))

	r.GET("/cached-encrypted-entry", func(c *gin.Context) {
		cache := genericRedisCache.(*goCache.GenericCache)
		key := cache.GetKey([]byte("cached-encrypted-entry")) // Data will be decrypted automatically when Getting
		entry := goCache.GenericCacheEntry{}
		err := cache.Get(key, &entry) // get a symmetrical signature to use an entry key
		if err != nil {
			logrus.Errorf("Error getting a value: %s", err)
			exp := 3 * time.Minute // override the default expiry for this entry
			entry = cache.NewGenericCacheEntry(getNewEntry(), exp)

			// why make the client wait... just do this set concurrently
			go func() {
				if err := cache.Set(key, entry, exp); err != nil { // Data will be encrypted automatically when Setting
					logrus.Errorf("Error setting a value: %s", err)
				}
			}()
		}
		c.JSON(200, gin.H{"entry": entry})
		return
	})

	r.GET("/cached-in-memory-not-encrypted-entry", func(c *gin.Context) {
		cache := genericInMemoryCache.(*goCache.GenericCache)
		key := cache.GetKey([]byte("cached-in-memory-not-encrypted-entry")) // Data will be decrypted automatically when Getting
		entry := goCache.GenericCacheEntry{}
		err := cache.Get(key, &entry) // get a symmetrical signature to use an entry key
		if err != nil {
			logrus.Errorf("Error getting a value: %s", err)
			exp := 3 * time.Minute // override the default expiry for this entry
			entry = cache.NewGenericCacheEntry(getNewEntry(), exp)

			// why make the client wait... just do this set concurrently
			go func() {
				if err := cache.Set(key, entry, exp); err != nil { // Data will be encrypted automatically when Setting
					logrus.Errorf("Error setting a value: %s", err)
				}
			}()
		}
		c.JSON(200, gin.H{"entry": entry})
		return
	})

	r.GET("/cached-in-memory-lru-with-expiry", func(c *gin.Context) {
		cache := lruExpiryCache.(*goCache.GenericCache)
		key := cache.GetKey([]byte("cached-in-memory-lru-with-expiry")) // Data will be decrypted automatically when Getting
		entry := goCache.GenericCacheEntry{}
		err := cache.Get(key, &entry) // get a symmetrical signature to use an entry key
		if err != nil {
			logrus.Errorf("Error getting a value: %s", err)
			exp := 3 * time.Minute // override the default expiry for this entry
			entry = cache.NewGenericCacheEntry(getNewEntry(), exp)

			// why make the client wait... just do this set concurrently
			go func() {
				if err := cache.Set(key, entry, exp); err != nil { // Data will be encrypted automatically when Setting
					logrus.Errorf("Error setting a value: %s", err)
				}
			}()
		}
		c.JSON(200, gin.H{"entry": entry})
		return
	})

	r.Run(":9090")
}

// TestEntry - just a sampe of what you can store in a GenericCacheEntry
type TestEntry struct {
	TestValue string
	TestBool  bool
}

func getNewEntry() TestEntry {
	return TestEntry{TestValue: "Hi Mom!", TestBool: true}
}

See also:

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrPaddingSize - represents padding errors
	ErrPaddingSize = errors.New("padding size error")
)
View Source
var (
	// PKCS5 represents pkcs5 struct
	PKCS5 = &pkcs5{}
)
View Source
var (
	// PKCS7 - difference with pkcs5 only block must be 8
	PKCS7 = &pkcs5{}
)

Functions

func InitReadOnlyRedisCache

func InitReadOnlyRedisCache(
	readOnlyCacheURL string,
	cachePassword string,
	connectionTimeoutMilliseconds int,
	readWriteTimeoutMilliseconds int,
	defaultExpMinutes int,
	maxConnections int,
	selectDatabase int,
	logger *logrus.Entry) (interface{}, error)

InitReadOnlyRedisCache - used by microservices to init their read-only redis cache. Returns an interface{} so other caches could be swapped in

func InitRedisCache

func InitRedisCache(
	useSentinel bool,
	defaultExpSeconds int,
	redisPassword []byte,
	connectionTimeoutMilliseconds int,
	readWriteTimeoutMilliseconds int,
	selectDatabase int,
	logger *logrus.Entry) (interface{}, error)

InitRedisCache - used by microservices to init their redis cache. Returns an interface{} so other caches could be swapped in - this uses ENV vars to figure out what to connect to. REDIS_MASTER_IDENTIFIER, REDIS_PASSWORD, REDIS_SENTINEL_ADDRESS

func NewSentinelPool

func NewSentinelPool(
	sentinelAddrs []string,
	masterIdentifier []byte,
	redisPassword []byte,
	connectionTimeoutMilliseconds int,
	readWriteTimeoutMilliseconds int,
	selectDatabase int,
	logger *logrus.Entry) *redis.Pool

NewSentinelPool - create a new pool for Redis Sentinel

Types

type GenericCache

type GenericCache struct {
	Cache     interface{}
	ReadCache *GenericCache

	DefaultExp time.Duration

	KeyPrefix   []byte
	Logger      *logrus.Entry
	EncryptData bool
	// contains filtered or unexported fields
}

GenericCache - represents the cache

  • Cache: empty interface to a persistent cache pool (could be writable if cType == Writable)
  • ReadCache: empty interface to a persistent read-only cache pool (cType == ReadOnly)
  • sharedSecret: used by GetKey() for generating signatures to be used as an entries primary key
  • DefaultExp: the default expiry for entries
  • cType: Writable or ReadOnly
  • cLevel: L1 (level 1) or L2 (level 2)
  • KeyPrefix: a prefex added to each key that's generated by GetKey()
  • Logger: the logger to use when writing logs

func NewCacheWithMultiPools

func NewCacheWithMultiPools(writeCachePool interface{}, readCachePool interface{}, cLevel Level, sharedSecret string, expirySeconds int, keyPrefix []byte, encryptData bool) *GenericCache

NewCacheWithMultiPools - creates a new generic cache for microservices using two Pools. One pool for writes and a separate pool for reads

func NewCacheWithPool

func NewCacheWithPool(cachePool interface{}, cType Type, cLevel Level, sharedSecret string, expirySeconds int, keyPrefix []byte, encryptData bool) *GenericCache

NewCacheWithPool - creates a new generic cache for microservices using a Pool for connecting (this cache should be read/write)

func (*GenericCache) Add

func (c *GenericCache) Add(key string, data interface{}, exp time.Duration) (err error)

Add - adds an entry to the cache

func (*GenericCache) AddExistingEntry

func (c *GenericCache) AddExistingEntry(key string, entry GenericCacheEntry, expiresAt int64) error

AddExistingEntry -

func (*GenericCache) Decrement

func (c *GenericCache) Decrement(key string, n uint64) (newValue uint64, err error)

Decrement - Decrement an entry in the cache

func (*GenericCache) Delete

func (c *GenericCache) Delete(key string) (err error)

Delete - deletes an entry in the cache

func (*GenericCache) Exists

func (c *GenericCache) Exists(key string) (found bool, entry GenericCacheEntry, err error)

Exists - searches the cache for an entry

func (*GenericCache) Flush

func (c *GenericCache) Flush() error

Flush - Flush all the keys in the cache

func (*GenericCache) Get

func (c *GenericCache) Get(key string, value interface{}) error

Get - retrieves and entry from the cache

func (*GenericCache) GetKey

func (c *GenericCache) GetKey(entryData []byte) string

GetKey - return a key for the entryData

func (*GenericCache) Increment

func (c *GenericCache) Increment(key string, n uint64) (newValue uint64, err error)

Increment - Increment an entry in the cache

func (*GenericCache) NewGenericCacheEntry

func (c *GenericCache) NewGenericCacheEntry(data interface{}, exp time.Duration) GenericCacheEntry

NewGenericCacheEntry creates an entry with the data and all the time attribs set

func (*GenericCache) RedisExpireAt

func (c *GenericCache) RedisExpireAt(key string, epoc uint64) error

RedisExpireAt - get the TTL of an entry

func (*GenericCache) RedisGetExpiresIn

func (c *GenericCache) RedisGetExpiresIn(key string) (int64, error)

func (*GenericCache) RedisIncrementAtomic

func (c *GenericCache) RedisIncrementAtomic(key string, n uint64) (newValue uint64, err error)

RedisIncrementAtomic - Increment an entry in the cache

func (*GenericCache) RedisIncrementCheckSet

func (c *GenericCache) RedisIncrementCheckSet(key string, n uint64) (newValue uint64, err error)

RedisIncrementCheckSet - Increment an entry in the cache

func (*GenericCache) Replace

func (c *GenericCache) Replace(key string, data interface{}, exp time.Duration) (err error)

Replace - Replace an entry in the cache

func (*GenericCache) Set

func (c *GenericCache) Set(key string, data interface{}, exp time.Duration) (err error)

Set - Set a key in the cache (over writting any existing entry)

type GenericCacheEntry

type GenericCacheEntry struct {
	Data      interface{}
	TimeAdded int64
	ExpiresAt int64
}

GenericCacheEntry - represents a cached entry...

  • Data: the entries data represented as an empty interface
  • TimeAdded: epoc at the time of addtion
  • ExpiresAd: epoc at the time of expiry

func (*GenericCacheEntry) Expired

func (e *GenericCacheEntry) Expired() bool

Expired - is the entry expired?

type InMemoryStore

type InMemoryStore struct {
	// contains filtered or unexported fields
}

InMemoryStore - an in memory LRU store with expiry

func NewInMemoryStore

func NewInMemoryStore(maxEntries int, defaultExpiration, cleanupInterval time.Duration, createMetric bool, metricLabel string) (*InMemoryStore, error)

NewInMemoryStore - create a new in memory cache

func (*InMemoryStore) Add

func (c *InMemoryStore) Add(key string, value interface{}, exp time.Duration) error

Add - add an entry

func (*InMemoryStore) Decrement

func (c *InMemoryStore) Decrement(key string, n uint64) (uint64, error)

Decrement (see CacheStore interface)

func (*InMemoryStore) Delete

func (c *InMemoryStore) Delete(key string) error

Delete - delete an entry

func (InMemoryStore) DeleteExpired

func (c InMemoryStore) DeleteExpired()

DeleteExpired - Delete all expired items from the cache.

func (*InMemoryStore) Flush

func (c *InMemoryStore) Flush() error

Flush (see CacheStore interface)

func (*InMemoryStore) Get

func (c *InMemoryStore) Get(key string, value interface{}) error

Get - Get an entry

func (*InMemoryStore) Increment

func (c *InMemoryStore) Increment(key string, n uint64) (uint64, error)

Increment (see CacheStore interface)

func (*InMemoryStore) Keys

func (c *InMemoryStore) Keys() []interface{}

Keys - get all the keys

func (*InMemoryStore) Len

func (c *InMemoryStore) Len() int

Len - get the current count of entries in the cache

func (*InMemoryStore) NewGenericCacheEntry

func (c *InMemoryStore) NewGenericCacheEntry(data interface{}, exp time.Duration) (newEntry GenericCacheEntry, err error)

NewGenericCacheEntry - create a new in memory cache entry

func (*InMemoryStore) Replace

func (c *InMemoryStore) Replace(key string, value interface{}, exp time.Duration) error

Replace - replace an entry

func (*InMemoryStore) Set

func (c *InMemoryStore) Set(key string, value interface{}, exp time.Duration) error

Set - set an entry

func (*InMemoryStore) Update

func (c *InMemoryStore) Update(key string, entry GenericCacheEntry) error

Update - update an entry

type Level

type Level int

Level - define a level for the cache: L1, L2, etc

const (
	// L1 ...
	L1 Level = iota + 1
	// L2 ...
	L2
)

type RedisConnectionInfo

type RedisConnectionInfo struct {
	MasterIdentifier              string // Redis Master identifieer - should come out of ENV from DBaaS and  can be 0 len string (defaults to mymaster)
	Password                      string // Redis password - should be defined via an ENV and should default to 0 len string for no AUTH to Redis
	SentinelURL                   string // Sentinel URL - should be defined via an ENV from DBaaS and can be 0 len string when you don't want to use sentinel
	RedisURL                      string // Redis URL to use if we're not using sentinel
	UseSentinel                   bool   // should be set via a config var - turn on/off sentinel use
	DefaultExpSeconds             int    // default expiry for redis entries - should be set via config
	ConnectionTimeoutMilliseconds int    // Redis connection timeout - should be set via config
	ReadWriteTimeoutMilliseconds  int    // Redis read/write timeout - should be set via config
	SelectDatabase                int    // which Redis database to use - should be set via config
}

RedisConnectionInfo - define all the things needed to manage a connection to redis (optionally using sentinel)

func (*RedisConnectionInfo) New

func (connInfo *RedisConnectionInfo) New(testWriteRead bool, logger *logrus.Entry) (interface{}, error)

New - Make a new cache interface using a RedisConnectionInfo for setup - it doesn't not rely on the ENV at all

type Type

type Type int

Type - define the type of cache: read or write

const (
	// ReadOnly ...
	ReadOnly Type = iota
	// Writable ...
	Writable
)

Directories

Path Synopsis
galapagos_gin

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL