-
Notifications
You must be signed in to change notification settings - Fork 169
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: implement raft example skeleton
- Loading branch information
Showing
20 changed files
with
2,633 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
# raftexample | ||
|
||
raftexample is an example usage of etcd's [raft library](https://github.com/etcd-io/raft). It provides a simple REST API for a key-value store cluster backed by the [Raft][raft] consensus algorithm. | ||
|
||
[raft]: http://raftconsensus.github.io/ | ||
|
||
## Getting Started | ||
|
||
### Building raftexample | ||
|
||
To Build `example`: | ||
|
||
```sh | ||
git clone [email protected]:etcd-io/raft.git | ||
cd ./raft/example | ||
go mod tidy | ||
go build -o bin/raftexample main.go | ||
``` | ||
|
||
### Running single node raftexample | ||
|
||
First start a single-member cluster of raftexample: | ||
|
||
```sh | ||
./bin/raftexample --id 1 --cluster http://127.0.0.1:12379 --port 12379 | ||
``` | ||
|
||
Each raftexample process maintains a single raft instance and a key-value server. | ||
The process's list of comma separated peers (--cluster), its raft ID index into the peer list (--id), and http key-value server port (--port) are passed through the command line. | ||
|
||
Next, store a value ("hello") to a key ("my-key"): | ||
|
||
``` | ||
curl -L http://127.0.0.1:12379/my-key -XPUT -d hello | ||
``` | ||
|
||
Finally, retrieve the stored key: | ||
|
||
``` | ||
curl -L http://127.0.0.1:12379/my-key | ||
``` | ||
|
||
### Dynamic cluster reconfiguration | ||
|
||
Nodes can be added to or removed from a running cluster using requests to the REST API. | ||
|
||
For example, suppose we have a 3-node cluster that was started with the commands: | ||
|
||
``` | ||
./bin/raftexample --id 1 --cluster http://127.0.0.1:12379,http://127.0.0.1:22379,http://127.0.0.1:32379 --port 12379 | ||
./bin/raftexample --id 2 --cluster http://127.0.0.1:12379,http://127.0.0.1:22379,http://127.0.0.1:32379 --port 22379 | ||
./bin/raftexample --id 3 --cluster http://127.0.0.1:12379,http://127.0.0.1:22379,http://127.0.0.1:32379 --port 32379 | ||
``` | ||
|
||
|
||
A fourth node with ID 4 can be added by issuing a POST: | ||
```sh | ||
./bin/raftexample --id 4 --cluster http://127.0.0.1:42379 --port 42379 | ||
|
||
curl -L http://127.0.0.1:12379/4 -XPOST -d http://127.0.0.1:42379 | ||
``` | ||
|
||
We can remove a node using a DELETE request: | ||
```sh | ||
curl -L http://127.0.0.1:12379/3 -XDELETE | ||
``` | ||
|
||
Node 3 should shut itself down once the cluster has processed this request. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
module go.etcd.io/raft/v3/example | ||
|
||
go 1.21 | ||
|
||
require ( | ||
github.com/sirupsen/logrus v1.9.3 | ||
go.etcd.io/raft/v3 v3.0.0 | ||
) | ||
|
||
replace go.etcd.io/raft/v3 => ../ | ||
|
||
require ( | ||
github.com/gogo/protobuf v1.3.2 // indirect | ||
github.com/golang/protobuf v1.5.3 // indirect | ||
golang.org/x/sys v0.15.0 // indirect | ||
google.golang.org/protobuf v1.31.0 // indirect | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA= | ||
github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= | ||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= | ||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= | ||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= | ||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= | ||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= | ||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= | ||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= | ||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= | ||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= | ||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= | ||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= | ||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= | ||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= | ||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | ||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | ||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | ||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | ||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | ||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | ||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | ||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | ||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= | ||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= | ||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= | ||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | ||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= | ||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= | ||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= | ||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= | ||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= | ||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | ||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package main | ||
|
||
import ( | ||
"flag" | ||
"fmt" | ||
"os" | ||
"os/signal" | ||
"strings" | ||
"syscall" | ||
"time" | ||
|
||
log "github.com/sirupsen/logrus" | ||
|
||
"go.etcd.io/raft/v3/example/server" | ||
) | ||
|
||
func main() { | ||
cluster := flag.String("cluster", "http://127.0.0.1:9021", "comma separated cluster peers") | ||
id := flag.Uint64("id", 1, "node ID") | ||
kvport := flag.Int("port", 9021, "key-value server port") | ||
logLevel := flag.String("log-level", "info", "You can set the logging level") | ||
logCaller := flag.Bool("log-caller", false, "You can set the logging Caller") | ||
//join := flag.Bool("join", false, "join an existing cluster") | ||
flag.Parse() | ||
|
||
newLogLevel, err := log.ParseLevel(*logLevel) | ||
if err != nil { | ||
newLogLevel = log.InfoLevel | ||
} | ||
|
||
log.SetLevel(newLogLevel) | ||
if *logCaller { | ||
log.SetReportCaller(true) | ||
} | ||
|
||
var members = []server.Member{} | ||
for i, v := range strings.Split(*cluster, ",") { | ||
members = append(members, server.Member{ | ||
NodeID: uint64(i + 1), | ||
Host: v, | ||
Learner: false, | ||
}) | ||
} | ||
cfg := &server.Config{ | ||
NodeId: *id, | ||
ListenPort: *kvport, | ||
TickInterval: 500 * time.Millisecond, | ||
ElectionTick: 5, | ||
Members: members, | ||
} | ||
fmt.Println(cfg.Members) | ||
|
||
if err := cfg.Verify(); err != nil { | ||
log.Fatalf("config verify err: %v", err) | ||
} | ||
|
||
log.Infof("cfg: %s", cfg) | ||
|
||
svr := server.NewServer(cfg) | ||
svr.Start() | ||
|
||
sig := waitForSigterm() | ||
log.Infof("service received signal %s", sig) | ||
log.Infof("gracefully shutting down http service at :%v", *kvport) | ||
|
||
svr.Stop() | ||
log.Infof("successfully shut down the service") | ||
} | ||
|
||
func waitForSigterm() os.Signal { | ||
ch := make(chan os.Signal, 1) | ||
signal.Notify(ch, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP) | ||
for { | ||
sig := <-ch | ||
return sig | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
package server | ||
|
||
import ( | ||
"sync" | ||
) | ||
|
||
func newBytes(size int) func() interface{} { | ||
return func() interface{} { | ||
return make([]byte, size) | ||
} | ||
} | ||
|
||
const ( | ||
zeroSize int = 1 << 14 // 16K | ||
|
||
// 1K - 2K - 4K - 8K - 16K - 32K - 64K | ||
numPools = 7 | ||
sizeStep = 2 | ||
startSize int = 1 << 10 // 1K | ||
maxSize int = 1 << 16 // 64K | ||
) | ||
|
||
var ( | ||
zero = make([]byte, zeroSize) | ||
|
||
pools [numPools]sync.Pool | ||
poolSize [numPools]int | ||
) | ||
|
||
func init() { | ||
size := startSize | ||
for ii := 0; ii < numPools; ii++ { | ||
pools[ii] = sync.Pool{ | ||
New: newBytes(size), | ||
} | ||
poolSize[ii] = size | ||
size *= sizeStep | ||
} | ||
} | ||
|
||
// GetPool returns a sync.Pool that generates bytes slice with the size. | ||
// Return nil if no such pool exists. | ||
func GetPool(size int) *sync.Pool { | ||
for idx, psize := range poolSize { | ||
if size <= psize { | ||
return &pools[idx] | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// Alloc returns a bytes slice with the size. | ||
// Make a new bytes slice if oversize. | ||
func Alloc(size int) []byte { | ||
if pool := GetPool(size); pool != nil { | ||
b := pool.Get().([]byte) | ||
return b[:size] | ||
} | ||
return make([]byte, size) | ||
} | ||
|
||
// Free puts the bytes slice into suitable pool. | ||
// Discard the bytes slice if oversize. | ||
func Free(b []byte) { | ||
size := cap(b) | ||
if size > maxSize { | ||
return | ||
} | ||
|
||
b = b[0:size] | ||
for ii := numPools - 1; ii >= 0; ii-- { | ||
if size >= poolSize[ii] { | ||
pools[ii].Put(b) // nolint: staticcheck | ||
return | ||
} | ||
} | ||
} | ||
|
||
// Zero clean up the bytes slice b to zero. | ||
func Zero(b []byte) { | ||
for len(b) > 0 { | ||
n := copy(b, zero) | ||
b = b[n:] | ||
} | ||
} |
Oops, something went wrong.