redis

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Oct 9, 2023 License: BSD-3-Clause Imports: 13 Imported by: 0

README

Redis client for Go

PkgGoDev

Features

  • Automatic connection pooling with
  • Print-like API with support for all Redis commands.
  • Pipelines and transactions
  • High-level PubSub API
  • Scripting with optimistic use of EVALSHA and EVALSHA_RO.
  • Reply helper type.

Documentation

Installation

go-redis supports 2 last Go versions and requires a Go version with modules support. So make sure to initialize a Go module:

go mod init github.com/my/repo

Then install go-redis:

go get github.com/weiwenchen2022/go-redis

Quickstart

import (
    "context"
    "fmt"

    "github.com/weiwenchen2022/go-redis"
)

func ExampleClient() {
    var (
        ctx = context.Background()
        rdb = redis.NewClient(&redis.Options{
            Addr:     "localhost:6379",
            Password: "", // no password set
            DB:       0,  // use default DB
        })
    )

    err := rdb.Do(ctx, "SET", "key", "value").Err()
    if err != nil {
        panic(err)
    }

    val, err := rdb.Do(ctx, "GET", "key").String()
    if err != nil {
        panic(err)
    }
    fmt.Println("key", val)

    val2, err := rdb.Get(ctx, "missing_key").String()
    switch err {
    default:
        panic(err)
    case redis.Nil:
        fmt.Println("missing_key does not exist")
    case nil:
        fmt.Println("missing_key", val2)
    }
    // Output: key value
    // missing_key does not exist
}

Documentation

Overview

Package redis is a client for the Redis database.

Example (Zpop)

This example implements ZPOP as described at http://redis.io/topics/transactions using WATCH/MULTI/EXEC and scripting.

package main

import (
	"context"
	"fmt"

	"github.com/weiwenchen2022/go-redis"
)

// zpop pops a value from the ZSET key using WATCH/MULTI/EXEC commands.
func zpop(ctx context.Context, rdb *redis.Client, key string) (result string, err error) {
	// Loop until transaction is successful.
	for {
		err = rdb.Watch(ctx, func(tx *redis.Tx) error {
			members, err := tx.Do(ctx, "ZRANGE", key, 0, 0).Strings()
			if err != nil {
				return err
			}
			if len(members) != 1 {
				return redis.ErrNil
			}

			_, err = tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error {
				pipe.Send("ZREM", key, members[0])
				return nil
			})
			if err == nil {
				result = members[0]
			}
			return err
		}, key)
		switch err {
		default:
			// Return any other error.
			return "", err
		case nil:
			// Success.
			return result, nil
		case redis.TxFailedErr:
			// Optimistic lock lost. Retry.
		}
	}
}

// zpopScript pops a value from a ZSET.
var zpopScript = redis.NewScript(`
    local r = redis.call('ZRANGE', KEYS[1], 0, 0)
    if r ~= nil then
        r = r[1]
        redis.call('ZREM', KEYS[1], r)
    end
    return r
`)

// This example implements ZPOP as described at
// http://redis.io/topics/transactions using WATCH/MULTI/EXEC and scripting.
func main() {
	var (
		ctx = context.Background()
		rdb = redis.NewClient(&redis.Options{
			Addr: "localhost:6379",
		})
	)

	// Add test data using a pipeline.
	if _, err := rdb.Pipelined(ctx, func(pipe redis.Pipeliner) error {
		for i, member := range []string{"red", "blue", "green"} {
			pipe.Send("ZADD", "zset", i, member)
		}
		return nil
	}); err != nil {
		panic(err)
	}

	// Pop using WATCH/MULTI/EXEC
	v, err := zpop(ctx, rdb, "zset")
	if err != nil {
		panic(err)
	}
	fmt.Println(v)

	// Pop using a script.

	v, err = zpopScript.Run(ctx, rdb, []string{"zset"}).String()
	if err != nil {
		panic(err)
	}
	fmt.Println(v)
}
Output:

red
blue

Index

Examples

Constants

View Source
const ErrClosed = redis.Error("redis: client is closed")

ErrClosed performs any operation on the closed client will return this error.

View Source
const ErrNil = redis.Error("redis: nil")

ErrNil reply returned by Redis when key does not exist.

View Source
const TxFailedErr = redis.Error("redis: transaction failed")

TxFailedErr transaction redis failed.

Variables

View Source
var ErrValuesExhausted = errors.New("redis: values exhausted")

Functions

This section is empty.

Types

type Args

