cache

package module
v0.2.5 Latest Latest
Warning

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

Go to latest
Published: Mar 30, 2022 License: Apache-2.0 Imports: 15 Imported by: 1

README

HybridCache

A multi-level cache library with cache stampede prevention for Go

import "github.com/cshum/hybridcache"

// Redis cache adapter based on Redigo
redisCache := cache.NewRedis(&redis.Pool{...})

// Memory cache adapter based on Ristretto
memoryCache := cache.NewMemory(1e5, 1<<29, time.Hour)
// bounded maximum ~100,000 items, ~500mb memory size, 1 hour ttl

// Hybrid cache adapter with Redis upstream + Memory downstream
hybridCache := cache.NewHybrid(redisCache, memoryCache)

The hybrid combination allows Redis upstream coordinate across multiple servers, while Memory downstream ensures minimal network I/O which brings the fastest response time. Shall the Redis upstream failed, memory downstream will still operate independently without service disruption.

// cache function client
cacheFunc := cache.NewFunc(hybridCache, time.Seconds*20, time.Minute, time.Hour)
// 20 seconds execution timeout, 1 minute fresh-for timeout, 1 hour ttl

var items []*Items
someKey := fmt.Sprintf("key-%d", id)
// wrap function call with hybrid cache
if err := cacheFunc.Do(ctx, someKey, func(ctx context.Context) (interface{}, error) {
	return someHeavyOperations(ctx, id)
}, &items); err != nil {
	return err
}
for _, item := range items {
	...
}

// cache http client
cacheHTTP := cache.NewHTTP(hybridCache, time.Seconds*30, time.Minute, time.Hour*12)
// 30 seconds request timeout, 1 minute fresh-for timeout, 12 hour ttl
// setting a high ttl will enable "always online" in case of service disruption.
// content will lazy refresh in background (goroutine) after fresh-for timeout

r := mux.NewRouter()
// use as an HTTP middleware
r.Use(cacheHTTP.Handler)
r.Mount("/", myWebServices)
http.ListenAndServe(":3001", r)

// wraps a http.RoundTripper
cachedTransport := cacheHTTP.RoundTripper(http.DefaultTransport)

A simple function wrapper or HTTP middleware gives you under the hood:

  • Lazy background refresh with timeout - after fresh-for timeout exceeded, the next cache hit will trigger a refresh in goroutine, where context deadline is detached from parent and based on wait-for timeout.
  • Cache stampede prevention - uses singleflight for memory call suppression and SEX NX for redis.
  • Marshal and unmarshal options for function calls - default to msgpack, with options to configure your own.

Conditional caching with cache.ErrNoCache:

var items []*Items
someKey := fmt.Sprintf("key-%d", id)
if err := cacheFunc.Do(ctx, someKey, func(ctx context.Context) (interface{}, error) {
	start := time.Now()
	items, err := someHeavyOperations(ctx, id)
	if err != nil {
		return nil, err
	}
	if time.Since(start) < time.Millisecond*300 {
		// no cache if response within 300 milliseconds
		return items, cache.ErrNoCache
	}
	return items, nil
}, &items); err != nil {
	// ErrNoCache will NOT appear outside
	return err
}

More options:

cacheFunc := cache.NewFunc(hybridCache, time.Seconds*20, time.Minute, time.Hour)
cacheFunc.Marshal = json.Marshal
cacheFunc.Unmarshal = json.Unmarshal
// custom Marshal Unmarshal function, default msgpack


h := cache.NewHTTP(hybridCache, time.Seconds*30, time.Minute, time.Hour*12)
h.RequestKey = func(r *http.Request) string {
	// cache key by url excluding query params
	return strings.Split(r.URL.String(), "?")[0]
}
h.AcceptRequest = func(r *http.Request) bool {
	if strings.Contains(r.URL.RawQuery, "nocache") {
		// no cache if nocache appears in query
		return false
	}
	return true
}
h.AcceptResponse = func(res *http.Response) bool {
	if res.StatusCode != http.StatusOK {
		// no cache if status code not 200
		return false
	}
	if res.ContentLength >= 1<<20 {
		// no cache if over 1 MB
		return false
	}
	return true
}
cacheHandler := h.Handler

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrNoCache = errors.New("hybridcache: no cache")

ErrNoCache denotes result should not be cached, does not result an error to the endpoint

View Source
var ErrNotFound = errors.New("hybridcache: not found")

