Skip to content

Commit 9403d57

Browse files
committedSep 22, 2012
first commit
0 parents  commit 9403d57

File tree

5 files changed

+1718
-0
lines changed

5 files changed

+1718
-0
lines changed
 

‎.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
test

‎LICENSE.txt

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
Copyright 2011-2012 The "Radix" Authors. All rights reserved.
2+
3+
BSD 2-Clause License
4+
5+
Redistribution and use in source and binary forms, with or without
6+
modification, are permitted provided that the following conditions are met:
7+
8+
* Redistributions of source code must retain the above copyright notice,
9+
this list of conditions and the following disclaimer.
10+
* Redistributions in binary form must reproduce the above copyright notice,
11+
this list of conditions and the following disclaimer in the documentation
12+
and/or other materials provided with the distribution.
13+
14+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17+
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
18+
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19+
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22+
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23+
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24+
POSSIBILITY OF SUCH DAMAGE.

‎README.md

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Go Redis Cluster
2+
3+
A [Redis](http://redis.io/) [Cluster](http://redis.io/topics/cluster-spec) Client for the Go Programming Language.
4+
5+
(Well, not really. It's only a thin wrapper above the amazing [Radix](https://github.com/fzzbt/radix/) module. It even uses the Radix module, borrows heavily from it, and the Radix guys did 99% of the work. This is only a thin wrapper that discovers your cluster, connects to all the nodes, and does client-side sharding.)
6+
7+
## Installing it
8+
9+
Since this is a wrapper above Radix, you'll need to install it:
10+
```
11+
go get github.com/fzzbt/radix/redis
12+
```
13+
And then you can install Go Redis Cluster:
14+
```
15+
go get github.com/joaojeronimo/go-redis-cluster
16+
```
17+
18+
## Using it
19+
20+
You'll need the link of one member of the cluster, and the rest will be automatically discovered, and everything will be dealt with (slots, etc). After that, you can use it pretty much like you use the [Radix](https://github.com/fzzbt/radix/) module, except you cannot use the obvious commands that (a) [affect multiple keys](http://redis.io/topics/transactions) (b) [Publish/Subscribe](http://redis.io/topics/pubsub).
21+
22+
Redis Cluster only implements a subset of the commands: "all the **single keys** commands available in the non distributed version of Redis" (read more about this in the **Implemented subset** section of the [Cluster Spec](http://redis.io/topics/cluster-spec))
23+
24+
Other than that, it's almost exactly the same as using the Radix module, even the commands' names are the same:
25+
26+
```go
27+
package main
28+
29+
import (
30+
"github.com/joaojeronimo/go-redis-cluster"
31+
)
32+
33+
func main() {
34+
cc := rediscluster.NewCluster("localhost:6379") // Connect to the whole cluster only knowing the link of one member
35+
defer cc.Close() // Close it when main returns
36+
37+
// the usual commands:
38+
cc.Hset("someHash", "someKey", "someValue")
39+
theValue := cc.Hget("someHash", "someKey").String()
40+
if theValue == "someValue" {
41+
println("OK")
42+
}
43+
44+
// Async stuff too:
45+
cc.Sadd("someSet", "value1", "value2", "value3")
46+
future := cc.AsyncScard("someSet")
47+
card, err := (<-future).Int()
48+
if err != nil {
49+
panic(err)
50+
}
51+
if 3 == card {
52+
println("OK")
53+
}
54+
}
55+
```
56+
57+
## Other stuff
58+
59+
I also created a [similar module for Node.JS](https://github.com/joaojeronimo/node_redis_cluster), we use both of them in production, as well as Redis 3.0 that is unstable but passes the tests. So far so good :)

‎cluster.go

+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package rediscluster
2+
3+
import (
4+
"github.com/fzzbt/radix/redis"
5+
"github.com/joaojeronimo/go-crc16"
6+
"strconv"
7+
"strings"
8+
)
9+
10+
func clusterSlot(key string) int {
11+
return (int(crc16.Crc16(key)) % 4096)
12+
}
13+
14+
type SlotInterval struct {
15+
LowerLimit int
16+
UpperLimit int
17+
}
18+
19+
func ParseNode(unparsedNode string) (node Node) {
20+
parts := strings.Split(unparsedNode, " ")
21+
node.Name = parts[0]
22+
node.Address = parts[1]
23+
node.Flags = parts[2]
24+
//node.LastPingSent, _ = strconv.Atoi(parts[4])
25+
//node.LastPongReceived, _ = strconv.Atoi(parts[5])
26+
node.State = parts[6]
27+
28+
slots := strings.Split(parts[7], "-")
29+
var slotInterval SlotInterval
30+
slotInterval.LowerLimit, _ = strconv.Atoi(slots[0])
31+
slotInterval.UpperLimit, _ = strconv.Atoi(slots[1])
32+
node.Slots = slotInterval
33+
return
34+
}
35+
36+
type Node struct {
37+
Name string
38+
Address string
39+
Client *redis.Client
40+
Flags string
41+
LastPingSent int
42+
LastPongReceived int
43+
State string
44+
Slots SlotInterval
45+
}
46+
47+
type Cluster struct {
48+
nodes []Node
49+
}
50+
51+
func (n *Node) Connect() {
52+
conf := redis.DefaultConfig()
53+
conf.Address = n.Address
54+
n.Client = redis.NewClient(conf)
55+
}
56+
57+
func discoverNodes(firstLink string) (cluster Cluster) {
58+
conf := redis.DefaultConfig()
59+
conf.Address = firstLink
60+
c := redis.NewClient(conf)
61+
s, err := c.Cluster("nodes").Str()
62+
if err != nil {
63+
return
64+
}
65+
unparsedNodes := strings.Split(s, "\n")
66+
var parsedNodes []Node
67+
for i := 0; i < len(unparsedNodes)-1; i++ { // -1 because the last line is empty
68+
parsedNode := ParseNode(unparsedNodes[i])
69+
if parsedNode.Address == ":0" {
70+
parsedNode.Address = firstLink
71+
parsedNode.Client = c
72+
} else {
73+
parsedNode.Connect()
74+
}
75+
parsedNodes = append(parsedNodes, parsedNode)
76+
}
77+
cluster.nodes = parsedNodes
78+
return
79+
}
80+
81+
func NewCluster(firstLink string) (cc Cluster) {
82+
return discoverNodes(firstLink)
83+
}
84+
85+
func (cc *Cluster) Call(command string, args ...interface{}) (reply *redis.Reply) {
86+
slot := clusterSlot(args[0].(string))
87+
var slots SlotInterval
88+
for i := 0; i < len(cc.nodes); i++ {
89+
slots = cc.nodes[i].Slots
90+
if slot > slots.LowerLimit && slot < slots.UpperLimit {
91+
reply = cc.nodes[i].Client.Call(command, args...)
92+
return
93+
}
94+
}
95+
return
96+
}
97+
98+
func (cc *Cluster) AsyncCall(command string, args ...interface{}) (future redis.Future) {
99+
slot := clusterSlot(args[0].(string))
100+
var slots SlotInterval
101+
for i := 0; i < len(cc.nodes); i++ {
102+
slots = cc.nodes[i].Slots
103+
if slot > slots.LowerLimit && slot < slots.UpperLimit {
104+
future = cc.nodes[i].Client.AsyncCall(command, args...)
105+
return
106+
}
107+
}
108+
return
109+
}
110+
111+
func (cc *Cluster) Close() bool {
112+
for i := 0; i < len(cc.nodes); i++ {
113+
cc.nodes[i].Client.Close()
114+
}
115+
return true
116+
}

‎cmd.go

+1,518
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)
Please sign in to comment.