type Args = redis.Args

type ChannelOption

type ChannelOption interface {
	// contains filtered or unexported methods
}

func WithChannelHealthCheckInterval

func WithChannelHealthCheckInterval(d time.Duration) ChannelOption

WithChannelHealthCheckInterval specifies the health check interval. PubSub will ping Redis Server if it does not receive any messages within the interval. To disable health check, use zero interval.

The default is 3 seconds.

func WithChannelSendTimeout

func WithChannelSendTimeout(d time.Duration) ChannelOption

WithChannelSendTimeout specifies the channel send timeout after which the message is dropped.

The default is 60 seconds.

func WithChannelSize

func WithChannelSize(size int) ChannelOption

WithChannelSize specifies the Go chan size that is used to buffer incoming messages.

The default is 100 messages.

type Client

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

Client is a Redis client representing a pool of zero or more underlying connections. It's safe for concurrent use by multiple goroutines. Client creates and frees connections automatically; it also maintains a free pool of idle connections.

Example
err := rdb.Do(ctx, "SET", "key", "value").Err()
if err != nil {
	panic(err)
}

val, err := rdb.Do(ctx, "GET", "key").String()
if err != nil {
	panic(err)
}
fmt.Println("key", val)

val2, err := rdb.Do(ctx, "GET", "missing_key").String()
switch err {
default:
	panic(err)
case redis.ErrNil:
	fmt.Println("missing_key does not exist")
case nil:
	fmt.Println("missing_key", val2)
}
Output:

key value
missing_key does not exist

func NewClient

func NewClient(opt *Options) *Client

NewClient returns a client to the Redis Server specified by Options.

Example
rdb := redis.NewClient(&redis.Options{
	Addr:     "localhost:6379", // use default Addr
	Password: "",               // no password set
	DB:       0,                // use default DB
})

pong, err := rdb.Do(ctx, "PING").String()
fmt.Println(pong, err)
Output:

PONG <nil>

func (Client) Close

func (p Client) Close() error

Close closes the client, releasing any open resources.

It is rare to Close a Client, as the Client is meant to be long-lived and shared between many goroutines.

func (Client) Conn

func (p Client) Conn() *Conn

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.

func (Client) Do

func (p Client) Do(ctx context.Context, commandName string, args ...any) *Result

Do sends a command to the server and returns the received reply. This function will use the timeout which was set when the connection is created

func (Client) Eval

func (p Client) Eval(ctx context.Context, script string, keys []string, args ...any) *Result

func (Client) EvalRO

func (p Client) EvalRO(ctx context.Context, script string, keys []string, args ...interface{}) *Result

func (Client) EvalSha

func (p Client) EvalSha(ctx context.Context, sha1 string, keys []string, args ...interface{}) *Result

func (Client) EvalShaRO

func (p Client) EvalShaRO(ctx context.Context, sha1 string, keys []string, args ...interface{}) *Result

func (Client) PSubscribe

func (p Client) PSubscribe(ctx context.Context, patterns ...string) *PubSub

PSubscribe subscribes the client to the given patterns. Patterns can be omitted to create empty subscription.

func (Client) Pipeline

func (p Client) Pipeline() Pipeliner
Example
err := rdb.Do(ctx, "DEL", "pipeline_counter").Err()
if err != nil {
	panic(err)
}

pipe := rdb.Pipeline()

incr := pipe.Send("INCR", "pipeline_counter")
pipe.Send("EXPIRE", "pipeline_counter", 1*time.Minute)

// Execute
//
//	INCR pipeline_counter
//	EXPIRE pipeline_counts 60
//
// using one rdb-server roundtrip.
_, err = pipe.Exec(ctx)
if err != nil {
	panic(err)
}
fmt.Println(incr.Int())
Output:

1 <nil>

func (Client) Pipelined

func (p Client) Pipelined(ctx context.Context, fn func(Pipeliner) error) ([]*Result, error)
Example
err := rdb.Do(ctx, "DEL", "pipelined_counter").Err()
if err != nil {
	panic(err)
}

var incr *redis.Result
_, err = rdb.Pipelined(ctx, func(pipe redis.Pipeliner) error {
	incr = pipe.Send("INCR", "pipelined_counter")
	pipe.Send("EXPIRE", "pipelined_counter", 1*time.Minute)
	return nil
})
if err != nil {
	panic(err)
}
fmt.Println(incr.Int())
Output:

1 <nil>

func (Client) PoolStats

func (p Client) PoolStats() PoolStats

