redis/v2
Vladimir Mihailenco 75c8dbf91d Go 1.0 compatibility. 2013-12-30 14:29:28 +02:00
..
README.md readme: fix typo. 2013-12-30 13:12:36 +02:00
command.go Make tests less flaky. 2013-12-30 14:24:43 +02:00
commands.go v2: use time package primitives where possible. 2013-10-15 16:47:12 +03:00
doc.go Remove examples from godoc. 2013-09-29 11:48:45 +03:00
example_test.go Change redis.Nil value. 2013-11-04 10:08:03 +02:00
export_test.go Make Script and Pipeline interoperable. Fixes #18. 2013-12-30 13:02:14 +02:00
multi.go Make Script and Pipeline interoperable. Fixes #18. 2013-12-30 13:02:14 +02:00
parser.go Go 1.0 compatibility. 2013-12-30 14:29:28 +02:00
pipeline.go Add ErrTxFailed. Small tweaks. 2013-09-29 12:41:04 +03:00
pool.go Make Script and Pipeline interoperable. Fixes #18. 2013-12-30 13:02:14 +02:00
pubsub.go Go 1.0 compatibility. 2013-12-30 14:29:28 +02:00
redis.go Document Close. 2013-11-10 10:06:33 +02:00
redis_test.go Make tests less flaky. 2013-12-30 14:24:43 +02:00
script.go Make Script and Pipeline interoperable. Fixes #18. 2013-12-30 13:02:14 +02:00

README.md

Redis client for Golang

Supports:

  • Redis 2.6 commands except QUIT, MONITOR, SLOWLOG and SYNC.
  • Pub/sub.
  • Transactions.
  • Pipelining.
  • Connection pool.
  • TLS connections.
  • Thread safety.
  • Timeouts.

API docs: http://godoc.org/github.com/vmihailenco/redis/v2. Examples: http://godoc.org/github.com/vmihailenco/redis/v2#pkg-examples.

Installation

Install:

go get github.com/vmihailenco/redis/v2

Upgrading from previous version

Type system should catch most changes. But you have to manually change SetEx, PSetEx, Expire and PExpire to use time.Duration instead of int64.

Getting started

Let's start with connecting to Redis using TCP:

client := redis.NewTCPClient(&redis.Options{
	Addr:     "localhost:6379",
	Password: "", // no password set
	DB:       0,  // use default DB
})
defer client.Close()

ping := client.Ping()
fmt.Println(ping.Val(), ping.Err())
// Output: PONG <nil>

or using Unix socket:

client := redis.NewUnixClient(&redis.Options{
	Addr: "/tmp/redis.sock",
})
defer client.Close()

ping := client.Ping()
fmt.Println(ping.Val(), ping.Err())
// Output: PONG <nil>

Then we can start sending commands:

set := client.Set("foo", "bar")
fmt.Println(set.Err(), set.Val())

get := client.Get("foo")
fmt.Println(get.Err(), get.Val())

// Output: <nil> OK
// <nil> bar

We can also pipeline two commands together:

pipeline := client.Pipeline()
set := pipeline.Set("key1", "hello1")
get := pipeline.Get("key2")
cmds, err := pipeline.Exec()
fmt.Println(cmds, err)
fmt.Println(set)
fmt.Println(get)
// Output: [SET key1 hello1: OK GET key2: (nil)] (nil)
// SET key1 hello1: OK
// GET key2: (nil)

or:

client := redis.NewTCPClient(&redis.Options{
	Addr: ":6379",
})
defer client.Close()

cmds, err := client.Pipelined(func(c *redis.Pipeline) {
	c.Set("key1", "hello1")
	c.Get("key2")
})
fmt.Println(cmds, err)
// Output: [SET key1 hello1: OK GET key2: (nil)] (nil)

We can also send several commands in transaction:

incr := func(tx *redis.Multi) ([]redis.Cmder, error) {
	get := tx.Get("key")
	if err := get.Err(); err != nil && err != redis.Nil {
		return nil, err
	}

	val, _ := strconv.ParseInt(get.Val(), 10, 64)

	return tx.Exec(func() {
		tx.Set("key", strconv.FormatInt(val+1, 10))
	})
}

client := redis.NewTCPClient(&redis.Options{
	Addr: ":6379",
})
defer client.Close()

client.Del("key")

tx := client.Multi()
defer tx.Close()

watch := tx.Watch("key")
_ = watch.Err()

for {
	cmds, err := incr(tx)
	if err == redis.TxFailedErr {
		continue
	} else if err != nil {
		panic(err)
	}
	fmt.Println(cmds, err)
	break
}

// Output: [SET key 1: OK] <nil>

To subscribe to the channel:

pubsub := client.PubSub()
defer pubsub.Close()

err := pubsub.Subscribe("mychannel")
_ = err

msg, err := pubsub.Receive()
fmt.Println(msg, err)

pub := client.Publish("mychannel", "hello")
_ = pub.Err()

msg, err = pubsub.Receive()
fmt.Println(msg, err)

// Output: &{subscribe mychannel 1} <nil>
// &{mychannel hello} <nil>

To use Lua scripting:

client := redis.NewTCPClient(&redis.Options{
	Addr: ":6379",
})
defer client.Close()

setnx := redis.NewScript(`
    if redis.call("get", KEYS[1]) == false then
        redis.call("set", KEYS[1], ARGV[1])
        return 1
    end
    return 0
`)

run1 := setnx.Run(client, []string{"keynx"}, []string{"foo"})
fmt.Println(run1.Val().(int64), run1.Err())

run2 := setnx.Run(client, []string{"keynx"}, []string{"bar"})
fmt.Println(run2.Val().(int64), run2.Err())

get := client.Get("keynx")
fmt.Println(get)

// Output: 1 <nil>
// 0 <nil>
// GET keynx: foo

You can also write custom commands:

Get := func(client *redis.Client, key string) *redis.StringCmd {
	cmd := redis.NewStringCmd("GET", key)
	client.Process(cmd)
	return cmd
}

client := redis.NewTCPClient(&redis.Options{
	Addr: ":6379",
})
defer client.Close()

get := Get(client, "key_does_not_exist")
fmt.Printf("%q %s", get.Val(), get.Err())
// Output: "" (nil)

Look and feel

Some corner cases:

SORT list LIMIT 0 2 ASC
client.Sort("list", redis.Sort{Offset: 0, Count: 2, Order: "ASC"})

ZRANGEBYSCORE zset -inf +inf WITHSCORES LIMIT 0 2
client.ZRangeByScoreWithScores("zset", redis.ZRangeByScore{
    Min: "-inf",
    Max: "+inf",
    Offset: 0,
    Count: 2,
})

ZINTERSTORE out 2 zset1 zset2 WEIGHTS 2 3 AGGREGATE SUM
client.ZInterStore("out", redis.ZStore{Weights: []int64{2, 3}}, "zset1", "zset2")

EVAL "return {KEYS[1],ARGV[1]}" 1 "key" "hello"
client.Eval("return {KEYS[1],ARGV[1]}", []string{"key"}, []string{"hello"})

Contributing

Configure Redis to allow maximum 10 clients:

maxclients 10

Run tests:

go test -gocheck.v

Run benchmarks:

go test -gocheck.b