aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoralex <[email protected]>2026-07-10 20:24:59 +0200
committeralex <[email protected]>2026-07-10 20:24:59 +0200
commit36d70e64340033f1e5b928524436c0b2f44c831e (patch)
treeacbfc67b411f184f2de14a24838aaf1d547b7d91
parentd1628cb76ac4f907d8e57346809e3ac0b8e12de5 (diff)
downloadredis-clone-36d70e64340033f1e5b928524436c0b2f44c831e.tar.xz
redis-clone-36d70e64340033f1e5b928524436c0b2f44c831e.zip
moved commands and database and main into separate packages
-rw-r--r--handlers.go153
-rw-r--r--main.go30
-rw-r--r--pubsub.go13
-rw-r--r--redisDB.go59
-rw-r--r--shards.go132
5 files changed, 100 insertions, 287 deletions
diff --git a/handlers.go b/handlers.go
index f3ddf25..1569771 100644
--- a/handlers.go
+++ b/handlers.go
@@ -4,6 +4,7 @@ import (
"errors"
"net"
"path/filepath"
+ "redisClone/pkg/core"
"sort"
"strconv"
"time"
@@ -17,14 +18,14 @@ func handleGet(args []string)interface{} {
key := args[1]
- result := db.ExecuteRead(key, func(s *Shard) interface{}{
- item, exists := s.data[key]
+ result := db.ExecuteRead(key, func(s *core.Shard) interface{}{
+ item, exists := s.Data[key]
if !exists {
return nil
}
if item.ExpiresAt != nil && time.Now().After(*item.ExpiresAt) {
- delete(s.data, key)
+ delete(s.Data, key)
return nil
}
@@ -46,8 +47,8 @@ func handleExists(args []string)interface{} {
}
key := args[1]
- result := db.ExecuteRead(key, func(s *Shard) interface{}{
- _, exists := s.data[key]
+ result := db.ExecuteRead(key, func(s *core.Shard) interface{}{
+ _, exists := s.Data[key]
if exists {
return 1
} else {
@@ -65,10 +66,10 @@ func handleSet(args []string)interface{} {
}
key := args[1]
- result := db.Execute(key, func(s *Shard) interface{}{
- item := Item{Value: args[2]}
- s.data[key] = item
- return SimpleString("OK")
+ result := db.Execute(key, func(s *core.Shard) interface{}{
+ item := core.Item{Value: args[2]}
+ s.Data[key] = item
+ return core.SimpleString("OK")
})
return result
@@ -81,13 +82,13 @@ func handleDel(args []string)interface{} {
return err
}
keys := args[1:]
- deletedKeys := db.ExecuteMulti(keys, func(shards []*Shard) interface{}{
+ deletedKeys := db.ExecuteMulti(keys, func(shards []*core.Shard) interface{}{
count := 0
for _, key := range keys{
- shard := db.getShard(key)
- _, exists := shard.data[key]
+ shard := db.GetShard(key)
+ _, exists := shard.Data[key]
if exists {
- delete(shard.data, key)
+ delete(shard.Data, key)
count += 1
}
}
@@ -106,8 +107,8 @@ func handleIncr(args []string)interface{} {
}
key := args[1]
- result := db.Execute(key, func(s *Shard)interface{}{
- item, exists := s.data[key]
+ result := db.Execute(key, func(s *core.Shard)interface{}{
+ item, exists := s.Data[key]
if exists {
strValue, ok := item.Value.(string)
if !ok {
@@ -122,10 +123,10 @@ func handleIncr(args []string)interface{} {
}
newValueStr := strconv.Itoa(currentInt + 1)
- s.data[key] = Item{Value: newValueStr}
+ s.Data[key] = core.Item{Value: newValueStr}
return currentInt + 1
} else {
- s.data[key] = Item{Value: "1"}
+ s.Data[key] = core.Item{Value: "1"}
return 1
}
@@ -142,8 +143,8 @@ func handleDecr(args []string)interface{} {
}
key := args[1]
- result := db.Execute(key, func(s *Shard)interface{}{
- item, exists := s.data[key]
+ result := db.Execute(key, func(s *core.Shard)interface{}{
+ item, exists := s.Data[key]
if exists {
strValue, ok := item.Value.(string)
if !ok {
@@ -158,10 +159,10 @@ func handleDecr(args []string)interface{} {
}
newValueStr := strconv.Itoa(currentInt + 1)
- s.data[key] = Item{Value: newValueStr}
+ s.Data[key] = core.Item{Value: newValueStr}
return currentInt - 1
} else {
- s.data[key] = Item{Value: "1"}
+ s.Data[key] = core.Item{Value: "1"}
return -1
}
@@ -170,7 +171,7 @@ func handleDecr(args []string)interface{} {
}
func handlePing(conn net.Conn, args []string) {
- conn.Write(serializeRESP(SimpleString("OK")))
+ conn.Write(core.SerializeRESP(core.SimpleString("OK")))
}
func handleFlushall(args []string)interface{} {
@@ -178,11 +179,11 @@ func handleFlushall(args []string)interface{} {
err := errors.New("wrong number of arguments for 'FLUSHALL'")
return err
}
- result := db.ExecuteAll(func(shards []*Shard)interface{}{
+ result := db.ExecuteAll(func(shards []*core.Shard)interface{}{
for _, shard := range shards{
- shard.data = make(map[string]Item)
+ shard.Data = make(map[string]core.Item)
}
- return SimpleString("OK")
+ return core.SimpleString("OK")
})
return result
}
@@ -194,17 +195,17 @@ func handleRename(args []string)interface{}{
}
keys := args[1:]
- result := db.ExecuteMulti(keys,func(shards []*Shard)interface{}{
- item, exists := db.getShard(keys[0]).data[keys[0]]
+ result := db.ExecuteMulti(keys,func(shards []*core.Shard)interface{}{
+ item, exists := db.GetShard(keys[0]).Data[keys[0]]
if !exists {
return errors.New("no such key")
}
- db.getShard(keys[1]).data[keys[1]] = item
- delete(db.getShard(keys[0]).data, keys[0])
+ db.GetShard(keys[1]).Data[keys[1]] = item
+ delete(db.GetShard(keys[0]).Data, keys[0])
- return SimpleString("OK")
+ return core.SimpleString("OK")
})
return result
}
@@ -217,9 +218,9 @@ func handleRpush(args []string)interface{} {
key := args[1]
newItems := args[2:]
- result := db.Execute(key, func(s *Shard) interface{} {
+ result := db.Execute(key, func(s *core.Shard) interface{} {
var list []string
- existingItem, exists := s.data[key]
+ existingItem, exists := s.Data[key]
if exists {
var ok bool
list, ok = existingItem.Value.([]string)
@@ -230,7 +231,7 @@ func handleRpush(args []string)interface{} {
list = []string{}
}
list = append(list, newItems...)
- s.data[key] = Item{Value: list}
+ s.Data[key] = core.Item{Value: list}
return len(list)
})
return result
@@ -244,9 +245,9 @@ func handleLpush(args []string)interface{} {
key := args[1]
newItems := args[2:]
- result := db.Execute(key, func(s *Shard) interface{} {
+ result := db.Execute(key, func(s *core.Shard) interface{} {
var list []string
- existingItem, exists := s.data[key]
+ existingItem, exists := s.Data[key]
if exists {
var ok bool
list, ok = existingItem.Value.([]string)
@@ -257,7 +258,7 @@ func handleLpush(args []string)interface{} {
list = []string{}
}
list = append(newItems, list...)
- s.data[key] = Item{Value: list}
+ s.Data[key] = core.Item{Value: list}
return len(list)
})
@@ -277,13 +278,13 @@ func handleLrange(args []string)interface{} {
}
key := args[1]
- result := db.ExecuteRead(key, func(s *Shard) interface{} {
- item, exists := s.data[key]
+ result := db.ExecuteRead(key, func(s *core.Shard) interface{} {
+ item, exists := s.Data[key]
if !exists {
return []string{}
}
if item.ExpiresAt != nil && time.Now().After(*item.ExpiresAt) {
- delete(s.data, key)
+ delete(s.Data, key)
return nil
}
typedList, ok := item.Value.([]string)
@@ -321,8 +322,8 @@ func handleLpop(args []string)interface{} {
}
key := args[1]
- result := db.Execute(key, func(s *Shard) interface{} {
- item, exists := s.data[key]
+ result := db.Execute(key, func(s *core.Shard) interface{} {
+ item, exists := s.Data[key]
if !exists {
return nil
}
@@ -336,9 +337,9 @@ func handleLpop(args []string)interface{} {
newItem := typedList[0]
newList := typedList[1:]
if len(newList) <= 0 {
- delete(s.data, key)
+ delete(s.Data, key)
} else {
- s.data[key] = Item{Value: newList}
+ s.Data[key] = core.Item{Value: newList}
}
return newItem
})
@@ -352,8 +353,8 @@ func handleRpop(args []string)interface{} {
}
key := args[1]
- result := db.Execute(key, func(s *Shard) interface{} {
- item, exists := s.data[key]
+ result := db.Execute(key, func(s *core.Shard) interface{} {
+ item, exists := s.Data[key]
if !exists {
return nil
}
@@ -367,9 +368,9 @@ func handleRpop(args []string)interface{} {
newItem := typedList[len(typedList)-1]
newList := typedList[:len(typedList)-1]
if len(newList) <= 0 {
- delete(s.data, key)
+ delete(s.Data, key)
} else {
- s.data[key] = Item{Value: newList}
+ s.Data[key] = core.Item{Value: newList}
}
return newItem
})
@@ -386,14 +387,14 @@ func handleExpire(args []string)interface{} {
return errors.New("value is not an integer")
}
- result := db.Execute(key, func(s *Shard) interface{} {
- item, exists := s.data[key]
+ result := db.Execute(key, func(s *core.Shard) interface{} {
+ item, exists := s.Data[key]
if !exists {
return 0
}
expiry := time.Now().Add(time.Duration(seconds) * time.Second)
item.ExpiresAt = &expiry
- s.data[key] = item
+ s.Data[key] = item
return 1
})
return result
@@ -406,8 +407,8 @@ func handleHset(args []string)interface{}{
}
key := args[1]
- result := db.Execute(key, func(s *Shard) interface{} {
- item, exists := s.data[key]
+ result := db.Execute(key, func(s *core.Shard) interface{} {
+ item, exists := s.Data[key]
var hash map[string]string
if exists {
var ok bool
@@ -423,7 +424,7 @@ func handleHset(args []string)interface{}{
hash[args[i]] = args[i+1]
count++
}
- s.data[key] = Item{Value: hash}
+ s.Data[key] = core.Item{Value: hash}
return count
})
return result
@@ -436,8 +437,8 @@ func handleHget(args []string)interface{} {
key := args[1]
field := args[2]
- result := db.ExecuteRead(key, func(s *Shard) interface{} {
- item, exists := s.data[key]
+ result := db.ExecuteRead(key, func(s *core.Shard) interface{} {
+ item, exists := s.Data[key]
if !exists {
return nil
}
@@ -460,8 +461,8 @@ func handleHgetall(args []string)interface{} {
}
key := args[1]
- result := db.ExecuteRead(key, func(s *Shard) interface{} {
- item, exists := s.data[key]
+ result := db.ExecuteRead(key, func(s *core.Shard) interface{} {
+ item, exists := s.Data[key]
if !exists {
return nil
}
@@ -480,8 +481,8 @@ func handleHkeys(args []string)interface{} {
}
key := args[1]
- result := db.ExecuteRead(key, func(s *Shard) interface{} {
- item, exists := s.data[key]
+ result := db.ExecuteRead(key, func(s *core.Shard) interface{} {
+ item, exists := s.Data[key]
if !exists {
return nil
}
@@ -504,8 +505,8 @@ func handleHvalues(args []string)interface{} {
}
key := args[1]
- result := db.ExecuteRead(key, func(s *Shard) interface{} {
- item, exists := s.data[key]
+ result := db.ExecuteRead(key, func(s *core.Shard) interface{} {
+ item, exists := s.Data[key]
if !exists {
return nil
}
@@ -528,20 +529,20 @@ func handleType(args []string)interface{} {
}
key := args[1]
- result := db.ExecuteRead(key, func(s *Shard) interface{} {
- item, exists := s.data[key]
+ result := db.ExecuteRead(key, func(s *core.Shard) interface{} {
+ item, exists := s.Data[key]
if !exists {
- return SimpleString("none")
+ return core.SimpleString("none")
}
switch item.Value.(type) {
case string:
- return SimpleString("string")
+ return core.SimpleString("string")
case []string:
- return SimpleString("list")
+ return core.SimpleString("list")
case map[string]string:
- return SimpleString("hash")
+ return core.SimpleString("hash")
default:
- return SimpleString("unknown")
+ return core.SimpleString("unknown")
}
})
return result
@@ -551,10 +552,10 @@ func handleDbsize(args []string)interface{} {
if len(args) != 1 {
return errors.New("wrong number of arguments for 'DBSIZE'")
}
- result := db.ExecuteReadAll(func(shards []*Shard) interface{} {
+ result := db.ExecuteReadAll(func(shards []*core.Shard) interface{} {
count := 0
for _, shard := range shards {
- count += len(shard.data)
+ count += len(shard.Data)
}
return count
})
@@ -575,10 +576,10 @@ func handleKeys(args []string)interface{} {
limit = val
}
- result := db.ExecuteReadAll(func(shards []*Shard) interface{} {
+ result := db.ExecuteReadAll(func(shards []*core.Shard) interface{} {
var matches []string
for _, shard := range shards {
- for key := range shard.data {
+ for key := range shard.Data {
matched, err := filepath.Match(pattern, key)
if err != nil {
return errors.New("illegal glob pattern")
@@ -608,8 +609,8 @@ func handleLrem(args []string)interface{} {
return errors.New("value is not an integer")
}
- result := db.Execute(key, func(s *Shard) interface{} {
- item, exists := s.data[key]
+ result := db.Execute(key, func(s *core.Shard) interface{} {
+ item, exists := s.Data[key]
if !exists {
return 0
}
@@ -660,10 +661,10 @@ func handleLrem(args []string)interface{} {
}
if len(newList) == 0 {
- delete(s.data, key)
+ delete(s.Data, key)
} else {
item.Value = newList
- s.data[key] = item
+ s.Data[key] = item
}
return counter
})
diff --git a/main.go b/main.go
index 84580bf..029bf84 100644
--- a/main.go
+++ b/main.go
@@ -5,13 +5,15 @@ import (
"fmt"
"net"
"os"
+ "redisClone/pkg/commands"
+ "redisClone/pkg/core"
"strings"
"time"
)
-const NumShards = 16
-var db RedisDB
+
+var db core.RedisDB
var hub Hub
type baseHandlerFunc func([]string)interface{}
@@ -50,28 +52,28 @@ var connectionCommandRegistry = map[string]connectionHandlerFunc{
}
func main() {
- shards := make([]*Shard, NumShards)
+ shards := make([]*core.Shard, core.NumShards)
- for i := 0; i < NumShards; i++ {
- shards[i] = &Shard{
- data: make(map[string]Item),
- id: i,
+ for i := 0; i < core.NumShards; i++ {
+ shards[i] = &core.Shard{
+ Data: make(map[string]core.Item),
+ Id: i,
}
}
- db = RedisDB{shards: shards}
+ db = core.RedisDB{Shards: shards}
hub = Hub{Channels: make(map[string]map[net.Conn]struct{}),}
go func() {
ticker := time.NewTicker(1 * time.Second)
for range ticker.C {
- for _,shard := range db.shards{
- shard.mu.Lock()
- for k, v := range shard.data {
+ for _,shard := range db.Shards{
+ shard.Mu.Lock()
+ for k, v := range shard.Data {
if v.ExpiresAt != nil && time.Now().After(*v.ExpiresAt) {
- delete(shard.data, k)
+ delete(shard.Data, k)
}
}
- shard.mu.Unlock()
+ shard.Mu.Unlock()
}
}
}()
@@ -117,7 +119,7 @@ func handleConnection(conn net.Conn) {
command := strings.ToUpper(args[0])
- conn.Write(serializeRESP(DispatchBaseCommand(command,args)))
+ conn.Write(core.SerializeRESP(commands.DispatchBaseCommand(db,command,args)))
}
} \ No newline at end of file
diff --git a/pubsub.go b/pubsub.go
index 31c3d86..5b6a747 100644
--- a/pubsub.go
+++ b/pubsub.go
@@ -3,6 +3,7 @@ package main
import (
"errors"
"net"
+ "redisClone/pkg/core"
"sync"
)
@@ -13,7 +14,7 @@ type Hub struct {
func handleSubscribe(conn net.Conn, args []string) {
if len(args) < 2 {
- conn.Write(serializeRESP(errors.New("wrong number of arguments for 'SUBSCRIBE'")))
+ conn.Write(core.SerializeRESP(errors.New("wrong number of arguments for 'SUBSCRIBE'")))
return
}
channels := args[1:]
@@ -32,7 +33,7 @@ func handleSubscribe(conn net.Conn, args []string) {
}
}
- conn.Write(serializeRESP([]any{"subscribe", channel, subCount}))
+ conn.Write(core.SerializeRESP([]any{"subscribe", channel, subCount}))
}
}
@@ -66,16 +67,16 @@ func handleUnsubscribe(conn net.Conn, args []string) {
}
}
- conn.Write(serializeRESP([]any{"unsubscribe", channel, subCount}))
+ conn.Write(core.SerializeRESP([]any{"unsubscribe", channel, subCount}))
}
}
func handlePublish(conn net.Conn, args []string) {
if len(args) != 3 {
- conn.Write(serializeRESP(errors.New("wrong number of arguments for 'PUBLISH'")))
+ conn.Write(core.SerializeRESP(errors.New("wrong number of arguments for 'PUBLISH'")))
return
}
- conn.Write(serializeRESP(hub.Publish(args[1], args[2])))
+ conn.Write(core.SerializeRESP(hub.Publish(args[1], args[2])))
}
func handleDisconnect(conn net.Conn) {
@@ -103,7 +104,7 @@ func (hub *Hub) Publish(channel, message string) int {
}
hub.mu.RUnlock()
- payload := serializeRESP([]string{"message", channel, message})
+ payload := core.SerializeRESP([]string{"message", channel, message})
count := 0
for _, conn := range conns {
_, err := conn.Write(payload)
diff --git a/redisDB.go b/redisDB.go
deleted file mode 100644
index 08c044d..0000000
--- a/redisDB.go
+++ /dev/null
@@ -1,59 +0,0 @@
-package main
-
-import (
- "fmt"
- "time"
-)
-type Item struct {
- Value any
- ExpiresAt *time.Time
-}
-
-type RedisDB struct {
- shards []*Shard
-}
-
-type SimpleString string
-
-func serializeRESP(v any) []byte {
- switch val := v.(type) {
- case string:
- bulkString := fmt.Sprintf("$%d\r\n%s\r\n", len(val), val)
- return []byte(bulkString)
- case int:
- integerString := fmt.Sprintf(":%d\r\n",val)
- return []byte(integerString)
- case nil:
- return []byte("$-1\r\n")
- case error:
- errorString := fmt.Sprintf("-ERR %s\r\n",val.Error())
- return []byte(errorString)
- case []string:
- size := len(val)
- result := []byte(fmt.Sprintf("*%d\r\n", size))
- for i := 0; i < size; i++ {
- result = append(result, serializeRESP(val[i])...)
- }
- return result
- case []any:
- size := len(val)
- result := []byte(fmt.Sprintf("*%d\r\n", size))
- for i := 0; i < size; i++ {
- result = append(result, serializeRESP(val[i])...)
- }
- return result
- case map[string]string:
- size := len(val)
- result := []byte(fmt.Sprintf("*%d\r\n", size*2))
- for key, value := range val {
- result = append(result, serializeRESP(key)...)
- result = append(result, serializeRESP(value)...)
- }
- return result
- case SimpleString:
- simpleStr := fmt.Sprintf("+%s\r\n", val)
- return []byte(simpleStr)
- default:
- return []byte("-ERR internal server error: unknown type\r\n")
- }
-}
diff --git a/shards.go b/shards.go
deleted file mode 100644
index 591e121..0000000
--- a/shards.go
+++ /dev/null
@@ -1,132 +0,0 @@
-package main
-
-import (
- "hash/fnv"
- "sort"
- "sync"
-)
-type Shard struct{
- mu sync.RWMutex
- id int
- data map[string]Item
-}
-
-func (db *RedisDB) getShard(key string)*Shard{
- hash := fnv.New64a()
- hash.Write([]byte(key))
- val := hash.Sum64()
-
- return db.shards[val%NumShards]
-}
-
-func (db *RedisDB) Execute(key string, fn func(*Shard) interface{}) interface{} {
- shard := db.getShard(key)
- shard.mu.Lock()
- defer shard.mu.Unlock()
- return fn(shard)
-}
-func (db *RedisDB) ExecuteRead(key string, fn func(*Shard) interface{}) interface{} {
- shard := db.getShard(key)
- shard.mu.RLock()
- defer shard.mu.RUnlock()
- return fn(shard)
-}
-func ( db *RedisDB) ExecuteMulti(keys []string, fn func([]*Shard) interface{})interface{}{
- if len(keys) == 1{
- shard := db.getShard(keys[0])
- shard.mu.Lock()
- defer shard.mu.Unlock()
- shards := []*Shard{shard}
- return fn(shards)
- }else{
- shardMap := make(map[int]*Shard)
- for _, k := range keys {
- shard := db.getShard(k)
- shardMap[shard.id] = shard
- }
- var sortedIDs []int
- for id := range shardMap {
- sortedIDs = append(sortedIDs, id)
- }
- sort.Ints(sortedIDs)
- for _, id := range sortedIDs{
- shardMap[id].mu.Lock()
- }
- defer func(){
- for i := len(sortedIDs)-1; i >= 0; i --{
- shardMap[sortedIDs[i]].mu.Unlock()
- }
- }()
-
- shards := make([]*Shard, 0, len(shardMap))
- for _, id := range sortedIDs {
- shards = append(shards, shardMap[id])
- }
- return fn(shards)
- }
-}
-
-func ( db *RedisDB) ExecuteAll(fn func([]*Shard) interface{})interface{}{
- for _, shard := range db.shards {
- shard.mu.Lock()
- }
-
- defer func(){
- for i := len(db.shards)-1;i >= 0; i --{
- db.shards[i].mu.Unlock()
- }
- }()
-
- return fn(db.shards)
-}
-
-func ( db *RedisDB) ExecuteReadAll(fn func([]*Shard) interface{})interface{}{
- for _, shard := range db.shards {
- shard.mu.RLock()
- }
-
- defer func(){
- for i := len(db.shards)-1;i >= 0; i --{
- db.shards[i].mu.RUnlock()
- }
- }()
-
- return fn(db.shards)
-}
-
-func ( db *RedisDB) Lock(keys []string){
- if len(keys) == 1{
- shard := db.getShard(keys[0])
- shard.mu.Lock()
- }else{
- shardMap := make(map[int]*Shard)
- for _, k := range keys {
- shard := db.getShard(k)
- shardMap[shard.id] = shard
- }
- var sortedIDs []int
- for id := range shardMap {
- sortedIDs = append(sortedIDs, id)
- }
- sort.Ints(sortedIDs)
- for _, id := range sortedIDs{
- shardMap[id].mu.Lock()
- }
- }
-}
-func ( db *RedisDB) Unlock(keys []string){
- shardMap := make(map[int]*Shard)
- for _, k := range keys {
- shard := db.getShard(k)
- shardMap[shard.id] = shard
- }
- var sortedIDs []int
- for id := range shardMap {
- sortedIDs = append(sortedIDs, id)
- }
- sort.Ints(sortedIDs)
-
- for i := len(sortedIDs)-1; i >= 0; i --{
- shardMap[sortedIDs[i]].mu.Unlock()
- }
-} \ No newline at end of file