PoolStats returns connection pool stats.

func (Client) ScriptExists

func (p Client) ScriptExists(ctx context.Context, hashes ...string) *Result

func (Client) ScriptFlush

func (p Client) ScriptFlush(ctx context.Context) *Result

func (Client) ScriptLoad

func (p Client) ScriptLoad(ctx context.Context, script string) *Result

func (Client) Subscribe

func (p Client) Subscribe(ctx context.Context, channels ...string) *PubSub

Subscribe subscribes the client to the specified channels. Channels can be omitted to create empty subscription. Note that this method does not wait on a response from Redis, so the subscription may not be active immediately. To force the connection to wait, you may call the Receive() method on the returned *PubSub like so:

sub := client.Subscribe(queryResp)
iface, err := sub.Receive()
if err != nil {
    // handle error
}

// Should be *Subscription, but others are possible if other actions have been
// taken on sub since it was created.
switch iface.(type) {
case *Subscription:
    // subscribe succeeded
case *Message:
    // received first message
case *Pong:
    // pong received
default:
    // handle error
}

ch := sub.Channel()

func (Client) TxPipeline

func (p Client) TxPipeline() Pipeliner

TxPipeline acts like Pipeline, but wraps queued commands with MULTI/EXEC.

Example
err := rdb.Do(ctx, "DEL", "tx_pipeline_counter").Err()
if err != nil {
	panic(err)
}

pipe := rdb.TxPipeline()

incr := pipe.Send("INCR", "tx_pipeline_counter")
pipe.Send("EXPIRE", "tx_pipeline_counter", 1*time.Minute)

// Execute
//
//	MULTI
//	INCR tx_pipeline_counter
//	EXPIRE tx_pipeline_counter 60
//	EXEC
//
// using one rdb-server roundtrip.
_, err = pipe.Exec(ctx)
if err != nil {
	panic(err)
}
fmt.Println(incr.Int())
Output:

1 <nil>

func (Client) TxPipelined

func (p Client) TxPipelined(ctx context.Context, fn func(Pipeliner) error) ([]*Result, error)
Example
err := rdb.Do(ctx, "DEL", "tx_pipelined_counter").Err()
if err != nil {
	panic(err)
}

var incr *redis.Result
_, err = rdb.TxPipelined(ctx, func(pipe redis.Pipeliner) error {
	incr = pipe.Send("INCR", "tx_pipelined_counter")
	pipe.Send("EXPIRE", "tx_pipelined_counter", 1*time.Minute)
	return nil
})
if err != nil {
	panic(err)
}
fmt.Println(incr.Int())
Output:

1 <nil>

func (Client) Watch

func (p Client) Watch(ctx context.Context, fn func(*Tx) error, keys ...string) error

Watch prepares a transaction and marks the keys to be watched for conditional execution if there are any keys.

The transaction is automatically closed when fn exits.

Example
// Increment transactionally increments key using GET and SET commands.
increment := func(key string) error {
	// Transactional function.
	txf := func(tx *redis.Tx) error {
		// Get current value or zero.
		n, err := tx.Do(ctx, "GET", key).Int()
		if err != nil && redis.ErrNil != err {
			return err
		}

		// Actual opperation (local in optimistic lock).
		n++

		// Operation is committed only if the watched keys remain unchanged.
		_, err = tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error {
			pipe.Send("SET", key, n)
			return nil
		})
		return err
	}

	const maxRetries = 10000
	for i := 0; i < maxRetries; i++ {
		switch err := rdb.Watch(ctx, txf, key); err {
		default:
			// Return any other error.
			return err
		case nil:
			// Success.
			return nil
		case redis.ErrNil:
			// Optimistic lock lost. Retry.
		}
	}
	return errors.New("increment reached maximum number of retries")
}

err := rdb.Do(ctx, "DEL", "counter3").Err()
if err != nil {
	panic(err)
}

var wg sync.WaitGroup
for i := 0; i < 100; i++ {
	wg.Add(1)
	go func() {
		defer wg.Done()

		if err := increment("counter3"); err != nil {
			fmt.Println("increment error:", err)
		}
	}()
}
wg.Wait()

n, err := rdb.Do(ctx, "GET", "counter3").Int()
fmt.Println("ended with", n, err)
Output:

ended with 100 <nil>

type Conn

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

Conn represents a single Redis connection rather than a pool of connections. Prefer running commands from Client unless there is a specific need for a continuous single Redis connection.