ErrNotFound result not found

Functions

func DetachContext

func DetachContext(ctx context.Context) context.Context

DetachContext returns a context that keeps all the values of its parent context but detaches from cancellation and timeout

func IsDetached

func IsDetached(ctx context.Context) bool

IsDetached returns if context is detached

Types

type Cache

type Cache interface {
	// Get value by key that prioritize quick access over freshness
	Get(key string) (value []byte, err error)

	// Fetch the freshest value with its remaining ttl by key
	Fetch(key string) (value []byte, ttl time.Duration, err error)

	// Set value and ttl by key
	Set(key string, value []byte, ttl time.Duration) error

	// Del deletes items from the cache by keys
	Del(keys ...string) error

	// Clear the cache
	Clear() error

	// Close releases the resources used by the cache
	Close() error

	// Race executes and returns the given function once
	// under specified timeout, suppressing multiple calls of the same key.
	// If a duplicate comes in, the duplicate caller waits for the
	// original to complete and receives the same results.
	Race(key string, fn func() ([]byte, error), waitFor, ttl time.Duration) ([]byte, error)
}

Cache interface for cache adaptor

type Func

type Func struct {
	// Cache adapter
	Cache Cache

	// WaitFor execution timeout for the function call
	WaitFor time.Duration

	// FreshFor best-before duration of cache before the next refresh
	FreshFor time.Duration

	// TTL duration for cache to stay
	TTL time.Duration

	// custom Marshal function, default msgpack
	Marshal func(interface{}) ([]byte, error)

	// custom Unmarshal function, default msgpack
	Unmarshal func([]byte, interface{}) error
}

Func cache client that wraps arbitrary functions

func NewFunc

func NewFunc(c Cache, waitFor, freshFor, ttl time.Duration) *Func

NewFunc creates cache function client with options:

waitFor execution timeout,
freshFor fresh duration until next refresh,
ttl cache time-to-live

func (Func) Do

func (f Func) Do(
	ctx context.Context, key string,
	fn func(context.Context) (interface{}, error),
	v interface{},
) (err error)

Do wraps and returns the result of the given function value pointed to by v.

fn to return error ErrNoCache tells client not to cache the result but will not result an error

func (Func) DoBytes

func (f Func) DoBytes(
	ctx context.Context, key string,
	fn func(context.Context) ([]byte, error),
) (value []byte, err error)

DoBytes wraps and returns the bytes result of the given function.

fn to return error ErrNoCache tells client not to cache the result but will not result an error

type HTTP

type HTTP struct {
	// Cache adapter
	Cache Cache

	// WaitFor request timeout
	WaitFor time.Duration

	// FreshFor best-before duration of cache before the next refresh
	FreshFor time.Duration

	// TTL duration for cache to stay
	TTL time.Duration

	// RequestKey function generates string key from incoming request
	//
	// by default request URL is used as key
	RequestKey func(*http.Request) string

	// AcceptRequest optional function determine request should be handled
	//
	// by default only GET requests are handled
	AcceptRequest func(*http.Request) bool

	// AcceptResponse function determine response should be cached
	//
	// by default only status code < 400 response are cached
	AcceptResponse func(*http.Response) bool

	// ErrorHandler function handles errors
	//
	// by default context deadline will result 408 error, 400 error for anything else
	ErrorHandler func(http.ResponseWriter, *http.Request, error)

	// Transport the http.RoundTripper to wrap. Defaults to http.DefaultTransport
	Transport http.RoundTripper
}

HTTP cache client as an HTTP middleware

func NewHTTP

func NewHTTP(c Cache, waitFor, freshFor, ttl time.Duration) *HTTP

NewHTTP creates cache HTTP middleware client with options:

waitFor request timeout,
freshFor fresh duration until next refresh,
ttl cache time-to-live

func (HTTP) Handler

func (h HTTP) Handler(next http.Handler) http.Handler

Handler is the HTTP cache middleware handler

func (HTTP) RoundTrip added in v0.2.1

func (h HTTP) RoundTrip(r *http.Request) (*http.Response, error)

RoundTrip implements http.RoundTripper

func (HTTP) RoundTripper added in v0.2.1

func (h HTTP) RoundTripper(transport http.RoundTripper) http.RoundTripper

RoundTripper wraps and returns a http.RoundTripper for cache

type Hybrid

type Hybrid struct {
	Upstream   Cache
	Downstream Cache
}

