cache

package
v1.1.3 Latest Latest
Warning

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

Go to latest
Published: Jan 23, 2022 License: Apache-2.0 Imports: 13 Imported by: 0

README

Cache

Simple cache implementation with pluggable storage; optional logging and instrumentation.

For usage examples please refer here

Redis storage

  • This library makes no effort to ensure it does not overwrite other data in the server. Key names should be chosen carefully
Tests

Tests that require Redis or DDB are protected with environment variable flags.

Use REDIS=true go test ./... to run Redis tests. (These tests assume redis running on :6379)

Use DDB=true go test ./... to run DDB tests. (These tests assume redis running on :6379)

(These tests use DynamoDbLocal and assume server running on https://s3-ap-southeast-1.amazonaws.com/dynamodb-local-singapore/release)

DynamoDB storage

  • TTL should be enabled on the table with attribute name ttl see reference

Notes:

Logging

While this package does log, it only logs asynchronous errors. Personally I would prefer not to log at all but this would leave async issues completely invisible.

That said, logging is optional.

Metrics

Metrics are provided but optional.

Documentation

Overview

Package cache please refer to README.md

Index

Examples

Constants

View Source
const (
	// CbRedisStorage is tag for redis storage circuit breaker.
	// This should be used for in calls to `hystrix.ConfigureCommand()`
	CbRedisStorage = "CbRedisStorage"

	// CbDynamoDbStorage is tag for DynamoDB storage circuit breaker.
	// This should be used for in calls to `hystrix.ConfigureCommand()`
	CbDynamoDbStorage = "CbDynamoDbStorage"
)

Variables

View Source
var ErrCacheMiss = errors.New("cache miss")

ErrCacheMiss is returned when the cache does not contain the requested key

Functions

This section is empty.

Types

type API

type API interface {
	// Get attempts to retrieve the value from cache and when it misses will run the builder func to create the value.
	Get(ctx context.Context, key string, dest BinaryEncoder, builder Builder) error

	// Set will update the cache with the supplied key/value pair
	// NOTE: generally this need not be called is it is called implicitly by Get
	Set(ctx context.Context, key string, val encoding.BinaryMarshaler)

	// Invalidate will force invalidate any matching key in the cache
	Invalidate(ctx context.Context, key string) error
}

API defines the main API for this package

type BinaryEncoder

type BinaryEncoder interface {
	encoding.BinaryMarshaler
	encoding.BinaryUnmarshaler
}

BinaryEncoder encodes/decodes the receiver to and from binary form

type Builder

type Builder interface {
	// Build returns the data for the supplied key by populating dest
	Build(ctx context.Context, key string, dest BinaryEncoder) error
}

Builder builds the data for a key

type BuilderFunc

type BuilderFunc func(ctx context.Context, key string, dest BinaryEncoder) error

BuilderFunc implements Builder as a function

func (BuilderFunc) Build

func (b BuilderFunc) Build(ctx context.Context, key string, dest BinaryEncoder) error

Build implements Builder

type Client

type Client struct {
	// Storage is the cache storage scheme. (Required)
	Storage Storage

	// Logger defines a logger to used for errors during async cache writes (optional)
	Logger Logger

	// Metrics allow for tracking cache events (hit/miss/etc) (optional)
	Metrics Metrics

	// WriteTimeout is the max time spent waiting for cache writes to complete (optional - default 3 seconds)
	WriteTimeout time.Duration
	// contains filtered or unexported fields
}

Client defines a cache instance.

This can represent the cache for the entire system or for a particular use-case/type.

If a cache is used for multiple purposes, then care must be taken to ensure uniqueness of cache keys.

It is not recommended to change this struct's member data after creation as a data race will likely ensue.

Example (HttpHandler)
package main

import (
	"context"
	"encoding/json"
	"errors"
	"net/http"
	"os"
	"time"

	"github.com/corsc/go-commons/cache"
	"github.com/garyburd/redigo/redis"
)

func main() {
	// not required during normal usage
	if os.Getenv("REDIS") == "" {
		return
	}

	// init - called once; perhaps a global variable or member variable
	userCache := &cache.Client{
		Storage: &cache.RedisStorage{
			Pool: &redis.Pool{
				MaxIdle:     3,
				IdleTimeout: 240 * time.Second,
				Dial:        func() (redis.Conn, error) { return redis.Dial("tcp", ":6379") },
			},
			TTL: 60 * time.Second,
		},
	}

	// the HTTP Handler
	handler := func(resp http.ResponseWriter, req *http.Request) {
		key := buildCacheKey(req)
		outputDTO := &myDTO{}

		err := userCache.Get(req.Context(), key, outputDTO, cache.BuilderFunc(func(ctx context.Context, key string, dest cache.BinaryEncoder) error {
			// logic that builds/marshals the cacheable value
			return errors.New("not implemented")
		}))

		if err != nil {
			http.Error(resp, err.Error(), http.StatusInternalServerError)
			return
		}

		data, err := outputDTO.MarshalBinary()
		if err != nil {
			http.Error(resp, err.Error(), http.StatusInternalServerError)
			return
		}

		resp.WriteHeader(http.StatusOK)
		_, _ = resp.Write(data)
	}

	_ = http.ListenAndServe(":8080", http.HandlerFunc(handler))
}

func buildCacheKey(_ *http.Request) string {

	return ""
}

type myDTO struct {
	Name  string
	Email string
}

// MarshalBinary implements cache.BinaryEncoder
func (m *myDTO) MarshalBinary() (data []byte, err error) {
	return json.Marshal(m)
}

// UnmarshalBinary implements cache.BinaryEncoder
func (m *myDTO) UnmarshalBinary(data []byte) error {
	return json.Unmarshal(data, m)
}
Output:

Example (NormalUsage)
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"os"
	"time"

	"github.com/corsc/go-commons/cache"
	"github.com/garyburd/redigo/redis"
)