Example
conn := rdb.Conn()
defer conn.Close()

err := conn.Do(ctx, "CLIENT", "SETNAME", "foobar").Err()
if err != nil {
	panic(err)
}

// Open other connections.
for i := 0; i < 10; i++ {
	go rdb.Do(ctx, "Ping")
}

s, err := conn.Do(ctx, "CLIENT", "GETNAME").String()
if err != nil {
	panic(err)
}
fmt.Println(s)
Output:

foobar

func (*Conn) Close

func (c *Conn) Close() error

func (*Conn) Do

func (c *Conn) Do(ctx context.Context, commandName string, args ...any) *Result

func (*Conn) Err

func (c *Conn) Err() error

func (Conn) Eval

func (c Conn) Eval(ctx context.Context, script string, keys []string, args ...any) *Result

func (Conn) EvalRO

func (c Conn) EvalRO(ctx context.Context, script string, keys []string, args ...any) *Result

func (Conn) EvalSha

func (c Conn) EvalSha(ctx context.Context, sha1 string, keys []string, args ...any) *Result

func (Conn) EvalShaRO

func (c Conn) EvalShaRO(ctx context.Context, sha1 string, keys []string, args ...any) *Result

func (*Conn) Flush

func (c *Conn) Flush() error

func (*Conn) Pipeline

func (c *Conn) Pipeline() Pipeliner

func (*Conn) Pipelined

func (c *Conn) Pipelined(ctx context.Context, fn func(Pipeliner) error) ([]*Result, error)

func (*Conn) Receive

func (c *Conn) Receive(ctx context.Context) (any, error)

func (*Conn) ReceiveTimeout

func (c *Conn) ReceiveTimeout(timeout time.Duration) (any, error)

func (Conn) ScriptExists

func (c Conn) ScriptExists(ctx context.Context, hashes ...string) *Result

func (Conn) ScriptFlush

func (c Conn) ScriptFlush(ctx context.Context) *Result

func (Conn) ScriptLoad

func (c Conn) ScriptLoad(ctx context.Context, script string) *Result

func (*Conn) Send

func (c *Conn) Send(commandName string, args ...any) error

func (*Conn) TxPipeline

func (c *Conn) TxPipeline() Pipeliner

TxPipeline acts like Pipeline, but wraps queued commands with MULTI/EXEC.

func (*Conn) TxPipelined

func (c *Conn) TxPipelined(ctx context.Context, fn func(Pipeliner) error) ([]*Result, error)

type Message

type Message struct {
	Channel string
	Pattern string
	Payload string
}

Message received as result of a PUBLISH command issued by another client.

func (*Message) String

func (m *Message) String() string

type Options

type Options struct {
	// The network type, either tcp or unix.
	// Default is tcp.
	Network string

	// host:port address.
	Addr string

	// ClientName specifies a client name to be used by the Redis server connection.
	ClientName string

	// DialContextFunc specifies a custom dial function with context for creating TCP connections.
	DialContextFunc func(ctx context.Context, network, addr string) (net.Conn, error)

	// Username specifies the username to use when connecting to the Redis server when Redis ACLs are used.
	// Password must also be set otherwise this option will have no effect.
	Username string

	// Password specifies the password to use when connecting to the Redis server.
	Password string

	// DB specifies the database to select when dialing a connection.
	DB int

	// ConnectTimeout specifies the timeout for connecting to the Redis server.
	// Default is 5 seconds.
	ConnectTimeout time.Duration

	// ReadTimeout specifies the timeout for reading a single command reply.
	// Supported values:
	//   - `0` - default timeout (3 seconds).
	//   - `-1` - no timeout (block indefinitely).
	//   - `-2` - disables SetReadDeadline calls completely.
	ReadTimeout time.Duration

	// WriteTimeout specifies the timeout for writing a single command.
	// Supported values:
	//   - `0` - default timeout (3 seconds).
	//   - `-1` - no timeout (block indefinitely).
	//   - `-2` - disables SetWriteDeadline calls completely.
	WriteTimeout time.Duration

	// TLSConfig specifies the config to use when a TLS connection is dialed.
	TLSConfig *tls.Config

	// Maximum number of idle connections in the pool.
	MaxIdle int

	// 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.
	IdleTimeout time.Duration
}

Options keeps the settings to set up redis connection.

type Pipeliner

