redis

package module
v0.1.2 Latest Latest
Warning

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

Go to latest
Published: Mar 25, 2024 License: MIT Imports: 13 Imported by: 0

README

gopkg Redis Package

Go Report Card GoDoc OSCS Status

gopkg/redis is an componentized redis package.

It provides:

  • An easy way to configre and manage redis client.
  • Lock handler.
  • Object fetcher.

Based on gomodule/redigo.

Install

go get github.com/wwwangxc/gopkg/redis

Quick Start

Client Proxy
package main

import (
        "context"
        "fmt"

        // gopkg/redis will automatically read configuration
        // files (./app.yaml) when package loaded
        "github.com/wwwangxc/gopkg/redis"
)

func main() {
        cli := redis.NewClientProxy("client_name",
                redis.WithDSN("dsn"),             // set dsn, default use database.client.dsn
                redis.WithMaxIdle(20),            // set max idel. default 2048
                redis.WithMaxActive(100),         // set max active. default 0
                redis.WithIdleTimeout(180000),    // set idle timeout. unit millisecond, default 180000
                redis.WithTimeout(1000),          // set command timeout. unit millisecond, default 1000
                redis.WithMaxConnLifetime(10000), // set max conn life time, default 0
                redis.WithWait(true),             // set wait
        )

        // Exec GET command
        cli.Do(context.Background(), "GET", "foo")

        // Pipeline
        // get a redis connection
        c := cli.Conn()
        defer c.Close()

        c.Send("SET", "foo", "bar")
        c.Send("GET", "foo")
        c.Flush()
        c.Receive()           // reply from SET
        v, err := c.Receive() // reply from GET
        fmt.Sprintf("reply: %s", v)
        fmt.Sprintf("error: %v", err)
}
Locker Proxy
package main

import (
        "context"
        "fmt"

        // gopkg/redis will automatically read configuration
        // files (./app.yaml) when package loaded
        "github.com/wwwangxc/gopkg/redis"
)

func main() {
        // cli := redis.NewClientProxy("client_name").Locker()
        cli := redis.NewLockerProxy("client_name",
                redis.WithDSN("dsn"),             // set dsn, default use database.client.dsn
                redis.WithMaxIdle(20),            // set max idel. default 2048
                redis.WithMaxActive(100),         // set max active. default 0
                redis.WithIdleTimeout(180000),    // set idle timeout. unit millisecond, default 180000
                redis.WithTimeout(1000),          // set command timeout. unit millisecond, default 1000
                redis.WithMaxConnLifetime(10000), // set max conn life time, default 0
                redis.WithWait(true),             // set wait
        )

        // try lock
        // not block the current goroutine.
        // return uuid when the lock is acquired
        // return error when lock fail or lock not acquired
        // support reentrant unlock
        // support automatically renewal
        uuid, err := l.TryLock(context.Background(), "locker_key",
        redis.WithLockExpire(1000*time.Millisecond),
        redis.WithLockHeartbeat(500*time.Millisecond))
        
        if err != nil {

                // return ErrLockNotAcquired when lock not acquired
                if redis.IsLockNotAcquired(err) {
                        fmt.Printf("lock not acquired\n")
                        return
                }
        
                fmt.Printf("try lock fail. error: %v\n", err)
                return
        }
        
        defer func() {

                // return ErrLockNotExist if the key does not exist
                // return ErrNotOwnerOfKey if the uuid invalid
                // support reentrant unlock
                if err := l.Unlock(context.Background(), "locker_key", uuid); err != nil {
                        fmt.Printf("unlock fail. error: %v\n", err)
                }
        }()
                
       // reentrant lock when uuid not empty
       // will block the current goroutine until lock is acquired when not reentrant lock
        _, err = l.Lock(context.Background(), "locker_key",
                redis.WithLockUUID(uuid),
                redis.WithLockExpire(1000*time.Millisecond),
                redis.WithLockHeartbeat(500*time.Millisecond))
                
        if err != nil {
                fmt.Printf("lock fail. error: %v\n", err)
                return
        }

        f := func() error {
                // do something...
                return nil
        }

        // try get lock first and call f() when lock acquired. Unlock will be performed
        // regardless of whether the f reports an error or not.
        if err := l.LockAndCall(context.Background(), "locker_key", f); err != nil {
                fmt.Printf("lock and call fail. error: %v\n", err)
                return
        }
}
Fetcher Proxy
package main