func main() {
	// not required during normal usage
	if os.Getenv("REDIS") == "" {
		return
	}

	// init - called once; perhaps a global variable or member variable
	cacheClient := &cache.Client{
		Storage: &cache.RedisStorage{
			Pool: &redis.Pool{
				MaxIdle:     3,
				IdleTimeout: 240 * time.Second,
				Dial:        func() (redis.Conn, error) { return redis.Dial("tcp", ":6379") },
			},
			TTL: 60 * time.Second,
		},
	}

	// general usage
	ctx, cancelFn := context.WithCancel(context.Background())
	defer cancelFn()

	cacheKey := "cache.key"
	dest := &myDTO{}

	err := cacheClient.Get(ctx, cacheKey, dest, cache.BuilderFunc(func(ctx context.Context, key string, dest cache.BinaryEncoder) error {
		// logic that builds/marshals the cacheable value
		return nil
	}))

	fmt.Printf("Err: %v", err)
}

type myDTO struct {
	Name  string
	Email string
}

// MarshalBinary implements cache.BinaryEncoder
func (m *myDTO) MarshalBinary() (data []byte, err error) {
	return json.Marshal(m)
}

// UnmarshalBinary implements cache.BinaryEncoder
func (m *myDTO) UnmarshalBinary(data []byte) error {
	return json.Unmarshal(data, m)
}
Output:

func (*Client) Get

func (c *Client) Get(ctx context.Context, key string, dest BinaryEncoder, builder Builder) error

Get attempts to retrieve the value from cache and when it misses will run the builder func to create the value.

It will asynchronously update/save the value in the cache on after a successful builder run

func (*Client) Invalidate

func (c *Client) Invalidate(ctx context.Context, key string) error

Invalidate will force invalidate any matching key in the cache

func (*Client) Set

func (c *Client) Set(ctx context.Context, key string, val encoding.BinaryMarshaler)

Set will update the cache with the supplied key/value pair NOTE: generally this need not be called is it is called implicitly by Get

type DynamoDbStorage

type DynamoDbStorage struct {
	// Service is the AWS DDB Client instance
	Service dynamodbiface.DynamoDBAPI

	// TableName is the AWS DDB Table name
	TableName string

	// TTL is the max TTL for cache items (required)
	TTL time.Duration
}

DynamoDbStorage implements Storage

It is strongly recommended that users customize the circuit breaker settings with a call similar to:

hystrix.ConfigureCommand(cache.CbDynamoDbStorage, hystrix.CommandConfig{
    Timeout: 1 * 1000,
    MaxConcurrentRequests: 1000,
    ErrorPercentThreshold: 50,
    })

func (*DynamoDbStorage) Get

func (r *DynamoDbStorage) Get(ctx context.Context, key string) ([]byte, error)

Get implements Storage

func (*DynamoDbStorage) Invalidate

func (r *DynamoDbStorage) Invalidate(ctx context.Context, key string) error