type Pipeliner interface {
	// Len is to obtain the number of commands in the pipeline that have not yet been executed.
	Len() int

	// Send writes the command to the client's output buffer.
	Send(commandName string, args ...any) *Result

	// Discard is to discard all commands in the cache that have not yet been executed.
	Discard()

	// Exec is to send all the commands buffered in the pipeline to the redis-server.
	Exec(ctx context.Context) ([]*Result, error)

	Pipelined(ctx context.Context, fn func(Pipeliner) error) ([]*Result, error)
}

Pipeliner is an mechanism to realise Redis Pipeline technique. Pipelining is a technique to extremely speed up processing by packing operations to batches, send them at once to Redis and read a replies in a single step. See https://redis.io/topics/pipelining

type Pong

type Pong struct {
	Payload string
}

Pong received as result of a PING command issued by another client.

func (*Pong) String

func (p *Pong) String() string

type PoolStats

type PoolStats = redis.PoolStats

type PubSub

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

PubSub implements Pub/Sub commands as described in http://redis.io/topics/pubsub. Message receiving is NOT safe for concurrent use by multiple goroutines.

PubSub automatically reconnects to Redis Server and resubscribes to the channels in case of network errors.

Example
pubsub := rdb.Subscribe(ctx, "mychannel1")

// Wait for confirmation that subscription is created before publishing anything.
_, err := pubsub.Receive(ctx)
if err != nil {
	panic(err)
}

// Go channel which receives messages.
ch := pubsub.Channel()

// Publish a message.
err = rdb.Do(ctx, "PUBLISH", "mychannel1", "hello").Err()
if err != nil {
	panic(err)
}

time.AfterFunc(1*time.Second, func() {
	// When pubsub is closed channel is closed too.
	_ = pubsub.Close()
})

// Consume messages.
for msg := range ch {
	fmt.Println(msg.Channel, msg.Payload)
}
Output:

mychannel1 hello

func (*PubSub) Channel

func (p *PubSub) Channel(opts ...ChannelOption) <-chan *Message

Channel returns a Go channel for concurrently receiving messages. The channel is closed together with the PubSub. If the Go channel is blocked full for 30 seconds the message is dropped. Receive* APIs can not be used after channel is created.

go-redis periodically sends ping messages to test connection health and re-subscribes if ping can not not received for 30 seconds.

func (*PubSub) ChannelWithSubscriptions

func (p *PubSub) ChannelWithSubscriptions(opts ...ChannelOption) <-chan any

ChannelWithSubscriptions is like Channel, but message type can be either *Subscription or *Message. Subscription messages can be used to detect reconnections.

ChannelWithSubscriptions can not be used together with Channel.

Example

This example shows how receive pubsub notifications with cancelation.

package main

import (
	"context"
	"fmt"

	"github.com/weiwenchen2022/go-redis"
)

// listenPubSubChannels listens for messages on Redis pubsub channels. The
// onStart function is called after the channels are subscribed. The onMessage
// function is called for each message.
func listenPubSubChannels(ctx context.Context, rdb *redis.Client,
	onStart func() error,
	onMessage func(channel string, payload string) error,
	channels ...string,
) error {
	pubsub := rdb.Subscribe(ctx, channels...)

	done := make(chan error, 1)

	// Start a goroutine to receive notifications from the server.
	go func() {
		// Go channel which receives messages.
		ch := pubsub.ChannelWithSubscriptions()

		// Consume messages.
		for msgi := range ch {
			switch msg := msgi.(type) {
			case *redis.Message:
				if err := onMessage(msg.Channel, msg.Payload); err != nil {
					done <- err
					return
				}
			case *redis.Subscription:
				switch msg.Count {
				case len(channels):
					// Notify application when all channels are subscribed.
					if err := onStart(); err != nil {
						done <- err
						return
					}
				case 0:
					// Return from the goroutine when all channels are unsubscribed.
					done <- nil
					return
				}
			}
		}
	}()

loop:
	for {
		select {
		case <-ctx.Done():
			break loop
		case err := <-done:
			// Return error from the receive goroutine.
			return err
		}
	}

	// Signal the receiving goroutine to exit by unsubscribing from all channels.
	if err := pubsub.Unsubscribe(ctx); err != nil {
		return err
	}

	// Wait for goroutine to complete.
	return <-done
}

func publish(ctx context.Context, rdb *redis.Client) {
	if err := rdb.Do(ctx, "PUBLISH", "c1", "hello").Err(); err != nil {
		panic(err)
	}
	if err := rdb.Do(ctx, "PUBLISH", "c2", "world").Err(); err != nil {
		panic(err)
	}
	if err := rdb.Do(ctx, "PUBLISH", "c1", "goodbye").Err(); err != nil {
		panic(err)
	}
}