import (
        "context"
        "fmt"

        // gopkg/redis will automatically read configuration
        // files (./app.yaml) when package loaded
        "github.com/wwwangxc/gopkg/redis"
)

func main() {
        // f := redis.NewClientProxy("client_name").Fetcher()
        f := redis.NewFetcherProxy("client_name",
                redis.WithDSN("dsn"),             // set dsn, default use database.client.dsn
                redis.WithMaxIdle(20),            // set max idel. default 2048
                redis.WithMaxActive(100),         // set max active. default 0
                redis.WithIdleTimeout(180000),    // set idle timeout. unit millisecond, default 180000
                redis.WithTimeout(1000),          // set command timeout. unit millisecond, default 1000
                redis.WithMaxConnLifetime(10000), // set max conn life time, default 0
                redis.WithWait(true),             // set wait
        )

        obj := struct {
                FieldA string `json:"field_a"`
                FieldB int    `json:"field_b"`
        }{}
        
        callback := func() (interface{}, error) {
                // do something...
                return nil, nil
        }
        
        // fetch object
        //
        // The callback function will be called if the key does not exist.
        // Will cache the callback results into the key and set timeout.
        // Default do nothing.
        //
        // The marshal function will be called before cache.
        //
        // Default callback do nothing, use json.Marshal and json.Unmarshal
        err := f.Fetch(context.Background(), "fetcher_key", &obj,
                redis.WithFetchCallback(callback, 1000*time.Millisecond),
                redis.WithFetchUnmarshal(json.Unmarshal),
                redis.WithFetchMarshal(json.Marshal))
        
        if err != nil {
                fmt.Printf("fetch fail. error: %v\n", err)
                return
        }
}
Config
client:
  redis:
    max_idle: 20
    max_active: 100
    max_conn_lifetime: 1000
    idle_timeout: 180000
    timeout: 1000
    wait: true
  service:
    - name: redis_1
      dsn: redis://username:password@127.0.0.1:6379/1?timeout=1000ms
    - name: redis_2
      dsn: redis://username:password@127.0.0.1:6379/2?timeout=1000ms
      max_idle: 22
      max_active: 111
      max_conn_lifetime: 2000
      idle_timeout: 200000
      timeout: 2000

How To Mock

Client Proxy
package tests

import (
    "testing"
    
    "github.com/agiledragon/gomonkey"
    "github.com/golang/mock/gomock"

    "github.com/wwwangxc/gopkg/redis"
    "github.com/wwwangxc/gopkg/redis/mockredis"
)