Hybrid cache adaptor based on Upstream and Downstream cache adaptors

func NewHybrid

func NewHybrid(upstream, downstream Cache) *Hybrid

NewHybrid creates Hybrid cache from upstream and downstream

func (*Hybrid) Clear added in v0.1.1

func (c *Hybrid) Clear() error

Clear implements the Clear method

func (*Hybrid) Close added in v0.1.1

func (c *Hybrid) Close() error

Close implements the Close method

func (*Hybrid) Del added in v0.1.1

func (c *Hybrid) Del(keys ...string) error

Del implements the Del method

func (*Hybrid) Fetch

func (c *Hybrid) Fetch(key string) (value []byte, ttl time.Duration, err error)

Fetch from upstream and then sync value by Set downstream value the remaining ttl

func (*Hybrid) Get

func (c *Hybrid) Get(key string) (value []byte, err error)

Get value by key from downstream, otherwise Fetch from upstream

func (*Hybrid) Race

func (c *Hybrid) Race(
	key string, fn func() ([]byte, error), timeout, ttl time.Duration,
) ([]byte, error)

Race implements the Race method by first acquiring downstream and then upstream

func (*Hybrid) Set

func (c *Hybrid) Set(key string, value []byte, ttl time.Duration) error

Set implements the Set method

type Memory

type Memory struct {

	// Cache ristretto in-memory cache
	Cache *ristretto.Cache

	// MaxTTL bounded maximum ttl
	MaxTTL time.Duration
	// contains filtered or unexported fields
}

Memory cache adaptor based on ristretto

func NewMemory

func NewMemory(maxItems, maxSize int64, maxTTL time.Duration) *Memory

NewMemory creates an in-memory cache with an upper bound for maxItems total number of items, maxSize total byte size maxTTL max ttl of each item

func (*Memory) Clear added in v0.1.1

func (c *Memory) Clear() error

Clear implements the Clear method

func (*Memory) Close added in v0.1.1

func (c *Memory) Close() error

Close implements the Close method

func (*Memory) Del added in v0.1.1

func (c *Memory) Del(keys ...string) error

Del implements the Del method

func (*Memory) Fetch

func (c *Memory) Fetch(key string) (value []byte, ttl time.Duration, err error)

Fetch get value and remaining ttl by key

func (*Memory) Get

func (c *Memory) Get(key string) ([]byte, error)

Get implements the Get method

func (*Memory) Race

func (c *Memory) Race(
	key string, fn func() ([]byte, error), _, _ time.Duration,
) ([]byte, error)

Race implements the Race method using singleflight

func (*Memory) Set

func (c *Memory) Set(key string, value []byte, ttl time.Duration) error

Set implements the Set method

type Redis

type Redis struct {
	// Pool redigo redis pool
	Pool *redis.Pool

	// Prefix of key
	Prefix string

	// LockPrefix prefix of lock key, default "!lock!"
	LockPrefix string

	// DelayFunc is used to decide the amount of time to wait between lock retries.
	DelayFunc func(tries int) time.Duration

	// SkipLock skips redis lock that manages call suppression for Race method,
	// which result function to be executed immediately.
	// This will skip the extra cost of redis lock, if you do not need suppression
	// across multiple servers
	SkipLock bool
}

Redis cache adaptor based on redigo

func NewRedis

func NewRedis(pool *redis.Pool) *Redis

NewRedis creates redis cache from redigo redis pool

func (*Redis) Clear added in v0.1.1

func (c *Redis) Clear() (err error)

Clear implements the Clear method by SCAN keys under prefix and batched DEL

func (*Redis) Close added in v0.1.1

func (c *Redis) Close() error

Close implements the Close method

func (*Redis) Del added in v0.1.1

func (c *Redis) Del(keys ...string) error

Del implements the Del method

func (*Redis) Fetch

func (c *Redis) Fetch(key string) (value []byte, ttl time.Duration, err error)

Fetch get value and remaining ttl by key

func (*Redis) Get

func (c *Redis) Get(key string) (res []byte, err error)

Get implements the Get method

func (*Redis) Race

func (c *Redis) Race(
	key string, fn func() ([]byte, error), waitFor, ttl time.Duration,
) (value []byte, err error)

Race implements the Race method using SEX NX

func (*Redis) Set

func (c *Redis) Set(key string, value []byte, ttl time.Duration) error

Set implements the Set method

Jump to

Keyboard shortcuts

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