// This example shows how receive pubsub notifications with cancelation.
func main() {
	var (
		ctx, cancel = context.WithCancel(context.Background())
		rdb         = redis.NewClient(&redis.Options{
			Addr: "localhost:6379",
		})
	)

	err := listenPubSubChannels(ctx, rdb,
		func() error {
			// The start callback is a good place to backfill missed
			// notifications. For the purpose of this example, a goroutine is
			// started to send notifications.
			go publish(ctx, rdb)
			return nil
		},
		func(channel string, payload string) error {
			fmt.Println("received", payload, "from", channel)

			// For the purpose of this example, cancel the listener's context
			// after receiving last message sent by publish().
			if payload == "goodbye" {
				cancel()
			}
			return nil
		},
		"c1", "c2")
	if err != nil {
		panic(err)
	}

}
Output:

received hello from c1
received world from c2
received goodbye from c1

func (*PubSub) Close

func (p *PubSub) Close() error

func (*PubSub) PSubscribe

func (p *PubSub) PSubscribe(ctx context.Context, patterns ...string) error

PSubscribe the client to the given patterns. It returns empty subscription if there are no patterns.

func (*PubSub) PUnsubscribe

func (p *PubSub) PUnsubscribe(ctx context.Context, patterns ...string) error

PUnsubscribe the client from the given patterns, or from all of them if none is given.

func (*PubSub) Ping

func (p *PubSub) Ping(ctx context.Context, payload ...string) error

func (*PubSub) Receive

func (p *PubSub) Receive(ctx context.Context) (any, error)

Receive returns a message as a Subscription, Message, Pong or error. See PubSub example for details. This is low-level API and in most cases Channel should be used instead.

Example
pubsub := rdb.Subscribe(ctx, "mychannel2")
defer pubsub.Close()

for i := 0; i < 2; i++ {
	// ReceiveTimeout is a low level API. Use ReceiveMessage instead.
	msgi, err := pubsub.ReceiveTimeout(1 * time.Second)
	if err != nil {
		panic(err)
	}

	switch msg := msgi.(type) {
	case *redis.Subscription:
		fmt.Println("subscribed to", msg.Channel)

		_, err := rdb.Do(ctx, "PUBLISH", "mychannel2", "hello").Int()
		if err != nil {
			panic(err)
		}
	case *redis.Message:
		fmt.Println("received", msg.Payload, "from", msg.Channel)
	default:
		panic("unreached")
	}
}
Output:

subscribed to mychannel2
received hello from mychannel2

func (*PubSub) ReceiveMessage

func (p *PubSub) ReceiveMessage(ctx context.Context) (*Message, error)

ReceiveMessage returns a Message or error ignoring Subscription and Pong messages. This is low-level API and in most cases Channel should be used instead.

func (*PubSub) ReceiveTimeout

func (p *PubSub) ReceiveTimeout(timeout time.Duration) (any, error)

ReceiveTimeout acts like Receive but returns an error if message is not received in time. This is low-level API and in most cases Channel should be used instead.

func (*PubSub) String

func (p *PubSub) String() string

func (*PubSub) Subscribe

func (p *PubSub) Subscribe(ctx context.Context, channels ...string) error

Subscribe the client to the specified channels. It returns empty subscription if there are no channels.

func (*PubSub) Unsubscribe

func (p *PubSub) Unsubscribe(ctx context.Context, channels ...string) error

Unsubscribe the client from the given channels, or from all of them if none is given.

type Result

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

func NewResult

func NewResult(reply any, err error) *Result

NewResult returns a Result initialised with reply and err.

func (*Result) Bool

func (r *Result) Bool() (bool, error)
Example
err := rdb.Do(ctx, "SET", "foo", 1).Err()
if err != nil {
	panic(err)
}
exists, err := rdb.Do(ctx, "EXISTS", "foo").Bool()
if err != nil {
	panic(err)
}
fmt.Printf("%t\n", exists)
Output:

true

func (*Result) Bools

func (r *Result) Bools() ([]bool, error)

func (*Result) ByteSlices

func (r *Result) ByteSlices() ([][]byte, error)

func (*Result) Bytes

func (r *Result) Bytes() ([]byte, error)

func (*Result) Err

func (r *Result) Err() error

func (*Result) Float64

func (r *Result) Float64() (float64, error)

func (*Result) Float64Map

