├── LICENSE
├── Makefile
├── README.md
├── go.mod
├── go.sum
├── logo.png
└── main.go
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License (MIT)
2 |
3 | Copyright (c) 2016 Josh Baker
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | all:
2 | go build -o redraft main.go
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 | RedRaft is an experimental project that allows for [Redis](http://redis.io) to run in a [Raft](https://raft.github.io/) environment. It uses [Finn](https://github.com/tidwall/finn) for the Raft implementation and Redis for the state-machine providing a fault-tolerant cluster.
9 |
10 | Getting Started
11 | ---------------
12 |
13 | ### Building
14 |
15 | 1. Install [Redis](http://redis.io/download).
16 |
17 | 2. Build RedRaft
18 |
19 | ```sh
20 | $ git clone https://github.com/tidwall/redraft
21 | $ cd redraft
22 | $ make
23 | ```
24 |
25 | Optionally you can download a prebuilt [zip](https://github.com/tidwall/redraft/releases/download/demo/redraft-demo-darwin-amd64.zip) for Mac OS which includes Redis and RedRaft.
26 |
27 | ### Running
28 |
29 | First start a single-member cluster:
30 | ```
31 | $ ./redraft
32 | ```
33 |
34 | This will start the server listening on port 7481 for client and server-to-server communication.
35 |
36 | Next, let's set a single key, and then retrieve it:
37 |
38 | ```
39 | $ redis-cli -p 7481 SET mykey "my value"
40 | OK
41 | $ redis-cli -p 7481 GET mykey
42 | "my value"
43 | ```
44 |
45 | Adding members:
46 | ```
47 | $ ./redraft -p 7482 -dir data2 -join 7481
48 | $ ./redraft -p 7483 -dir data3 -join 7481
49 | ```
50 |
51 | That's it. Now if node1 goes down, node2 and node3 will continue to operate.
52 |
53 |
54 | Supported Commands
55 | ------------------
56 | get, strlen, exists, getbit, getrange, substr, mget,
57 | llen, lindex, lrange, sismember, scard, srandmember,
58 | sinter, sunion, sdiff, smembers, sscan, zrange,
59 | zrangebyscore, zrevrangebyscore, zrangebylex, zrevrangebylex,
60 | zcount, zlexcount, zrevrange, zcard, zscore, zrank,
61 | zrevrank, zscan, hget, hmget, hlen, hstrlen, hkeys,
62 | hvals, hgetall, hexists, hscan, randomkey, keys, scan,
63 | dbsize, echo, type, info, ttl, pttl, dump, object,
64 | memory, time, bitpos, bitcount, geohash, geopos, geodist,
65 | pfcount, set, setnx, setex, psetex, append, del, setbit,
66 | bitfield, setrange, incr, decr, rpush, lpush, rpushx,
67 | lpushx, linsert, rpop, lpop, brpop, brpoplpush, blpop,
68 | lset, ltrim, lrem, rpoplpush, sadd, srem, smove, spop,
69 | sinterstore, sunionstore, sdiffstore, zadd, zincrby, zrem,
70 | zremrangebyscore, zremrangebyrank, zremrangebylex, zunionstore,
71 | zinterstore, hset, hsetnx, hmset, hincrby, hincrbyfloat,
72 | hdel, incrby, decrby, incrbyfloat, getset, mset, msetnx,
73 | select, move, rename, renamenx, expire, expireat, pexpire,
74 | pexpireat, auth, flushdb, flushall, sort, persist, config,
75 | restore, migrate, bitop, geoadd, georadius,
76 | georadiusbymember, pfadd, pfmerge:
77 |
78 | Not Supported
79 | -------------
80 | Cluster, Replication, and PubSub commands.
81 |
82 | Built-in Raft Commands
83 | ----------------------
84 | Here are a few commands for monitoring and managing the cluster:
85 |
86 | - **RAFTADDPEER addr**
87 | Adds a new member to the Raft cluster
88 | - **RAFTREMOVEPEER addr**
89 | Removes an existing member
90 | - **RAFTLEADER**
91 | Returns the Raft leader, if known
92 | - **RAFTSNAPSHOT**
93 | Triggers a snapshot operation
94 | - **RAFTSTATE**
95 | Returns the state of the node
96 | - **RAFTSTATS**
97 | Returns information and statistics for the node and cluster
98 |
99 | Read Consistency
100 | ----------------
101 |
102 | The `--consistency` param has the following options:
103 |
104 | - `low` - all nodes accept reads, small risk of [stale](http://stackoverflow.com/questions/1563319/what-is-stale-state) data
105 | - `medium` - only the leader accepts reads, itty-bitty risk of stale data during a leadership change
106 | - `high` - only the leader accepts reads, the raft log index is incremented to guaranteeing no stale data
107 |
108 | For example, setting the following options:
109 |
110 | ```
111 | $ ./redraft --consistency high
112 | ```
113 |
114 | Provides the highest level of consistency.
115 |
116 | Leadership Changes
117 | ------------------
118 |
119 | In a Raft cluster only the leader can apply commands. If a command is attempted on a follower you will be presented with the response:
120 |
121 | ```
122 | SET x y
123 | -TRY 127.0.0.1:7481
124 | ```
125 |
126 | This means you should try the same command at the specified address.
127 |
128 | Contact
129 | -------
130 | Josh Baker [@tidwall](http://twitter.com/tidwall)
131 |
132 | License
133 | -------
134 | RedRaft source code is available under the MIT [License](/LICENSE).
135 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/tidwall/redraft
2 |
3 | go 1.14
4 |
5 | require (
6 | github.com/tidwall/finn v0.1.2
7 | github.com/tidwall/raft-redcon v0.1.0
8 | github.com/tidwall/redcon v1.0.0
9 | github.com/tidwall/redlog v0.0.0-20180507234857-bbed90f29893
10 | )
11 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
2 | github.com/armon/go-metrics v0.3.0 h1:B7AQgHi8QSEi4uHu7Sbsga+IJDU+CENgjxoo81vDUqU=
3 | github.com/armon/go-metrics v0.3.0/go.mod h1:zXjbSimjXTd7vOpY8B0/2LpvNvDoXBuplAD+gJD3GYs=
4 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
5 | github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
6 | github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
7 | github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
8 | github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
9 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
10 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
11 | github.com/garyburd/redigo v1.6.0 h1:0VruCpn7yAIIu7pWVClQC8wxCJEcG3nyzpMSHKi1PQc=
12 | github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
13 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
14 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
15 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
16 | github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
17 | github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0=
18 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
19 | github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI=
20 | github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
21 | github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
22 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
23 | github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
24 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
25 | github.com/hashicorp/raft v0.1.0 h1:OC+j7LWkv7x8s9c5wnXCEgtP1J0LDw2fKNxUiYCZFNo=
26 | github.com/hashicorp/raft v0.1.0/go.mod h1:DVSAWItjLjTOkVbSpWQ0j0kUADIvDaCtBxIcbNAQLkI=
27 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
28 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
29 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
30 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
31 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
32 | github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
33 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
34 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
35 | github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
36 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
37 | github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
38 | github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
39 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
40 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
41 | github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
42 | github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
43 | github.com/tidwall/finn v0.1.2 h1:gSRpZwQHmzwhYPRKoYswBqMcsnVvWwOsOnBO6Fu/ceA=
44 | github.com/tidwall/finn v0.1.2/go.mod h1:u5JsUQ5LMBO3rOd9OGp17BphJ89hnyOTvPN+qMBAdCs=
45 | github.com/tidwall/raft-boltdb v0.0.0-20160909211738-25b87f2c5677 h1:8FkXr+GCV4wb8WAct/V1vKB/Ivy11Y+fm919EHgdfWA=
46 | github.com/tidwall/raft-boltdb v0.0.0-20160909211738-25b87f2c5677/go.mod h1:O7b2tvwZmC+IFu8djLOZj0jc/tjssDPiJ8xIt+U2jTU=
47 | github.com/tidwall/raft-fastlog v0.0.0-20160922202426-2f0d0a0ce558 h1:hQYEIfMzrH6LRzjz7Jp5Rv8jrty1bAR5M0DjOYSxxks=
48 | github.com/tidwall/raft-fastlog v0.0.0-20160922202426-2f0d0a0ce558/go.mod h1:KNwBhka/a5Ucw5bfEzKHTEKuCO2Do1tKs+kDdu3Sbb4=
49 | github.com/tidwall/raft-leveldb v0.0.0-20170127185243-ada471496dc9 h1:Z5QMqF/MSuvnrTibHqs/xx+ZE5gypLV02YU8Ry4kJ7A=
50 | github.com/tidwall/raft-leveldb v0.0.0-20170127185243-ada471496dc9/go.mod h1:KNAMyK8s/oUOTbIL/T07fTL6/EgJfHhK8XeeEPq35eU=
51 | github.com/tidwall/raft-redcon v0.1.0 h1:qwYaFaAVNFleY2EFm0j7UK4vEpoNa19ohH7U4idbg+s=
52 | github.com/tidwall/raft-redcon v0.1.0/go.mod h1:YhoECfJs8MXbrwak9H7wKYDMZ3rMaB7el7zZ7MRw9Xw=
53 | github.com/tidwall/redcon v1.0.0 h1:D4AzzJ81Afeh144fgnj5H0aSVPBBJ5RI9Rzj0zThU+E=
54 | github.com/tidwall/redcon v1.0.0/go.mod h1:bdYBm4rlcWpst2XMwKVzWDF9CoUxEbUmM7CQrKeOZas=
55 | github.com/tidwall/redlog v0.0.0-20180507234857-bbed90f29893 h1:aGyVYs0o1pThR9i+SuYCG/VqWibHkUXl9kIMZGhAXDw=
56 | github.com/tidwall/redlog v0.0.0-20180507234857-bbed90f29893/go.mod h1:NssoNA+Uwqd5WHKkVwAzO7AT6VuG3wiC8r5nBqds3Ao=
57 | github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
58 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
59 | golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g=
60 | golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
61 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
62 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
63 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
64 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
65 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
66 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
67 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
68 | golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
69 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
70 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
71 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
72 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
73 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
74 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
75 |
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tidwall/redraft/47c684f77f9cc9b43cad52ad70af5ac14d357e78/logo.png
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bufio"
5 | "errors"
6 | "flag"
7 | "fmt"
8 | "io"
9 | "io/ioutil"
10 | "log"
11 | "net"
12 | "os"
13 | "os/exec"
14 | "os/signal"
15 | "path"
16 | "strings"
17 | "sync"
18 | "syscall"
19 | "time"
20 |
21 | "github.com/tidwall/finn"
22 | "github.com/tidwall/raft-redcon"
23 | "github.com/tidwall/redcon"
24 | "github.com/tidwall/redlog"
25 | )
26 |
27 | func main() {
28 | var port int
29 | var backend string
30 | var durability string
31 | var consistency string
32 | var loglevel string
33 | var join string
34 | var dir string
35 |
36 | flag.IntVar(&port, "p", 7481, "Bind port")
37 | flag.StringVar(&backend, "backend", "fastlog", "Raft log backend [fastlog,bolt,inmem]")
38 | flag.StringVar(&durability, "durability", "medium", "Log durability [low,medium,high]")
39 | flag.StringVar(&consistency, "consistency", "medium", "Raft consistency [low,medium,high]")
40 | flag.StringVar(&loglevel, "loglevel", "notice", "Log level [quiet,warning,notice,verbose,debug]")
41 | flag.StringVar(&dir, "dir", "data", "Data directory")
42 | flag.StringVar(&join, "join", "", "Join a cluster by providing an address")
43 | flag.Parse()
44 |
45 | var opts finn.Options
46 |
47 | switch strings.ToLower(backend) {
48 | default:
49 | log.Fatalf("invalid backend '%v'", backend)
50 | case "fastlog":
51 | opts.Backend = finn.FastLog
52 | case "bolt":
53 | opts.Backend = finn.Bolt
54 | case "inmem":
55 | opts.Backend = finn.InMem
56 | }
57 | switch strings.ToLower(durability) {
58 | default:
59 | log.Fatalf("invalid durability '%v'", durability)
60 | case "low":
61 | opts.Durability = finn.Low
62 | case "medium":
63 | opts.Durability = finn.Medium
64 | case "high":
65 | opts.Durability = finn.High
66 | }
67 | switch strings.ToLower(consistency) {
68 | default:
69 | log.Fatalf("invalid consistency '%v'", consistency)
70 | case "low":
71 | opts.Consistency = finn.Low
72 | case "medium":
73 | opts.Consistency = finn.Medium
74 | case "high":
75 | opts.Consistency = finn.High
76 | }
77 | opts.LogOutput = os.Stdout
78 | switch strings.ToLower(loglevel) {
79 | default:
80 | log.Fatalf("invalid loglevel '%v'", loglevel)
81 | case "quiet":
82 | opts.LogOutput = ioutil.Discard
83 | loglevel = "warning" // reassign for redis
84 | case "warning":
85 | opts.LogLevel = finn.Warning
86 | case "notice":
87 | opts.LogLevel = finn.Notice
88 | case "verbose":
89 | opts.LogLevel = finn.Verbose
90 | case "debug":
91 | opts.LogLevel = finn.Debug
92 | }
93 |
94 | // Store appendonly.aof and unix.sock in a temp directory
95 | tmpdir, err := ioutil.TempDir("", "redis")
96 | if err != nil {
97 | log.Fatal(err)
98 | }
99 | defer os.RemoveAll(tmpdir)
100 |
101 | // make redis executable path
102 | wd, err := os.Getwd()
103 | if err != nil {
104 | log.Fatal(err)
105 | }
106 | redisServerPath := "redis-server"
107 | fi, err := os.Stat(path.Join(wd, redisServerPath))
108 | if err == nil && !fi.IsDir() {
109 | redisServerPath = path.Join(wd, redisServerPath)
110 | }
111 |
112 | // Create the Redis command line
113 | cmd := exec.Command(redisServerPath,
114 | "--port", "0",
115 | "--bind", "127.0.0.1",
116 | "--unixsocketperm", "755",
117 | "--unixsocket", path.Join(tmpdir, "unix.sock"),
118 | "--appendonly", "yes",
119 | "--syslog-enabled", "yes",
120 | "--save", "",
121 | "--loglevel", loglevel,
122 | )
123 | cmd.Dir = tmpdir
124 | cmd.Stdout = redlog.RedisLogColorizer(opts.LogOutput)
125 | cmd.Stderr = os.Stderr
126 |
127 | // Start the Redis processes
128 | err = cmd.Start()
129 | if err != nil {
130 | log.Fatal(err)
131 | }
132 | defer cmd.Process.Kill()
133 |
134 | // Watch for forced termination such as Ctrl-C.
135 | ch := make(chan os.Signal)
136 | signal.Notify(ch, os.Interrupt, syscall.SIGTERM)
137 | go func() {
138 | <-ch
139 | // Send a SIGTERM to Redis will force the process to end and
140 | // tear down Redis and Raft.
141 | cmd.Process.Signal(syscall.SIGTERM)
142 | }()
143 |
144 | // Start up Finn
145 | rr, err := NewRedRaft(cmd, tmpdir, &opts)
146 | if err != nil {
147 | log.Fatal(err)
148 | }
149 | n, err := finn.Open(dir, fmt.Sprintf(":%d", port), join, rr, &opts)
150 | if err != nil {
151 | log.Fatal(err)
152 | }
153 | defer n.Close()
154 |
155 | // Wait for the Redis process to complete.
156 | cmd.Wait()
157 | }
158 |
159 | type RedRaft struct {
160 | tmpdir string
161 | mu sync.RWMutex
162 | rconn net.Conn
163 | rd *bufio.Reader
164 | cmd *exec.Cmd
165 | }
166 | type connCtx struct {
167 | rconn net.Conn
168 | rd *bufio.Reader
169 | }
170 |
171 | func NewRedRaft(
172 | cmd *exec.Cmd, tmpdir string, opts *finn.Options,
173 | ) (*RedRaft, error) {
174 | rr := &RedRaft{
175 | tmpdir: tmpdir,
176 | cmd: cmd,
177 | }
178 | unixSock := path.Join(tmpdir, "unix.sock")
179 | start := time.Now()
180 | for {
181 | // Interprocess connection to Redis.
182 | rconn, err := net.Dial("unix", unixSock)
183 | if err != nil {
184 | if time.Now().Sub(start) > time.Second*5 {
185 | // 5 second timeout waiting to connect.
186 | return nil, err
187 | }
188 | time.Sleep(time.Millisecond * 100)
189 | continue
190 | }
191 | rr.rconn = rconn
192 | break
193 | }
194 | rr.rd = bufio.NewReader(rr.rconn)
195 | opts.ConnAccept = func(conn redcon.Conn) bool {
196 | rconn, err := net.Dial("unix", unixSock)
197 | if err != nil {
198 | return false
199 | }
200 | conn.SetContext(&connCtx{rconn, bufio.NewReader(rconn)})
201 | return true
202 | }
203 | opts.ConnClosed = func(conn redcon.Conn, err error) {
204 | conn.Context().(*connCtx).rconn.Close()
205 | }
206 | return rr, nil
207 | }
208 |
209 | const (
210 | unknown = 0
211 | read = 1
212 | write = 2
213 | )
214 |
215 | func commandKind(cmd redcon.Command) int {
216 | switch strings.ToLower(string(cmd.Args[0])) {
217 | default:
218 | return unknown
219 | case // Read commands
220 | "get", "strlen", "exists", "getbit", "getrange", "substr", "mget",
221 | "llen", "lindex", "lrange", "sismember", "scard", "srandmember",
222 | "sinter", "sunion", "sdiff", "smembers", "sscan", "zrange",
223 | "zrangebyscore", "zrevrangebyscore", "zrangebylex", "zrevrangebylex",
224 | "zcount", "zlexcount", "zrevrange", "zcard", "zscore", "zrank",
225 | "zrevrank", "zscan", "hget", "hmget", "hlen", "hstrlen", "hkeys",
226 | "hvals", "hgetall", "hexists", "hscan", "randomkey", "keys", "scan",
227 | "dbsize", "echo", "type", "info", "ttl", "pttl", "dump", "object",
228 | "memory", "time", "bitpos", "bitcount", "geohash", "geopos", "geodist",
229 | "pfcount":
230 | return read
231 | case // Write commands
232 | "set", "setnx", "setex", "psetex", "append", "del", "setbit",
233 | "bitfield", "setrange", "incr", "decr", "rpush", "lpush", "rpushx",
234 | "lpushx", "linsert", "rpop", "lpop", "brpop", "brpoplpush", "blpop",
235 | "lset", "ltrim", "lrem", "rpoplpush", "sadd", "srem", "smove", "spop",
236 | "sinterstore", "sunionstore", "sdiffstore", "zadd", "zincrby", "zrem",
237 | "zremrangebyscore", "zremrangebyrank", "zremrangebylex", "zunionstore",
238 | "zinterstore", "hset", "hsetnx", "hmset", "hincrby", "hincrbyfloat",
239 | "hdel", "incrby", "decrby", "incrbyfloat", "getset", "mset", "msetnx",
240 | "select", "move", "rename", "renamenx", "expire", "expireat", "pexpire",
241 | "pexpireat", "auth", "flushdb", "flushall", "sort", "persist", "config",
242 | "restore", "migrate", "bitop", "geoadd", "georadius",
243 | "georadiusbymember", "pfadd", "pfmerge":
244 | return write
245 | }
246 | }
247 |
248 | func (rr *RedRaft) Command(m finn.Applier, conn redcon.Conn, cmd redcon.Command) (interface{}, error) {
249 | switch commandKind(cmd) {
250 | default:
251 | return nil, finn.ErrUnknownCommand
252 |
253 | case write:
254 | return m.Apply(conn, cmd,
255 | func() (interface{}, error) {
256 | rr.mu.Lock()
257 | defer rr.mu.Unlock()
258 | resp, err := rr.do(conn, cmd.Raw)
259 | if err != nil {
260 | return nil, err
261 | }
262 | return resp, nil
263 | },
264 | func(v interface{}) (interface{}, error) {
265 | conn.WriteRaw(v.([]byte))
266 | return nil, nil
267 | },
268 | )
269 |
270 | case read:
271 | return m.Apply(conn, cmd, nil,
272 | func(v interface{}) (interface{}, error) {
273 | rr.mu.RLock()
274 | defer rr.mu.RUnlock()
275 | resp, err := rr.do(conn, cmd.Raw)
276 | if err != nil {
277 | return nil, err
278 | }
279 | conn.WriteRaw(resp)
280 | return nil, nil
281 | },
282 | )
283 | }
284 | return nil, nil
285 | }
286 |
287 | // do will proxy the input command to the Redis process. A failed
288 | // read or write indicates a broken pipe and will cause a panic.
289 | func (rr *RedRaft) do(conn redcon.Conn, cmd []byte) (resp []byte, err error) {
290 | var rconn net.Conn
291 | var rd *bufio.Reader
292 | if conn == nil {
293 | rconn = rr.rconn
294 | rd = rr.rd
295 | } else {
296 | ctx := conn.Context().(*connCtx)
297 | rconn = ctx.rconn
298 | rd = ctx.rd
299 | }
300 | if _, err = rconn.Write(cmd); err == nil {
301 | var kind byte
302 | resp, kind, err = raftredcon.ReadRawResponse(rd)
303 | if err == nil {
304 | if kind == '-' {
305 | err = errors.New(strings.TrimSpace(string(resp[1:])))
306 | return
307 | }
308 | return
309 | }
310 | }
311 | fmt.Fprintf(os.Stderr, "panic: %v\n", err)
312 | rr.cmd.Process.Signal(syscall.SIGTERM)
313 | return
314 | }
315 |
316 | // Restore restores a snapshot
317 | func (rr *RedRaft) Restore(rd io.Reader) error {
318 | rr.mu.Lock()
319 | defer rr.mu.Unlock()
320 | conn, err := net.Dial("unix", path.Join(rr.tmpdir, "unix.sock"))
321 | if err != nil {
322 | return err
323 | }
324 | defer conn.Close()
325 | var crd = bufio.NewReader(conn)
326 | var buf []byte
327 | var count int
328 | // issue a FLUSHALL to delete all keys from Redis process.
329 | buf = append(buf, "*1\r\n$8\r\nFLUSHALL\r\n"...)
330 | count++
331 | flush := func() error {
332 | if _, err := conn.Write(buf); err != nil {
333 | return err
334 | }
335 | for i := 0; i < count; i++ {
336 | _, _, err := raftredcon.ReadRawResponse(crd)
337 | if err != nil {
338 | return err
339 | }
340 | }
341 | buf = buf[:0]
342 | count = 0
343 | return nil
344 | }
345 | var brd = bufio.NewReader(rd)
346 | for {
347 | raw, kind, err := raftredcon.ReadRawResponse(brd)
348 | if err != nil {
349 | if err == io.EOF {
350 | if err := flush(); err != nil {
351 | return err
352 | }
353 | break
354 | }
355 | return err
356 | }
357 | if kind != '*' {
358 | return errors.New("invalid command")
359 | }
360 | buf = append(buf, raw...)
361 | count++
362 | if len(buf) >= 16*1024*1024 {
363 | if err := flush(); err != nil {
364 | return err
365 | }
366 | }
367 | }
368 | return nil
369 | // return finn.ErrDisabled
370 | }
371 |
372 | // Snapshot creates a snapshot
373 | func (rr *RedRaft) Snapshot(wr io.Writer) error {
374 | // Open the file and read the file info behind a lock.
375 | // We'll use the file size this to limit the amount of writing.
376 | var end int64
377 | var f *os.File
378 | var err error
379 | func() {
380 | rr.mu.Lock()
381 | defer rr.mu.Unlock()
382 | f, err = os.Open(path.Join(rr.tmpdir, "appendonly.aof"))
383 | if err == nil {
384 | var fi os.FileInfo
385 | fi, err = f.Stat()
386 | if err != nil {
387 | f.Close()
388 | return
389 | }
390 | end = fi.Size()
391 | }
392 | }()
393 | if err != nil {
394 | return err
395 | }
396 | defer f.Close()
397 | // copy the file to the writer
398 | _, err = io.CopyN(wr, f, end)
399 | return err
400 | }
401 |
--------------------------------------------------------------------------------