func TestMockClientProxy(t *testing.T){
    ctrl := gomock.NewController(t)
    defer ctrl.Finish()

    // Mock redis client
    mockConn := mockredis.NewMockConn(ctrl)
    mockConn.EXPECT().Close().Return(nil).AnyTimes()
    mockConn.EXPECT().Send(gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
    mockConn.EXPECT().Flush().Return(nil).AnyTimes()
    mockConn.EXPECT().Receive().Return(nil, nil).AnyTimes()

    // Mock locker
    mockLocker := mockredis.NewMockLocker(ctrl)
    mockLocker.EXPECT().TryLock(gomock.Any(), gomock.Any(), gomock.Any()).Return("", nil).AnyTimes()
    mockLocker.EXPECT().Lock(gomock.Any(), gomock.Any(), gomock.Any()).Return("", nil).AnyTimes()
    mockLocker.EXPECT().Unlock(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()

    // Mock fetcher
    mockFetcher := mockredis.NewMockFetcher(ctrl)
    mockFetcher.EXPECT().Fetch(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()

    // Mock client proxy
    mockCli := mockredis.NewMockClientProxy(ctrl)
    mockCli.EXPECT().Do(gomock.Any(), gomock.Any(), gomock.Any()).Return("reply", nil).AnyTimes()   // Do
    mockCli.EXPECT().Conn().Return(mockConn).AnyTimes()        // Conn
    mockCli.EXPECT().Locker().Return(mockLocker).AnyTimes()    // Locker
    mockCli.EXPECT().Fetcher().Return(mockFetcher).AnyTimes()  // Fetcher
    
    patches := gomonkey.ApplyFunc(redis.NewClientProxy,
        func(string, ...redis.ClientOption) redis.ClientProxy {
            return mockCli
        })
    defer patches.Reset()

    // do something...
}
Locker Proxy
package tests

import (
    "testing"
    
    "github.com/agiledragon/gomonkey"
    "github.com/golang/mock/gomock"

    "github.com/wwwangxc/gopkg/redis"
    "github.com/wwwangxc/gopkg/redis/mockredis"
)

func TestMockLockerProxy(t *testing.T){
    ctrl := gomock.NewController(t)
    defer ctrl.Finish()

    // Mock locker
    mockLocker := mockredis.NewMockLocker(ctrl)
    mockLocker.EXPECT().TryLock(gomock.Any(), gomock.Any(), gomock.Any()).Return("", nil).AnyTimes()
    mockLocker.EXPECT().Lock(gomock.Any(), gomock.Any(), gomock.Any()).Return("", nil).AnyTimes()
    mockLocker.EXPECT().Unlock(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
    mockLocker.EXPECT().LockAndCall(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
    
    patches := gomonkey.ApplyFunc(redis.NewLockerProxy,
        func(string, ...redis.ClientOption) redis.Locker {
            return mockLocker
        })
    defer patches.Reset()

    // do something...
}
Fetcher Proxy
package tests

import (
    "testing"
    
    "github.com/agiledragon/gomonkey"
    "github.com/golang/mock/gomock"

    "github.com/wwwangxc/gopkg/redis"
    "github.com/wwwangxc/gopkg/redis/mockredis"
)

func TestMockFetcherProxy(t *testing.T){
    ctrl := gomock.NewController(t)
    defer ctrl.Finish()

    // Mock fetcher
    mockFetcher := mockredis.NewMockFetcher(ctrl)
    mockFetcher.EXPECT().Fetch(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
    
    patches := gomonkey.ApplyFunc(redis.NewFetcherProxy,
        func(string, ...redis.ClientOption) redis.Fetcher {
            return mockFetcher
        })
    defer patches.Reset()

    // do something...
}

Documentation

Overview

Package gopkg/redis is a componentized redis plugin.

It provides an easy way to configre and manage redis client.

Based on https://github.com/gomodule/redigo

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// ErrTimeout
	ErrTimeout = errors.New("timeout")

	// ErrLockNotAcquired lock not acquired
	ErrLockNotAcquired = errors.New("lock not acquired")

	// ErrLockNotExist lock dose not exist
	ErrLockNotExist = errors.New("lock does not exist")

	// ErrNotOwnerOfLock not the owner of the key
	ErrNotOwnerOfLock = errors.New("not the owner of the lock")

	// ErrKeyNotExist key not exist
	ErrKeyNotExist = errors.New("key not exist")
)

Functions

func Bool

func Bool(reply interface{}, err error) (bool, error)

Bool is a helper that converts a command reply to a boolean. If err is not equal to nil, then Bool returns false, err. Otherwise Bool converts the reply to boolean as follows:

Reply type      Result
integer         value != 0, nil
bulk string     strconv.ParseBool(reply)
nil             false, ErrNil
other           false, error

See: https://github.com/gomodule/redigo/blob/master/redis/reply.go

func ByteSlices

func ByteSlices(reply interface{}, err error) ([][]byte, error)

ByteSlices is a helper that converts an array command reply to a [][]byte. If err is not equal to nil, then ByteSlices returns nil, err. Nil array items are stay nil. ByteSlices returns an error if an array item is not a bulk string or nil.

See: https://github.com/gomodule/redigo/blob/master/redis/reply.go

func Bytes

func Bytes(reply interface{}, err error) ([]byte, error)

Bytes is a helper that converts a command reply to a slice of bytes. If err is not equal to nil, then Bytes returns nil, err. Otherwise Bytes converts the reply to a slice of bytes as follows:

Reply type      Result
bulk string     reply, nil
simple string   []byte(reply), nil
nil             nil, ErrNil
other           nil, error

See: https://github.com/gomodule/redigo/blob/master/redis/reply.go

func Float64

func Float64(reply interface{}, err error) (float64, error)

Float64 is a helper that converts a command reply to 64 bit float. If err is not equal to nil, then Float64 returns 0, err. Otherwise, Float64 converts the reply to a float64 as follows:

Reply type    Result
bulk string   parsed reply, nil
nil           0, ErrNil
other         0, error

See: https://github.com/gomodule/redigo/blob/master/redis/reply.go

func Float64s

func Float64s(reply interface{}, err error) ([]float64, error)

Float64s is a helper that converts an array command reply to a []float64. If err is not equal to nil, then Float64s returns nil, err. Nil array items are converted to 0 in the output slice. Floats64 returns an error if an array item is not a bulk string or nil.

See: https://github.com/gomodule/redigo/blob/master/redis/reply.go

func Int

func Int(reply interface{}, err error) (int, error)

Int is a helper that converts a command reply to an integer. If err is not equal to nil, then Int returns 0, err. Otherwise, Int converts the reply to an int as follows:

Reply type    Result
integer       int(reply), nil
bulk string   parsed reply, nil
nil           0, ErrNil
other         0, error

See: https://github.com/gomodule/redigo/blob/master/redis/reply.go

func Int64

func Int64(reply interface{}, err error) (int64, error)

Int64 is a helper that converts a command reply to 64 bit integer. If err is not equal to nil, then Int64 returns 0, err. Otherwise, Int64 converts the reply to an int64 as follows:

Reply type    Result
integer       reply, nil
bulk string   parsed reply, nil
nil           0, ErrNil
other         0, error

See: https://github.com/gomodule/redigo/blob/master/redis/reply.go

func Int64Map

func Int64Map(result interface{}, err error) (map[string]int64, error)

Int64Map is a helper that converts an array of strings (alternating key, value) into a map[string]int64. The HGETALL commands return replies in this format. Requires an even number of values in result.

See: https://github.com/gomodule/redigo/blob/master/redis/reply.go

func Int64s

func Int64s(reply interface{}, err error) ([]int64, error)

Int64s is a helper that converts an array command reply to a []int64. If err is not equal to nil, then Int64s returns nil, err. Nil array items are stay nil. Int64s returns an error if an array item is not a bulk string or nil.

See: https://github.com/gomodule/redigo/blob/master/redis/reply.go

func IntMap

func IntMap(result interface{}, err error) (map[string]int, error)

IntMap is a helper that converts an array of strings (alternating key, value) into a map[string]int. The HGETALL commands return replies in this format. Requires an even number of values in result.

See: https://github.com/gomodule/redigo/blob/master/redis/reply.go

func Ints

func Ints(reply interface{}, err error) ([]int, error)

Ints is a helper that converts an array command reply to a []int. If err is not equal to nil, then Ints returns nil, err. Nil array items are stay nil. Ints returns an error if an array item is not a bulk string or nil.

See: https://github.com/gomodule/redigo/blob/master/redis/reply.go

func IsErrNotOwnerOfLock

func IsErrNotOwnerOfLock(err error) bool

IsErrNotOwnerOfLock is not owner of lock

func IsKeyNotExist

func IsKeyNotExist(err error) bool

IsKeyNotExist is key not exist error

func IsLockNotAcquired

func IsLockNotAcquired(err error) bool

IsLockNotAcquired is lock not acquired error

func IsLockNotExist

func IsLockNotExist(err error) bool

IsLockNotExist is lock not exist error

func IsTimeout

func IsTimeout(err error) bool

IsTimeout is timeout error

func Positions

func Positions(result interface{}, err error) ([]*[2]float64, error)

Positions is a helper that converts an array of positions (lat, long) into a [][2]float64. The GEOPOS command returns replies in this format.

See: https://github.com/gomodule/redigo/blob/master/redis/reply.go

func Scan

func Scan(src []interface{}, dest ...interface{}) ([]interface{}, error)

Scan copies from src to the values pointed at by dest.

Scan uses RedisScan if available otherwise:

The values pointed at by dest must be an integer, float, boolean, string, []byte, interface{} or slices of these types. Scan uses the standard strconv package to convert bulk strings to numeric and boolean types.

If a dest value is nil, then the corresponding src value is skipped.

If a src element is nil, then the corresponding dest value is not modified.

To enable easy use of Scan in a loop, Scan returns the slice of src following the copied values.

See: https://github.com/gomodule/redigo/blob/master/redis/scan.go

func ScanSlice

func ScanSlice(src []interface{}, dest interface{}, fieldNames ...string) error

ScanSlice scans src to the slice pointed to by dest.

If the target is a slice of types which implement Scanner then the custom RedisScan method is used otherwise the following rules apply:

The elements in the dest slice must be integer, float, boolean, string, struct or pointer to struct values.

Struct fields must be integer, float, boolean or string values. All struct fields are used unless a subset is specified using fieldNames.

See: https://github.com/gomodule/redigo/blob/master/redis/scan.go

func ScanStruct

func ScanStruct(src []interface{}, dest interface{}) error

ScanStruct scans alternating names and values from src to a struct. The HGETALL and CONFIG GET commands return replies in this format.

ScanStruct uses exported field names to match values in the response. Use 'redis' field tag to override the name:

Field int `redis:"myName"`

Fields with the tag redis:"-" are ignored.

Each field uses RedisScan if available otherwise: Integer, float, boolean, string and []byte fields are supported. Scan uses the standard strconv package to convert bulk string values to numeric and boolean types.

If a src element is nil, then the corresponding field is not modified.

See: https://github.com/gomodule/redigo/blob/master/redis/scan.go

func SlowLogs

func SlowLogs(result interface{}, err error) ([]redigo.SlowLog, error)

SlowLogs is a helper that parse the SLOWLOG GET command output and return the array of SlowLog

See: https://github.com/gomodule/redigo/blob/master/redis/reply.go

func String

func String(reply interface{}, err error) (string, error)

String is a helper that converts a command reply to a string. If err is not equal to nil, then String returns "", err. Otherwise String converts the reply to a string as follows:

Reply type      Result
bulk string     string(reply), nil
simple string   reply, nil
nil             "",  ErrNil
other           "",  error

See: https://github.com/gomodule/redigo/blob/master/redis/reply.go

func StringMap

func StringMap(result interface{}, err error) (map[string]string, error)

StringMap is a helper that converts an array of strings (alternating key, value) into a map[string]string. The HGETALL and CONFIG GET commands return replies in this format. Requires an even number of values in result.

See: https://github.com/gomodule/redigo/blob/master/redis/reply.go

func Strings

func Strings(reply interface{}, err error) ([]string, error)

Strings is a helper that converts an array command reply to a []string. If err is not equal to nil, then Strings returns nil, err. Nil array items are converted to "" in the output slice. Strings returns an error if an array item is not a bulk string or nil.

See: https://github.com/gomodule/redigo/blob/master/redis/reply.go

func Uint64

func Uint64(reply interface{}, err error) (uint64, error)

Uint64 is a helper that converts a command reply to 64 bit unsigned integer. If err is not equal to nil, then Uint64 returns 0, err. Otherwise, Uint64 converts the reply to an uint64 as follows:

Reply type    Result
+integer      reply, nil
bulk string   parsed reply, nil
nil           0, ErrNil
other         0, error

See: https://github.com/gomodule/redigo/blob/master/redis/reply.go

func Uint64Map

func Uint64Map(result interface{}, err error) (map[string]uint64, error)

Uint64Map is a helper that converts an array of strings (alternating key, value) into a map[string]uint64. The HGETALL commands return replies in this format. Requires an even number of values in result.

See: https://github.com/gomodule/redigo/blob/master/redis/reply.go

func Uint64s

func Uint64s(reply interface{}, err error) ([]uint64, error)

Uint64s is a helper that converts an array command reply to a []uint64. If err is not equal to nil, then Uint64s returns nil, err. Nil array items are stay nil. Uint64s returns an error if an array item is not a bulk string or nil.

See: https://github.com/gomodule/redigo/blob/master/redis/reply.go

func Values

func Values(reply interface{}, err error) ([]interface{}, error)

Values is a helper that converts an array command reply to a []interface{}. If err is not equal to nil, then Values returns nil, err. Otherwise, Values converts the reply as follows:

Reply type      Result
array           reply, nil
nil             nil, ErrNil
other           nil, error

See: https://github.com/gomodule/redigo/blob/master/redis/reply.go

Types

type ClientOption

type ClientOption func(*serviceConfig)

ClientOption redis client proxy option

func WithClientDSN

func WithClientDSN(dsn string) ClientOption

WithClientDSN set dsn

func WithClientIdleTimeout

func WithClientIdleTimeout(idleTimeout int) ClientOption

WithClientIdleTimeout set idle timeout

Close connections after remaining idle for this duration. If the value is zero, then idle connections are not closed. Applications should set the timeout to a value less than the server's timeout. Unit millisecond, default 180000

func WithClientMaxActive

func WithClientMaxActive(maxActive int) ClientOption

WithClientMaxActive set max active

Maximum number of connections allocated by the pool at a given time. When zero, there is no limit on the number of connections in the pool. Default 0

func WithClientMaxConnLifetime

func WithClientMaxConnLifetime(maxConnLifetime int) ClientOption

WithClientMaxConnLifetime set max conn lifetime

Close connections older than this duration. If the value is zero, then the pool does not close connections based on age. Unit millisecond, default 0

func WithClientMaxIdle

func WithClientMaxIdle(maxIdle int) ClientOption

WithClientMaxIdle set max idle

Maximum number of connections in the idle connection pool. Default 2048

func WithClientTimeout

func WithClientTimeout(timeout int) ClientOption

WithClientTimeout set timeout

Write, read and connect timeout Unit millisecond, default 1000

func WithClientWait

func WithClientWait(wait bool) ClientOption

WithClientWait set wait

If Wait is true and the pool is at the MaxActive limit, then Get() waits for a connection to be returned to the pool before returning.

type ClientProxy

type ClientProxy interface {

	// Do sends a command to server and returns the received reply.
	// min(ctx,DialReadTimeout()) will be used as the deadline.
	// The connection will be closed if DialReadTimeout() timeout or ctx timeout or ctx canceled when this function is running.
	// DialReadTimeout() timeout return err can be checked by strings.Contains(e.Error(), "io/timeout").
	// ctx timeout return err context.DeadlineExceeded.
	// ctx canceled return err context.Canceled.
	Do(ctx context.Context, cmd string, args ...interface{}) (interface{}, error)

	// Conn gets a connection. The application must close the returned connection.
	// This method always returns a valid connection so that applications can defer
	// error handling to the first use of the connection. If there is an error
	// getting an underlying connection, then the connection Err, Do, Send, Flush and Receive methods return that error.
	Conn() redigo.Conn

	// Locker gets a distributed lock provider
	Locker() LockerProxy

	// Fetcher gets an object fetcher
	Fetcher() FetcherProxy
}

ClientProxy Redis client proxy

Example
package main

import (
	"context"

	"github.com/wwwangxc/gopkg/redis"
)

func main() {
	// new client proxy
	cli := redis.NewClientProxy("client_name",
		redis.WithClientDSN("dsn"),             // set dsn, default use database.client.dsn
		redis.WithClientMaxIdle(20),            // set max idel. default 2048
		redis.WithClientMaxActive(100),         // set max active. default 0
		redis.WithClientIdleTimeout(180000),    // set idle timeout. unit millisecond, default 180000
		redis.WithClientTimeout(1000),          // set command timeout. unit millisecond, default 1000
		redis.WithClientMaxConnLifetime(10000), // set max conn life time, default 0
		redis.WithClientWait(true),             // set wait
	)

	// Do sends a command to server and returns the received reply.
	cli.Do(context.Background(), "GET", "foo")

	// Connection
	c := cli.Conn()
	defer c.Close()
	c.Send("SET", "foo", "bar")
	c.Send("GET", "foo")
	c.Flush()
	c.Receive() // reply from SET
	c.Receive() // reply from GET

	// Gets a distributed lock provider
	_ = cli.Locker()

	// Gets an object fetcher
	_ = cli.Fetcher()
}
Output:

func NewClientProxy

func NewClientProxy(name string, opts ...ClientOption) ClientProxy

NewClientProxy new redis client proxy

type FetchOption

type FetchOption func(*FetchOptions)

FetchOption fetch option

func WithFetchCallback

func WithFetchCallback(callback func() (interface{}, error), expire time.Duration) FetchOption

WithFetchCallback set fetch callback & expire option

The callback function will be called if the key does not exist. Will cache the callback results into the key and set timeout. Default do nothing.

func WithFetchMarshal

func WithFetchMarshal(marshal func(v interface{}) ([]byte, error)) FetchOption

WithFetchMarshal set mashal function to fetcher

The marshal function will be called before cache. Default use json.Marshal.

func WithFetchUnmarshal

func WithFetchUnmarshal(unmarshal func(data []byte, dest interface{}) error) FetchOption

WithFetchUnmarshal set unmarshal function to fetcher

Default use json.Unmarshal.

func WithSingleflight added in v0.1.2

func WithSingleflight(expire time.Duration) FetchOption

WithSingleflight use singleflight for fetcher

type FetchOptions

type FetchOptions struct {
	Expire             time.Duration
	ExpireSingleflight time.Duration
	Callback           func() (interface{}, error)
	Marshal            func(v interface{}) ([]byte, error)
	Unmarshal          func(data []byte, dest interface{}) error
}

FetchOptions fetch options

type FetcherProxy

type FetcherProxy interface {

	// Fetch data and storing the result into the struct pointed at by dest.
	//
	// Use json decode
	Fetch(ctx context.Context, key string, dest interface{}, opts ...FetchOption) error
}

FetcherProxy object fetcher

Example
package main

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

	"github.com/wwwangxc/gopkg/redis"
)

func main() {
	obj := struct {
		FieldA string `json:"field_a"`
		FieldB int    `json:"field_b"`
	}{}

	callback := func() (interface{}, error) {
		// load object
		return nil, nil
	}

	// new fetcher proxy
	f := redis.NewFetcherProxy("client_name",
		redis.WithClientDSN("dsn"),             // set dsn, default use database.client.dsn
		redis.WithClientMaxIdle(20),            // set max idel. default 2048
		redis.WithClientMaxActive(100),         // set max active. default 0
		redis.WithClientIdleTimeout(180000),    // set idle timeout. unit millisecond, default 180000
		redis.WithClientTimeout(1000),          // set command timeout. unit millisecond, default 1000
		redis.WithClientMaxConnLifetime(10000), // set max conn life time, default 0
		redis.WithClientWait(true),             // set wait
	)

	// fetch object
	err := f.Fetch(context.Background(), "fetcher_key", &obj,
		redis.WithFetchCallback(callback, 1000*time.Millisecond), // set callback method and cache result for 1000 millisecond
		redis.WithFetchUnmarshal(json.Unmarshal),                 // unmarshal by json
		redis.WithFetchMarshal(json.Marshal),                     // marshal by json
		redis.WithSingleflight(time.Second),                      // use singleflight for fetcher
	)

	if err != nil {
		fmt.Printf("fetch fail. error: %v\n", err)
		return
	}
}
Output:

func NewFetcherProxy

func NewFetcherProxy(name string, opts ...ClientOption) FetcherProxy

NewFetcherProxy new object fetcher proxy

type LockOption

type LockOption func(*LockOptions)

LockOption distributed lock option

func WithLockExpire

func WithLockExpire(expire time.Duration) LockOption

WithLockExpire set lock expire

default 1000 millisecond

func WithLockHeartbeat

func WithLockHeartbeat(heartbeat time.Duration) LockOption

WithLockHeartbeat set heartbeat

Heartbeat indicates the time interval for automatically renewal. Heartbeat = 0 means the lock will not automatically renewal when it expires. default 0

func WithLockRetry

func WithLockRetry(retry time.Duration) LockOption

WithLockRetry set retry Retry indicates the time interval for retrying the acquire lock. Default 1000 millisecond

func WithLockUUID

func WithLockUUID(uuid string) LockOption

WithLockUUID set uuid of the distributed lock

A non-null UUID indicates a reentrant lock.

type LockOptions

type LockOptions struct {
	// UUID of the lock
	// A non-null UUID indicates a reentrant lock.
	UUID string

	// Expire of the lock
	// Default 1000 millisecond
	Expire time.Duration

	// Heartbeat indicates the time interval for automatically renewal.
	// Heartbeat = 0 means the lock will not automatically renewal when it expires.
	// Default 0
	Heartbeat time.Duration

	// Retry indicates the time interval for retrying the acquire lock.
	// Default 1000 millisecond
	Retry time.Duration
}

LockOptions distributed lock options

type LockerProxy

type LockerProxy interface {

	// LockAndCall try get lock first and call f() when lock acquired. Unlock will be performed
	// regardless of whether the f reports an error or not.
	//
	// Will block the current goroutine when lock not acquired
	// Will reentrant lock when UUID option not empty.
	// If Heartbeat option not empty and not a reentrant lock, will automatically
	// renewal until unlocked.
	LockAndCall(ctx context.Context, key string, f func() error, opts ...LockOption) error

	// TryLock try get lock, if lock acquired will return lock uuid.
	//
	// Not block the current goroutine.
	// Return ErrLockNotAcquired when lock not acquired.
	// Will reentrant lock when UUID option not empty.
	// If Heartbeat option not empty and not a reentrant lock, will automatically
	// renewal until unlocked.
	TryLock(ctx context.Context, key string, opts ...LockOption) (uuid string, err error)

	// Lock try get lock until the context canceled or the lock acquired
	//
	// Will block the current goroutine.
	// Will reentrant lock when UUID option not empty.
	// If Heartbeat option not empty and not a reentrant lock, will automatically
	// renewal until unlocked.
	Lock(ctx context.Context, key string, opts ...LockOption) (uuid string, err error)

	// Unlock
	//
	// Return ErrLockNotExist if the key does not exist.
	// Return ErrNotOwnerOfKey if the uuid invalid.
	// Support reentrant unlock.
	Unlock(ctx context.Context, key, uuid string) error
}

LockerProxy distributed lock provider

Example
package main

import (
	"context"
	"fmt"
	"time"

	"github.com/wwwangxc/gopkg/redis"
)

func main() {
	// new locker proxy
	l := redis.NewLockerProxy("client_name",
		redis.WithClientDSN("dsn"),             // set dsn, default use database.client.dsn
		redis.WithClientMaxIdle(20),            // set max idel. default 2048
		redis.WithClientMaxActive(100),         // set max active. default 0
		redis.WithClientIdleTimeout(180000),    // set idle timeout. unit millisecond, default 180000
		redis.WithClientTimeout(1000),          // set command timeout. unit millisecond, default 1000
		redis.WithClientMaxConnLifetime(10000), // set max conn life time, default 0
		redis.WithClientWait(true),             // set wait
	)

	// try lock
	// not block the current goroutine.
	// return uuid when the lock is acquired
	// return error when lock fail or lock not acquired
	// support reentrant unlock
	// support automatically renewal
	uuid, err := l.TryLock(context.Background(), "locker_key",
		redis.WithLockExpire(1000*time.Millisecond),
		redis.WithLockHeartbeat(500*time.Millisecond))

	if err != nil {

		// return ErrLockNotAcquired when lock not acquired
		if redis.IsLockNotAcquired(err) {
			fmt.Printf("lock not acquired\n")
			return
		}

		fmt.Printf("try lock fail. error: %v\n", err)
		return
	}

	defer func() {

		// return ErrLockNotExist if the key does not exist
		// return ErrNotOwnerOfKey if the uuid invalid
		// support reentrant unlock
		if err := l.Unlock(context.Background(), "locker_key", uuid); err != nil {
			fmt.Printf("unlock fail. error: %v\n", err)
		}
	}()

	// reentrant lock when uuid not empty
	// will block the current goroutine until lock is acquired when not reentrant lock
	_, err = l.Lock(context.Background(), "locker_key",
		redis.WithLockUUID(uuid),
		redis.WithLockExpire(1000*time.Millisecond),
		redis.WithLockHeartbeat(500*time.Millisecond))

	if err != nil {
		fmt.Printf("lock fail. error: %v\n", err)
		return
	}

	f := func() error {
		// do something...
		return nil
	}

	// try get lock first and call f() when lock acquired. Unlock will be performed
	// regardless of whether the f reports an error or not.
	if err := l.LockAndCall(context.Background(), "locker_key", f); err != nil {
		fmt.Printf("lock and call fail. error: %v\n", err)
		return
	}
}
Output:

func NewLockerProxy

func NewLockerProxy(name string, opts ...ClientOption) LockerProxy

NewLockerProxy new locker proxy

Directories

Path Synopsis
Package mockredis is a generated GoMock package.
Package mockredis is a generated GoMock package.

Jump to

Keyboard shortcuts

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