func (r *Result) Float64Map() (map[string]float64, error)

func (*Result) Float64s

func (r *Result) Float64s() ([]float64, error)

func (*Result) Int

func (r *Result) Int() (int, error)
Example
err := rdb.Do(ctx, "SET", "k1", 1).Err()
if err != nil {
	panic(err)
}

n, err := rdb.Do(ctx, "GET", "k1").Int()
if err != nil {
	panic(err)
}
fmt.Printf("%d\n", n)

n, err = rdb.Do(ctx, "INCR", "k1").Int()
if err != nil {
	panic(err)
}
fmt.Printf("%d\n", n)
Output:

1
2

func (*Result) Int64

func (r *Result) Int64() (int64, error)

func (*Result) Int64Map

func (r *Result) Int64Map() (map[string]int64, error)

func (*Result) Int64s

func (r *Result) Int64s() ([]int64, error)

func (*Result) IntMap

func (r *Result) IntMap() (map[string]int, error)

func (*Result) Ints

func (r *Result) Ints() ([]int, error)
Example
err := rdb.Do(ctx, "SADD", "set_with_integers", 4, 5, 6).Err()
if err != nil {
	panic(err)
}
ints, err := rdb.Do(ctx, "SMEMBERS", "set_with_integers").Ints()
if err != nil {
	panic(err)
}
fmt.Printf("%v\n", ints)
Output:

[4 5 6]

func (*Result) Positions

func (r *Result) Positions() ([]*[2]float64, error)

func (*Result) Scan

func (r *Result) Scan(dest ...any) error
Example
err := rdb.Do(ctx, "DEL", "album:1", "album:2", "album:3", "albums").Err()
if err != nil {
	panic(err)
}

if _, err := rdb.Pipelined(ctx, func(pipe redis.Pipeliner) error {
	pipe.Send("HSET", "album:1", "title", "Red", "rating", 5)
	pipe.Send("HSET", "album:2", "title", "Earthbound", "rating", 1)
	pipe.Send("HSET", "album:3", "title", "Beat")
	pipe.Send("LPUSH", "albums", "1", "2", "3")
	return nil
}); err != nil {
	panic(err)
}

res := rdb.Do(ctx, "SORT", "albums",
	"BY", "album:*->rating",
	"GET", "album:*->title",
	"GET", "album:*->rating",
)
if err := res.Err(); err != nil {
	panic(err)
}

loop:
for {
	var title string
	rating := -1 // initialize to illegal value to detect nil.
	err = res.Scan(&title, &rating)
	switch err {
	default:
		panic(err)
	case redis.ErrValuesExhausted:
		break loop
	case nil:
	}

	if rating == -1 {
		fmt.Println(title, "not-rated")
	} else {
		fmt.Println(title, rating)
	}
}
Output:

Beat not-rated
Earthbound 1
Red 5

func (*Result) ScanSlice

func (r *Result) ScanSlice(dest any, fieldNames ...string) error
Example
err := rdb.Do(ctx, "DEL", "album:1", "album:2", "album:3", "albums").Err()
if err != nil {
	panic(err)
}

if _, err := rdb.Pipelined(ctx, func(pipe redis.Pipeliner) error {
	pipe.Send("HSET", "album:1", "title", "Red", "rating", 5)
	pipe.Send("HSET", "album:2", "title", "Earthbound", "rating", 1)
	pipe.Send("HSET", "album:3", "title", "Beat", "rating", 4)
	pipe.Send("LPUSH", "albums", "1", "2", "3")
	return nil
}); err != nil {
	panic(err)
}

res := rdb.Do(ctx, "SORT", "albums",
	"BY", "album:*->rating",
	"GET", "album:*->title",
	"GET", "album:*->rating",
)
if err := res.Err(); err != nil {
	panic(err)
}

var albums []struct {
	Title  string
	Rating int
}
if err := res.ScanSlice(&albums); err != nil {
	panic(err)
}
fmt.Printf("%v\n", albums)
Output:

[{Earthbound 1} {Beat 4} {Red 5}]

func (*Result) ScanStruct

func (r *Result) ScanStruct(dest any) error
Example
err := rdb.Do(ctx, "DEL", "album:1").Err()
if err != nil {
	panic(err)
}
if err := rdb.Do(ctx, "HSET", "album:1",
	"title", "Electric Ladyland",
	"artist", "Jimi Hendrix",
	"price", 4.95,
	"likes", 8,
).Err(); err != nil {
	panic(err)
}

