diff options
| author | alex <[email protected]> | 2026-07-10 20:24:59 +0200 |
|---|---|---|
| committer | alex <[email protected]> | 2026-07-10 20:24:59 +0200 |
| commit | 36d70e64340033f1e5b928524436c0b2f44c831e (patch) | |
| tree | acbfc67b411f184f2de14a24838aaf1d547b7d91 | |
| parent | d1628cb76ac4f907d8e57346809e3ac0b8e12de5 (diff) | |
| download | redis-clone-36d70e64340033f1e5b928524436c0b2f44c831e.tar.xz redis-clone-36d70e64340033f1e5b928524436c0b2f44c831e.zip | |
moved commands and database and main into separate packages
| -rw-r--r-- | handlers.go | 153 | ||||
| -rw-r--r-- | main.go | 30 | ||||
| -rw-r--r-- | pubsub.go | 13 | ||||
| -rw-r--r-- | redisDB.go | 59 | ||||
| -rw-r--r-- | shards.go | 132 |
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 }) @@ -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 @@ -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 |
