├── .gitignore ├── Makefile ├── README.md ├── cache ├── cache.go └── cacher.go ├── client ├── client.go └── runtest │ └── main.go ├── go.mod ├── go.sum ├── main.go ├── proto ├── protocol.go └── protocol_test.go └── server.go /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | 23 | bin/ 24 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | go build -o bin/TigerDB 3 | 4 | run: build 5 | ./bin/TigerDB 6 | 7 | runfollower: build 8 | ./bin/TigerDB --listenaddr :4000 --leaderaddr :3000 9 | 10 | test: 11 | @go test -v ./... -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TigerDB: A Scalable and Consistent Distributed Cache 2 | 3 | **TigerDB** provides a horizontally scalable distributed cache, offering high performance and availability for key-value storage. It utilizes the Raft consensus algorithm for strong consistency and fault tolerance, ensuring data integrity even during node failures. 4 | 5 | 6 | https://github.com/manthanguptaa/TigerDB/assets/42516515/84d433ce-7dc7-4130-89ad-a436d64a6229 7 | 8 | 9 | **Key Features:** 10 | 11 | - **Scalability:** Effortlessly scale your cache by adding nodes to the cluster. 12 | - **High Availability:** Experience uninterrupted data access despite node issues. 13 | - **Consistency:** Robust Raft consensus guarantees consistent data across all nodes. 14 | - **Performance:** Efficiently cache frequently accessed data for faster retrieval. 15 | - **Open Source:** Freely use and modify TigerDB under the [insert license name]. 16 | 17 | **Getting Started:** 18 | 19 | 1. **Clone the Repository:** 20 | ```bash 21 | git clone https://github.com/manthanguptaa/TigerDB 22 | ``` 23 | 24 | **Usage:** 25 | 26 | **Client API:** 27 | 28 | - Connect to the cache using the provided client library. 29 | - Perform `SET`, `GET`, `Has`, and `Delete` operations on key-value pairs. 30 | - Example usage will be included in the client library documentation. 31 | 32 | **Cache Implementation:** 33 | 34 | - The `Cacher` interface defines the core cache operations. 35 | - The `Cache` struct implements `Cacher` with a thread-safe in-memory cache and optional TTL support. 36 | 37 | **Roadmap:** 38 | 39 | - The raft consensus isn't working properly right now. I still have to figure out why it isn't working. Any help is appreciated. 40 | -------------------------------------------------------------------------------- /cache/cache.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | type Cache struct { 10 | mu sync.RWMutex 11 | data map[string][]byte 12 | } 13 | 14 | func NewCache() *Cache { 15 | return &Cache{ 16 | data: make(map[string][]byte), 17 | } 18 | } 19 | 20 | func (c *Cache) Delete(key []byte) error { 21 | c.mu.Lock() 22 | defer c.mu.Unlock() 23 | 24 | delete(c.data, string(key)) 25 | return nil 26 | } 27 | 28 | func (c *Cache) Has(key []byte) bool { 29 | c.mu.RLock() 30 | defer c.mu.RUnlock() 31 | 32 | _, ok := c.data[string(key)] 33 | 34 | return ok 35 | } 36 | 37 | func (c *Cache) Get(key []byte) ([]byte, error) { 38 | c.mu.RLock() 39 | defer c.mu.RUnlock() 40 | 41 | val, ok := c.data[string(key)] 42 | 43 | if !ok { 44 | return nil, fmt.Errorf("key (%s) not found", string(key)) 45 | } 46 | 47 | return val, nil 48 | } 49 | 50 | func (c *Cache) Set(key, value []byte, ttl time.Duration) error { 51 | c.mu.Lock() 52 | defer c.mu.Unlock() 53 | 54 | if ttl > 0 { 55 | go func() { 56 | <-time.After(ttl) 57 | delete(c.data, string(key)) 58 | }() 59 | } 60 | 61 | c.data[string(key)] = value 62 | 63 | return nil 64 | } 65 | -------------------------------------------------------------------------------- /cache/cacher.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import "time" 4 | 5 | type Cacher interface { 6 | Set([]byte, []byte, time.Duration) error 7 | Has([]byte) bool 8 | Get([]byte) ([]byte, error) 9 | Delete([]byte) error 10 | } 11 | -------------------------------------------------------------------------------- /client/client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "TigerDB/proto" 5 | "context" 6 | "fmt" 7 | "net" 8 | ) 9 | 10 | type Options struct{} 11 | 12 | type Client struct { 13 | conn net.Conn 14 | } 15 | 16 | func NewClientFromConn(conn net.Conn) *Client { 17 | return &Client{ 18 | conn: conn, 19 | } 20 | } 21 | 22 | func New(endpoint string, opts Options) (*Client, error) { 23 | conn, err := net.Dial("tcp", endpoint) 24 | if err != nil { 25 | return nil, err 26 | } 27 | return &Client{ 28 | conn: conn, 29 | }, nil 30 | } 31 | 32 | func (c *Client) Get(ctx context.Context, key []byte) ([]byte, error) { 33 | cmd := &proto.CommandGet{ 34 | Key: key, 35 | } 36 | 37 | serialize, _ := cmd.Bytes() 38 | _, err := c.conn.Write(serialize) 39 | if err != nil { 40 | return nil, err 41 | } 42 | 43 | resp, err := proto.ParseGetResponse(c.conn) 44 | if err != nil { 45 | return nil, err 46 | } 47 | 48 | if resp.Status == proto.StatusKeyNotFound { 49 | return nil, fmt.Errorf("couldn't find the key [%s]", key) 50 | } 51 | 52 | if resp.Status != proto.StatusOK { 53 | return nil, fmt.Errorf("server responded with non OK status [%s]", resp.Status) 54 | } 55 | 56 | return resp.Value, nil 57 | } 58 | 59 | func (c *Client) Set(ctx context.Context, key []byte, value []byte, ttl int) error { 60 | cmd := &proto.CommandSet{ 61 | Key: key, 62 | Value: value, 63 | TTL: ttl, 64 | } 65 | serialize, _ := cmd.Bytes() 66 | _, err := c.conn.Write(serialize) 67 | if err != nil { 68 | return err 69 | } 70 | resp, err := proto.ParseSetResponse(c.conn) 71 | if err != nil { 72 | return err 73 | } 74 | 75 | if resp.Status != proto.StatusOK { 76 | return fmt.Errorf("server responded with non OK status [%s]", resp.Status) 77 | } 78 | return nil 79 | } 80 | 81 | func (c *Client) Close() error { 82 | return c.conn.Close() 83 | } 84 | -------------------------------------------------------------------------------- /client/runtest/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "TigerDB/client" 5 | "context" 6 | "fmt" 7 | "log" 8 | "os" 9 | "time" 10 | 11 | "github.com/hashicorp/raft" 12 | ) 13 | 14 | func main() { 15 | var ( 16 | cfg = raft.DefaultConfig() 17 | fsm = &raft.MockFSM{} 18 | logStore = raft.NewInmemStore() 19 | timeout = time.Second * 5 20 | stable = raft.NewInmemStore() 21 | ) 22 | 23 | snapshotStore, err := raft.NewFileSnapshotStore("log", 3, nil) 24 | if err != nil { 25 | log.Fatal(err) 26 | } 27 | 28 | cfg.LocalID = "RandomString" 29 | 30 | tr, err := raft.NewTCPTransport("localhost:4000", nil, 10, timeout, os.Stdout) 31 | if err != nil { 32 | log.Fatal(err) 33 | } 34 | 35 | follower_1 := raft.Server{ 36 | Suffrage: raft.Voter, 37 | ID: raft.ServerID(cfg.LocalID), 38 | Address: raft.ServerAddress("localhost:4000"), 39 | } 40 | follower_2 := raft.Server{ 41 | Suffrage: raft.Voter, 42 | ID: raft.ServerID("follower_2"), 43 | Address: raft.ServerAddress("localhost:4001"), 44 | } 45 | follower_3 := raft.Server{ 46 | Suffrage: raft.Voter, 47 | ID: raft.ServerID("follower_3"), 48 | Address: raft.ServerAddress("localhost:4002"), 49 | } 50 | 51 | serverConfig := raft.Configuration{ 52 | Servers: []raft.Server{follower_1, follower_2, follower_3}, 53 | } 54 | 55 | r, err := raft.NewRaft(cfg, fsm, logStore, stable, snapshotStore, tr) 56 | if err != nil { 57 | log.Fatal(err) 58 | } 59 | 60 | r.BootstrapCluster(serverConfig) 61 | 62 | fmt.Printf("%+v\n", r) 63 | 64 | select {} 65 | } 66 | 67 | func SendStuff() { 68 | c, err := client.New(":3000", client.Options{}) 69 | if err != nil { 70 | log.Fatal(err) 71 | } 72 | for i := 0; i < 10; i++ { 73 | var key = fmt.Sprintf("key_%d", i) 74 | var value = fmt.Sprintf("value_%d", i) 75 | 76 | err = c.Set(context.Background(), []byte(key), []byte(value), 0) 77 | if err != nil { 78 | log.Println(err) 79 | } 80 | 81 | time.Sleep(time.Second) 82 | } 83 | c.Close() 84 | } 85 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module TigerDB 2 | 3 | go 1.21.5 4 | 5 | require ( 6 | github.com/hashicorp/raft v1.6.0 7 | github.com/stretchr/testify v1.8.4 8 | go.uber.org/zap v1.26.0 9 | ) 10 | 11 | require ( 12 | github.com/armon/go-metrics v0.4.1 // indirect 13 | github.com/davecgh/go-spew v1.1.1 // indirect 14 | github.com/fatih/color v1.13.0 // indirect 15 | github.com/hashicorp/go-hclog v1.5.0 // indirect 16 | github.com/hashicorp/go-immutable-radix v1.0.0 // indirect 17 | github.com/hashicorp/go-msgpack/v2 v2.1.1 // indirect 18 | github.com/hashicorp/golang-lru v0.5.0 // indirect 19 | github.com/mattn/go-colorable v0.1.12 // indirect 20 | github.com/mattn/go-isatty v0.0.14 // indirect 21 | github.com/pmezard/go-difflib v1.0.0 // indirect 22 | go.uber.org/multierr v1.11.0 // indirect 23 | golang.org/x/sys v0.13.0 // indirect 24 | gopkg.in/yaml.v3 v3.0.1 // indirect 25 | ) 26 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= 2 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 3 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 4 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 5 | github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 6 | github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= 7 | github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= 8 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 9 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 10 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 11 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 12 | github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= 13 | github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= 14 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 15 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 16 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 17 | github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= 18 | github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= 19 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 20 | github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 21 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 22 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 23 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 24 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 25 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 26 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 27 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 28 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 29 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 30 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 31 | github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 32 | github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= 33 | github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= 34 | github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0= 35 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 36 | github.com/hashicorp/go-msgpack/v2 v2.1.1 h1:xQEY9yB2wnHitoSzk/B9UjXWRQ67QKu5AOm8aFp8N3I= 37 | github.com/hashicorp/go-msgpack/v2 v2.1.1/go.mod h1:upybraOAblm4S7rx0+jeNy+CWWhzywQsSRV5033mMu4= 38 | github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= 39 | github.com/hashicorp/go-uuid v1.0.0 h1:RS8zrF7PhGwyNPOtxSClXXj9HA8feRnJzgnI1RJCSnM= 40 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 41 | github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo= 42 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 43 | github.com/hashicorp/raft v1.6.0 h1:tkIAORZy2GbJ2Trp5eUSggLXDPOJLXC+JJLNMMqtgtM= 44 | github.com/hashicorp/raft v1.6.0/go.mod h1:Xil5pDgeGwRWuX4uPUmwa+7Vagg4N804dz6mhNi6S7o= 45 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 46 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 47 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 48 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 49 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 50 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 51 | github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= 52 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 53 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 54 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 55 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 56 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 57 | github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 58 | github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= 59 | github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= 60 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 61 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= 62 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 63 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 64 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 65 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 66 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 67 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 68 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 69 | github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= 70 | github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 71 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 72 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 73 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 74 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 75 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 76 | github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= 77 | github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= 78 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 79 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 80 | github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 81 | github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 82 | github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= 83 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 84 | github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 85 | github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= 86 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 87 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 88 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 89 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 90 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 91 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 92 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 93 | github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= 94 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 95 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 96 | github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= 97 | go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= 98 | go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= 99 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 100 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 101 | go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= 102 | go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= 103 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 104 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 105 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 106 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 107 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 108 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 109 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 110 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 111 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 112 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 113 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 114 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 115 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 116 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 117 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 118 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 119 | golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 120 | golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= 121 | golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 122 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 123 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 124 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 125 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 126 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 127 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 128 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 129 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 130 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 131 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 132 | gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 133 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 134 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 135 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "TigerDB/cache" 5 | "flag" 6 | ) 7 | 8 | func main() { 9 | listenAddr := flag.String("listenaddr", ":3000", "listen address of the server") 10 | leaderAddr := flag.String("leaderaddr", "", "listen address of the leader") 11 | flag.Parse() 12 | 13 | opts := ServerOpts{ 14 | ListenAddr: *listenAddr, 15 | IsLeader: len(*leaderAddr) == 0, 16 | LeaderAddr: *leaderAddr, 17 | } 18 | 19 | server := NewServer(opts, cache.NewCache()) 20 | server.Start() 21 | } 22 | -------------------------------------------------------------------------------- /proto/protocol.go: -------------------------------------------------------------------------------- 1 | package proto 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | "io" 8 | ) 9 | 10 | type Command byte 11 | 12 | type Status byte 13 | 14 | func (s Status) String() string { 15 | switch s { 16 | case StatusError: 17 | return "Err" 18 | case StatusOK: 19 | return "OK" 20 | case StatusKeyNotFound: 21 | return "KEY NOT FOUND" 22 | default: 23 | return "NONE" 24 | } 25 | } 26 | 27 | const ( 28 | StatusNone Status = iota 29 | StatusOK 30 | StatusError 31 | StatusKeyNotFound 32 | ) 33 | 34 | const ( 35 | CmdNonce Command = iota 36 | CmdSet 37 | CmdGet 38 | CmdDel 39 | CmdJoin 40 | ) 41 | 42 | type ResponseSet struct { 43 | Status Status 44 | } 45 | 46 | type ResponseGet struct { 47 | Status Status 48 | Value []byte 49 | } 50 | 51 | type CommandJoin struct { 52 | } 53 | 54 | type CommandSet struct { 55 | Key []byte 56 | Value []byte 57 | TTL int 58 | } 59 | 60 | type CommandGet struct { 61 | Key []byte 62 | } 63 | 64 | func (r *ResponseGet) Bytes() ([]byte, error) { 65 | buf := new(bytes.Buffer) 66 | if err := binary.Write(buf, binary.LittleEndian, r.Status); err != nil { 67 | return nil, err 68 | } 69 | 70 | valueLen := int32(len(r.Value)) 71 | if err := binary.Write(buf, binary.LittleEndian, valueLen); err != nil { 72 | return nil, err 73 | } 74 | if err := binary.Write(buf, binary.LittleEndian, r.Value); err != nil { 75 | return nil, err 76 | } 77 | 78 | return buf.Bytes(), nil 79 | } 80 | 81 | func (r *ResponseSet) Bytes() ([]byte, error) { 82 | buf := new(bytes.Buffer) 83 | if err := binary.Write(buf, binary.LittleEndian, r.Status); err != nil { 84 | return nil, err 85 | } 86 | return buf.Bytes(), nil 87 | } 88 | 89 | func (c *CommandGet) Bytes() ([]byte, error) { 90 | buf := new(bytes.Buffer) 91 | if err := binary.Write(buf, binary.LittleEndian, CmdGet); err != nil { 92 | return nil, err 93 | } 94 | 95 | if err := binary.Write(buf, binary.LittleEndian, int32(len(c.Key))); err != nil { 96 | return nil, err 97 | } 98 | if err := binary.Write(buf, binary.LittleEndian, c.Key); err != nil { 99 | return nil, err 100 | } 101 | return buf.Bytes(), nil 102 | } 103 | 104 | func (c *CommandSet) Bytes() ([]byte, error) { 105 | buf := new(bytes.Buffer) 106 | if err := binary.Write(buf, binary.LittleEndian, CmdSet); err != nil { 107 | return nil, err 108 | } 109 | 110 | if err := binary.Write(buf, binary.LittleEndian, int32(len(c.Key))); err != nil { 111 | return nil, err 112 | } 113 | if err := binary.Write(buf, binary.LittleEndian, c.Key); err != nil { 114 | return nil, err 115 | } 116 | 117 | if err := binary.Write(buf, binary.LittleEndian, int32(len(c.Value))); err != nil { 118 | return nil, err 119 | } 120 | if err := binary.Write(buf, binary.LittleEndian, c.Value); err != nil { 121 | return nil, err 122 | } 123 | 124 | if err := binary.Write(buf, binary.LittleEndian, int32(c.TTL)); err != nil { 125 | return nil, err 126 | } 127 | return buf.Bytes(), nil 128 | } 129 | 130 | func ParseSetResponse(r io.Reader) (*ResponseSet, error) { 131 | resp := &ResponseSet{} 132 | if err := binary.Read(r, binary.LittleEndian, &resp.Status); err != nil { 133 | return nil, err 134 | } 135 | return resp, nil 136 | } 137 | 138 | func ParseGetResponse(r io.Reader) (*ResponseGet, error) { 139 | resp := &ResponseGet{} 140 | if err := binary.Read(r, binary.LittleEndian, &resp.Status); err != nil { 141 | return nil, err 142 | } 143 | 144 | var valueLen int32 145 | if err := binary.Read(r, binary.LittleEndian, &valueLen); err != nil { 146 | return nil, err 147 | } 148 | 149 | resp.Value = make([]byte, valueLen) 150 | if err := binary.Read(r, binary.LittleEndian, &resp.Value); err != nil { 151 | return nil, err 152 | } 153 | 154 | return resp, nil 155 | } 156 | 157 | func ParseCommand(r io.Reader) (any, error) { 158 | var cmd Command 159 | if err := binary.Read(r, binary.LittleEndian, &cmd); err != nil { 160 | return nil, err 161 | } 162 | 163 | switch cmd { 164 | case CmdSet: 165 | set, err := parseSetCommand(r) 166 | return set, err 167 | case CmdGet: 168 | get, err := parseGetCommand(r) 169 | return get, err 170 | case CmdJoin: 171 | return &CommandJoin{}, nil 172 | default: 173 | return nil, fmt.Errorf("invalid command") 174 | } 175 | } 176 | 177 | func parseSetCommand(r io.Reader) (*CommandSet, error) { 178 | cmd := &CommandSet{} 179 | 180 | var keyLen int32 181 | if err := binary.Read(r, binary.LittleEndian, &keyLen); err != nil { 182 | return nil, err 183 | } 184 | cmd.Key = make([]byte, keyLen) 185 | if err := binary.Read(r, binary.LittleEndian, &cmd.Key); err != nil { 186 | return nil, err 187 | } 188 | 189 | var valueLen int32 190 | if err := binary.Read(r, binary.LittleEndian, &valueLen); err != nil { 191 | return nil, err 192 | } 193 | cmd.Value = make([]byte, valueLen) 194 | if err := binary.Read(r, binary.LittleEndian, &cmd.Value); err != nil { 195 | return nil, err 196 | } 197 | 198 | var ttl int32 199 | if err := binary.Read(r, binary.LittleEndian, &ttl); err != nil { 200 | return nil, err 201 | } 202 | cmd.TTL = int(ttl) 203 | 204 | return cmd, nil 205 | } 206 | 207 | func parseGetCommand(r io.Reader) (*CommandGet, error) { 208 | cmd := &CommandGet{} 209 | 210 | var keyLen int32 211 | if err := binary.Read(r, binary.LittleEndian, &keyLen); err != nil { 212 | return nil, err 213 | } 214 | cmd.Key = make([]byte, keyLen) 215 | if err := binary.Read(r, binary.LittleEndian, &cmd.Key); err != nil { 216 | return nil, err 217 | } 218 | 219 | return cmd, nil 220 | } 221 | -------------------------------------------------------------------------------- /proto/protocol_test.go: -------------------------------------------------------------------------------- 1 | package proto 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestParseSetCommand(t *testing.T) { 12 | cmd := &CommandSet{ 13 | Key: []byte("Foo"), 14 | Value: []byte("Bar"), 15 | TTL: 2, 16 | } 17 | 18 | serialized, err := cmd.Bytes() 19 | assert.Nil(t, err) 20 | 21 | fmt.Println(serialized) 22 | 23 | r := bytes.NewReader(serialized) 24 | 25 | pcmd, err := ParseCommand(r) 26 | assert.Nil(t, err) 27 | assert.Equal(t, cmd, pcmd) 28 | } 29 | 30 | func TestParseGetCommand(t *testing.T) { 31 | cmd := &CommandGet{ 32 | Key: []byte("Foo"), 33 | } 34 | 35 | serialized, err := cmd.Bytes() 36 | assert.Nil(t, err) 37 | 38 | r := bytes.NewReader(serialized) 39 | 40 | pcmd, err := ParseCommand(r) 41 | assert.Nil(t, err) 42 | assert.Equal(t, cmd, pcmd) 43 | } 44 | 45 | func BenchmarkParseCommand(b *testing.B) { 46 | cmd := &CommandSet{ 47 | Key: []byte("Foo"), 48 | Value: []byte("Bar"), 49 | TTL: 2, 50 | } 51 | for i := 0; i < b.N; i++ { 52 | serialized, _ := cmd.Bytes() 53 | r := bytes.NewReader(serialized) 54 | ParseCommand(r) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "TigerDB/cache" 5 | "TigerDB/client" 6 | "TigerDB/proto" 7 | "context" 8 | "encoding/binary" 9 | "fmt" 10 | "io" 11 | "log" 12 | "net" 13 | "time" 14 | 15 | "go.uber.org/zap" 16 | ) 17 | 18 | type ServerOpts struct { 19 | ListenAddr string 20 | IsLeader bool 21 | LeaderAddr string 22 | } 23 | 24 | type Server struct { 25 | ServerOpts 26 | members map[*client.Client]struct{} 27 | cache cache.Cacher 28 | logger *zap.SugaredLogger 29 | } 30 | 31 | func NewServer(opts ServerOpts, c cache.Cacher) *Server { 32 | l, _ := zap.NewProduction() 33 | lsugar := l.Sugar() 34 | return &Server{ 35 | ServerOpts: opts, 36 | cache: c, 37 | members: make(map[*client.Client]struct{}), 38 | logger: lsugar, 39 | } 40 | } 41 | 42 | func (s *Server) Start() error { 43 | ln, err := net.Listen("tcp", s.ListenAddr) 44 | if err != nil { 45 | return fmt.Errorf("listen error: %s", err) 46 | } 47 | 48 | if !s.IsLeader && len(s.LeaderAddr) != 0 { 49 | go func() { 50 | if err := s.dialLeader(); err != nil { 51 | log.Println(err) 52 | } 53 | }() 54 | } 55 | 56 | s.logger.Infow("server starting", "addr", s.ListenAddr, "leader", s.IsLeader) 57 | 58 | for { 59 | conn, err := ln.Accept() 60 | if err != nil { 61 | log.Printf("accept error: %s\n", err) 62 | continue 63 | } 64 | go s.handleConn(conn) 65 | } 66 | } 67 | 68 | func (s *Server) dialLeader() error { 69 | conn, err := net.Dial("tcp", s.LeaderAddr) 70 | if err != nil { 71 | return fmt.Errorf("faild to dial the leader: [%s]", s.LeaderAddr) 72 | } 73 | 74 | s.logger.Infow("connected to leader", "addr", s.LeaderAddr) 75 | 76 | binary.Write(conn, binary.LittleEndian, proto.CmdJoin) 77 | 78 | s.handleConn(conn) 79 | return nil 80 | } 81 | 82 | func (s *Server) handleConn(conn net.Conn) { 83 | defer conn.Close() 84 | 85 | for { 86 | cmd, err := proto.ParseCommand(conn) 87 | if err != nil { 88 | if err == io.EOF { 89 | break 90 | } 91 | log.Println("parse command error: ", err) 92 | break 93 | } 94 | go s.handleCommand(conn, cmd) 95 | } 96 | } 97 | 98 | func (s *Server) handleCommand(conn net.Conn, cmd any) { 99 | switch v := cmd.(type) { 100 | case *proto.CommandSet: 101 | s.handleSetCommand(conn, v) 102 | case *proto.CommandGet: 103 | s.handleGetCommand(conn, v) 104 | case *proto.CommandJoin: 105 | s.handleJoinCommand(conn, v) 106 | } 107 | } 108 | 109 | func (s *Server) handleJoinCommand(conn net.Conn, cmd *proto.CommandJoin) error { 110 | fmt.Println("member just joined the cluster: ", conn.RemoteAddr()) 111 | s.members[client.NewClientFromConn(conn)] = struct{}{} 112 | return nil 113 | } 114 | 115 | func (s *Server) handleSetCommand(conn net.Conn, cmd *proto.CommandSet) error { 116 | log.Printf("SET %s to %s", cmd.Key, cmd.Value) 117 | 118 | go func() { 119 | for member := range s.members { 120 | if err := member.Set(context.TODO(), cmd.Key, cmd.Value, cmd.TTL); err != nil { 121 | log.Println("write failed on a follower: ", err) 122 | } 123 | } 124 | }() 125 | 126 | resp := proto.ResponseSet{} 127 | if err := s.cache.Set(cmd.Key, cmd.Value, time.Duration(cmd.TTL)); err != nil { 128 | resp.Status = proto.StatusError 129 | serialize, _ := resp.Bytes() 130 | _, err := conn.Write(serialize) 131 | return err 132 | } 133 | 134 | resp.Status = proto.StatusOK 135 | serialize, _ := resp.Bytes() 136 | _, err := conn.Write(serialize) 137 | return err 138 | } 139 | 140 | func (s *Server) handleGetCommand(conn net.Conn, cmd *proto.CommandGet) error { 141 | 142 | resp := proto.ResponseGet{} 143 | value, err := s.cache.Get(cmd.Key) 144 | if err != nil { 145 | resp.Status = proto.StatusKeyNotFound 146 | serialize, err := resp.Bytes() 147 | if err != nil { 148 | return err 149 | } 150 | _, err = conn.Write(serialize) 151 | return err 152 | } 153 | 154 | resp.Status = proto.StatusOK 155 | resp.Value = value 156 | serialize, err := resp.Bytes() 157 | if err != nil { 158 | return err 159 | } 160 | _, err = conn.Write(serialize) 161 | if err != nil { 162 | return err 163 | } 164 | return nil 165 | } 166 | --------------------------------------------------------------------------------