res := rdb.Do(ctx, "HGETALL", "album:1")
if err := res.Err(); err != nil {
	panic(err)
}

type Album struct {
	Title  string  `redis:"title"`
	Artist string  `redis:"artist"`
	Price  float64 `redis:"price"`
	Likes  int     `redis:"likes"`
}

ab := new(Album)
if err := res.ScanStruct(ab); err != nil {
	panic(err)
}
fmt.Printf("%+v\n", ab)
Output:

&{Title:Electric Ladyland Artist:Jimi Hendrix Price:4.95 Likes:8}

func (*Result) String

func (r *Result) String() (string, error)
Example
err := rdb.Do(ctx, "SET", "hello", "world").Err()
if err != nil {
	panic(err)
}
s, err := rdb.Do(ctx, "GET", "hello").String()
if err != nil {
	panic(err)
}
fmt.Printf("%s\n", s)
Output:

world

func (*Result) StringMap

func (r *Result) StringMap() (map[string]string, error)

func (*Result) Strings

func (r *Result) Strings() ([]string, error)

func (*Result) Uint64

func (r *Result) Uint64() (uint64, error)

func (*Result) Uint64Map

func (r *Result) Uint64Map() (map[string]uint64, error)

func (*Result) Uint64s

func (r *Result) Uint64s() ([]uint64, error)

func (*Result) Val

func (r *Result) Val() any

func (*Result) Values

func (r *Result) Values() ([]any, error)

type Script

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

Script encapsulates the source and hash for a Lua script.

Example
incrByXX := redis.NewScript(`
		if not redis.call("GET", KEYS[1]) then
			return false
		end
		return redis.call("INCRBY", KEYS[1], ARGV[1])
	`)

if err := rdb.Do(ctx, "DEL", "xx_counter").Err(); err != nil {
	panic(err)
}

n, err := incrByXX.Run(ctx, rdb, []string{"xx_counter"}, 2).Int()
fmt.Println(n, err)

err = rdb.Do(ctx, "SET", "xx_counter", "40").Err()
if err != nil {
	panic(err)
}

n, err = incrByXX.Run(ctx, rdb, []string{"xx_counter"}, 2).Int()
fmt.Println(n, err)
Output:

0 redis: nil
42 <nil>

func NewScript

func NewScript(src string) *Script

func (*Script) Exists

func (s *Script) Exists(ctx context.Context, c Scripter) *Result

func (*Script) Hash

func (s *Script) Hash() string

func (*Script) Load

func (s *Script) Load(ctx context.Context, c Scripter) *Result

func (*Script) Run

func (s *Script) Run(ctx context.Context, c Scripter, keys []string, args ...any) *Result

Run optimistically uses EVALSHA to run the script. If script does not exist it is retried using EVAL.

func (*Script) RunRO

func (s *Script) RunRO(ctx context.Context, c Scripter, keys []string, args ...any) *Result

RunRO optimistically uses EVALSHA_RO to run the script. If script does not exist it is retried using EVAL_RO.

type Scripter

type Scripter interface {
	Eval(ctx context.Context, script string, keys []string, args ...any) *Result
	EvalSha(ctx context.Context, sha1 string, keys []string, args ...any) *Result
	EvalRO(ctx context.Context, script string, keys []string, args ...any) *Result
	EvalShaRO(ctx context.Context, sha1 string, keys []string, args ...any) *Result
	ScriptExists(ctx context.Context, hashes ...string) *Result
	ScriptLoad(ctx context.Context, script string) *Result
}

type Subscription

type Subscription struct {
	// Can be "subscribe", "unsubscribe", "psubscribe" or "punsubscribe".
	Kind string
	// Channel name we have subscribed to.
	Channel string
	// Number of channels we are currently subscribed to.
	Count int
}

Subscription received after a successful subscription to channel.

func (*Subscription) String

func (s *Subscription) String() string

type Tx

type Tx struct {
	*Conn
}

Tx implements Redis transactions as described in http://redis.io/topics/transactions. It's NOT safe for concurrent use by multiple goroutines, because Exec resets list of watched keys.

If you don't need WATCH, use Pipeline instead.

func (*Tx) Close

func (t *Tx) Close(ctx context.Context) error

func (*Tx) Unwatch

func (t *Tx) Unwatch(ctx context.Context, keys ...string) *Result

func (*Tx) Watch

func (t *Tx) Watch(ctx context.Context, keys ...string) *Result

Jump to

Keyboard shortcuts

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