Invalidate implements Storage

func (*DynamoDbStorage) Set

func (r *DynamoDbStorage) Set(ctx context.Context, key string, bytes []byte) error

Set implements Storage

type Event

type Event int

Event denote the cache event type

const (
	// CacheHit denotes the key was found in the cache and successfully returned
	CacheHit Event = iota

	// CacheMiss denotes the key was not found in the cache
	CacheMiss

	// CacheGetError denotes an error occurred with the cache or underlying storage (during gets)
	CacheGetError

	// CacheSetError denotes an error occurred with the cache or underlying storage (during sets)
	CacheSetError

	// CacheInvalidateError denotes an error occurred with the cache or underlying storage (during invalidates)
	CacheInvalidateError

	// CacheLambdaError denotes an error occurred in the user code (e.g. the Builder caller)
	// Note: The builder is only called after a cache miss and therefore CacheLambdaError events should not be counted
	// towards "total cache usage" or used in "cache hit rate" calcuations
	CacheLambdaError

	// CacheUnmarshalError denotes an error occurred in while calling BinaryEncoder.UnmarshalBinary
	//
	// If the BinaryEncoder is implemented correctly, this event should never happen
	CacheUnmarshalError

	// CacheMarshalError denotes an error occurred in while calling BinaryEncoder.MarshalBinary
	//
	// If the BinaryEncoder is implemented correctly, this event should never happen
	CacheMarshalError
)

type LambdaError

type LambdaError struct {
	Cause error
}

LambdaError is the error type returned when the user fetch/build lambda failed

func (LambdaError) Error

func (e LambdaError) Error() string

Error implements error

type Logger

type Logger interface {
	// Build returns the data for the supplied key by populating dest
	Log(message string, args ...interface{})
}

Logger allows for logging errors in the asynchronous calls

type LoggerFunc

type LoggerFunc func(message string, args ...interface{})

LoggerFunc implements Logger as a function

func (LoggerFunc) Log

func (l LoggerFunc) Log(message string, args ...interface{})

Log implements Logger

type Metrics

type Metrics interface {
	// Tracks
	Track(event Event)
}

Metrics allows for instrumenting the cache for hit/miss and errors

type MetricsFunc

type MetricsFunc func(event Event)

MetricsFunc implements Metrics as a function

func (MetricsFunc) Track

func (m MetricsFunc) Track(event Event)

Track implements Metrics

type MockStorage

type MockStorage struct {
	mock.Mock
}

MockStorage is an autogenerated mock type for the Storage type

func (*MockStorage) Get

func (_m *MockStorage) Get(ctx context.Context, key string) ([]byte, error)

Get provides a mock function with given fields: ctx, key

func (*MockStorage) Invalidate

func (_m *MockStorage) Invalidate(ctx context.Context, key string) error

Invalidate provides a mock function with given fields: ctx, key

func (*MockStorage) Set

func (_m *MockStorage) Set(ctx context.Context, key string, bytes []byte) error

Set provides a mock function with given fields: ctx, key, bytes

type RedisStorage

type RedisStorage struct {
	// Pool is the redis connection pool (required)
	Pool *redis.Pool

	// TTL is the max TTL for cache items (required)
	TTL time.Duration
	// contains filtered or unexported fields
}

RedisStorage implements Storage

It is strongly recommended that users customize the circuit breaker settings with a call similar to:

hystrix.ConfigureCommand(cache.CbRedisStorage, hystrix.CommandConfig{
    Timeout: 1 * 1000,
    MaxConcurrentRequests: 1000,
    ErrorPercentThreshold: 50,
    })

func (*RedisStorage) Get

func (r *RedisStorage) Get(ctx context.Context, key string) ([]byte, error)

Get implements Storage

func (*RedisStorage) Invalidate

func (r *RedisStorage) Invalidate(ctx context.Context, key string) error

Invalidate implements Storage

func (*RedisStorage) Set

func (r *RedisStorage) Set(ctx context.Context, key string, bytes []byte) error

Set implements Storage

type Storage

type Storage interface {
	// Get will attempt to get a value from storage
	Get(ctx context.Context, key string) ([]byte, error)

	// Set will save a value into storage
	Set(ctx context.Context, key string, bytes []byte) error

	// Invalidate will force invalidate/remove a key from storage
	Invalidate(ctx context.Context, key string) error
}

Storage is an abstract definition of the underlying cache storage

Jump to

Keyboard shortcuts

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