├── redis.conf ├── go.mod ├── .DS_Store ├── util.go ├── .gitignore ├── server.go ├── networking.go ├── t_list.go ├── t_zset_test.go ├── main.go ├── db.go ├── t_hash.go ├── object.go ├── dict_test.go ├── README.md ├── client.go ├── adlist.go ├── adlist_test.go ├── redis.go ├── dict.go ├── t_zset.go └── command.go /redis.conf: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module mini-redis 2 | 3 | go 1.20 4 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shkctl/mini-redis/HEAD/.DS_Store -------------------------------------------------------------------------------- /util.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "math" 5 | "strconv" 6 | ) 7 | 8 | func string2l(s *string, len int, lval *int64) bool { 9 | llval, err := strconv.ParseInt(*s, 10, 64) 10 | if err != nil { 11 | return false 12 | } 13 | if llval < math.MinInt64 || llval > math.MaxInt64 { 14 | return false 15 | } 16 | 17 | *lval = llval 18 | return true 19 | } 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | v# ---> JetBrains 2 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio 3 | 4 | *.iml 5 | 6 | ## Directory-based project format: 7 | .idea/ 8 | # if you remove the above rule, at least ignore the following: 9 | 10 | # User-specific stuff: 11 | # .idea/workspace.xml 12 | # .idea/tasks.xml 13 | # .idea/dictionaries 14 | 15 | # Sensitive or high-churn files: 16 | # .idea/dataSources.ids 17 | # .idea/dataSources.xml 18 | # .idea/sqlDataSources.xml 19 | # .idea/dynamic.xml 20 | # .idea/uiDesigner.xml 21 | 22 | # Gradle: 23 | # .idea/gradle.xml 24 | # .idea/libraries 25 | 26 | # Mongo Explorer plugin: 27 | # .idea/mongoSettings.xml 28 | 29 | ## File-based project format: 30 | *.ipr 31 | *.iws 32 | 33 | ## Plugin-specific files: 34 | 35 | # IntelliJ 36 | /out/ 37 | 38 | # mpeltonen/sbt-idea plugin 39 | .idea_modules/ 40 | 41 | # JIRA plugin 42 | atlassian-ide-plugin.xml 43 | 44 | # Crashlytics plugin (for Android Studio and IntelliJ) 45 | com_crashlytics_export_strings.xml 46 | crashlytics.properties 47 | crashlytics-build.properties 48 | 49 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "unsafe" 5 | ) 6 | 7 | var dbDictType = dictType{ 8 | hashFunction: dictSdsHash, 9 | keyDup: nil, 10 | valDup: nil, 11 | keyCompare: dictCompare, 12 | keyDestructor: nil, 13 | valDestructor: nil, 14 | } 15 | 16 | func dictSdsHash(key string) int { 17 | return dictGenHashFunction(key, len(key)) 18 | } 19 | 20 | func dictGenHashFunction(key string, kLen int) int { 21 | var seed int = dict_hash_function_seed 22 | var m int = 0x5bd1e995 23 | var r int = 24 24 | 25 | var h int = seed ^ kLen 26 | 27 | runes := []rune(key) 28 | pos := 0 29 | 30 | for kLen >= 4 { 31 | k := *(*int)(unsafe.Pointer(&runes[pos])) 32 | k *= m 33 | k ^= k >> r 34 | k *= m 35 | h ^= k 36 | 37 | pos += 4 38 | kLen -= 4 39 | } 40 | 41 | switch kLen { 42 | case 3: 43 | h ^= int(runes[2]) << 16 44 | 45 | case 2: 46 | h ^= int(runes[1]) << 8 47 | 48 | case 1: 49 | h ^= int(runes[0]) 50 | h *= m 51 | } 52 | 53 | h ^= h >> 13 54 | h *= m 55 | h ^= h >> 15 56 | return h 57 | 58 | } 59 | 60 | func dictCompare(privdata *interface{}, key1 string, key2 string) bool { 61 | return key1 == key2 62 | } 63 | -------------------------------------------------------------------------------- /networking.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "strconv" 4 | 5 | func addReply(c *redisClient, reply *string) { 6 | c.conn.Write([]byte(*reply)) 7 | } 8 | 9 | func addReplyBulk(c *redisClient, obj *robj) { 10 | if obj.encoding == REDIS_ENCODING_EMBSTR { 11 | value := (*obj.ptr).(string) 12 | c.conn.Write([]byte("$" + strconv.Itoa(len(value)) + *shared.crlf + value + *shared.crlf)) 13 | } else if obj.encoding == REDIS_ENCODING_INT { 14 | num := (*obj.ptr).(int64) 15 | numStr := strconv.FormatInt(num, 10) 16 | c.conn.Write([]byte("$" + strconv.Itoa(len(numStr)) + *shared.crlf + numStr + *shared.crlf)) 17 | } 18 | 19 | } 20 | 21 | func addReplyError(c *redisClient, s *string) { 22 | c.conn.Write([]byte("-ERR " + *s + "\r\n")) 23 | } 24 | 25 | func addReplyLongLong(c *redisClient, ll int64) { 26 | if ll == 0 { 27 | addReply(c, shared.czero) 28 | } else if ll == 1 { 29 | addReply(c, shared.cone) 30 | } else { 31 | addReplyLongLongWithPrefix(c, ll, ":") 32 | } 33 | } 34 | 35 | func addReplyLongLongWithPrefix(c *redisClient, ll int64, prefix string) { 36 | c.conn.Write([]byte(":" + strconv.FormatInt(ll, 10) + "\r\n")) 37 | } 38 | 39 | func addReplyMultiBulkLen(c *redisClient, length int64) { 40 | if length < REDIS_SHARED_BULKHDR_LEN { 41 | s := (*shared.bulkhdr[length].ptr).(string) 42 | addReply(c, &s) 43 | } else { 44 | addReplyLongLongWithPrefix(c, length, "*") 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /t_list.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | ) 6 | 7 | func listTypePush(subject *robj, value *robj, where int) { 8 | 9 | if subject.encoding == REDIS_ENCODING_ZIPLIST { 10 | //todo 11 | } else if subject.encoding == REDIS_ENCODING_LINKEDLIST { 12 | lobj := (*subject.ptr).(*list) 13 | node := interface{}(value) 14 | if where == REDIS_HEAD { 15 | listAddNodeHead(lobj, &node) 16 | } else { 17 | listAddNodeTail(lobj, &node) 18 | } 19 | } else { 20 | log.Panic("Unknown list encoding") 21 | } 22 | } 23 | 24 | func listTypeLength(subject *robj) int64 { 25 | 26 | if subject.encoding == REDIS_ENCODING_ZIPLIST { 27 | //todo 28 | return 0 29 | } else if subject.encoding == REDIS_ENCODING_LINKEDLIST { 30 | lobj := (*subject.ptr).(*list) 31 | return lobj.len 32 | } else { 33 | log.Panic("Unknown list encoding") 34 | } 35 | return -1 36 | } 37 | 38 | func listTypePop(subject *robj, where int) *robj { 39 | var value *robj 40 | if subject.encoding == REDIS_ENCODING_ZIPLIST { 41 | //todo 42 | } else if subject.encoding == REDIS_ENCODING_LINKEDLIST { 43 | lobj := (*subject.ptr).(*list) 44 | var ln *listNode 45 | //get the head or tail element based on the linked list identifier. 46 | if where == REDIS_HEAD { 47 | ln = lobj.head 48 | } else { 49 | ln = lobj.tail 50 | } 51 | //read the value of the element and remove it from the linked list. 52 | value = (*ln.value).(*robj) 53 | listDelNode(lobj, ln) 54 | } else { 55 | log.Panic("Unknown list encoding") 56 | } 57 | return value 58 | } 59 | -------------------------------------------------------------------------------- /t_zset_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "testing" 6 | ) 7 | 8 | func TestCreateZskipList(t *testing.T) { 9 | zsl := zslCreate() 10 | 11 | if zsl == nil { 12 | t.Error("zslCreate failed") 13 | } 14 | 15 | if zsl.header == nil { 16 | t.Error("跳表头节点为空") 17 | 18 | } 19 | 20 | if zsl.length > 0 { 21 | t.Error("跳表长度初始化失败") 22 | } 23 | 24 | if zsl.level != 1 { 25 | t.Error("跳表索引初始化失败") 26 | } 27 | 28 | if zsl.tail != nil { 29 | t.Error("跳表尾节点不为空") 30 | } 31 | 32 | if zsl.header.backward != nil { 33 | t.Error("头节点的后继节点不为空") 34 | } 35 | 36 | } 37 | 38 | func TestZslInsert(t *testing.T) { 39 | zsl := zslCreate() 40 | 41 | num := interface{}(1) 42 | obj := createObject(REDIS_ENCODING_INT, &num) 43 | zslInsert(zsl, 1.0, obj) 44 | 45 | num2 := interface{}(2) 46 | obj2 := createObject(REDIS_ENCODING_INT, &num2) 47 | zslInsert(zsl, 2.0, obj2) 48 | 49 | x := zsl.header 50 | for x != nil { 51 | log.Print("node:", x.obj, " span:", x.level[0].span) 52 | x = x.level[0].forward 53 | } 54 | 55 | log.Println("tail:", zsl.tail.obj) 56 | log.Println("length:", zsl.length) 57 | 58 | num1_5 := interface{}(1.5) 59 | obj1_5 := createObject(REDIS_ENCODING_INT, &num1_5) 60 | zslInsert(zsl, 1.5, obj1_5) 61 | 62 | printZskipListNode(zsl) 63 | 64 | rank := zslGetRank(zsl, 2.0, obj2) 65 | log.Println("rank:", rank) 66 | 67 | zslDelete(zsl, 1.5, obj1_5) 68 | log.Println("after delete print ****************") 69 | printZskipListNode(zsl) 70 | 71 | } 72 | 73 | func printZskipListNode(zsl *zskiplist) { 74 | log.Println("*******************") 75 | x := zsl.header 76 | for i := 0; i < zsl.level; i++ { 77 | x = zsl.header 78 | for x != nil { 79 | log.Print("node:", x.obj, " span:", x.level[i].span) 80 | x = x.level[i].forward 81 | } 82 | 83 | log.Println("*********** level", i, " end ***********") 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net" 6 | "os" 7 | "os/signal" 8 | "strconv" 9 | "sync" 10 | "syscall" 11 | ) 12 | 13 | var server redisServer 14 | var wg sync.WaitGroup 15 | 16 | func main() { 17 | 18 | wg.Add(1) 19 | 20 | //initialize redis server and configuration 21 | loadServerConfig() 22 | initServerConfig() 23 | initServer() 24 | 25 | //listen to the shutdown signal 26 | sigCh := make(chan os.Signal) 27 | signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT) 28 | go func(server *redisServer) { 29 | sig := <-sigCh 30 | switch sig { 31 | case syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT: 32 | closeRedisServer() 33 | //modifying atomic variables means that the server is ready to shut down. 34 | server.done.Store(1) 35 | } 36 | }(&server) 37 | 38 | //parse address information 39 | address := server.ip + ":" + strconv.Itoa(server.port) 40 | log.Println("this redis server address:", address) 41 | //binding port number 42 | listen, err := net.Listen("tcp", address) 43 | if err != nil { 44 | log.Fatal("redis server listen failed,err:", err) 45 | return 46 | } 47 | server.listen = listen 48 | 49 | //handle client connections that are actively closed 50 | go func(server *redisServer) { 51 | for { 52 | c := <-server.closeClientCh 53 | log.Println("receive close client signal") 54 | _ = c.conn.Close() 55 | server.clients.Delete(c.string()) 56 | log.Println("close client successful ", server.clients) 57 | } 58 | }(&server) 59 | 60 | go func(s *redisServer) { 61 | //retrieve the Redis client from "commandCh" and call "processCommand" to handle the instructions parsed from the array. 62 | for redisClient := range s.commandCh { 63 | processCommand(&redisClient) 64 | } 65 | }(&server) 66 | 67 | //listen for incoming connections. 68 | go func() { 69 | for { 70 | log.Println("event loop is listening and waiting for client connection.") 71 | conn, err := listen.Accept() 72 | if err != nil { 73 | log.Println("accept conn failed,err:", err) 74 | break 75 | } 76 | acceptTcpHandler(conn) 77 | 78 | } 79 | }() 80 | 81 | wg.Wait() 82 | log.Println("redis service is down........................") 83 | 84 | } 85 | -------------------------------------------------------------------------------- /db.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "time" 4 | 5 | type redisDb struct { 6 | //dict map[string]*robj 7 | //expires map[string]int64 8 | dict dict 9 | expires dict 10 | id int 11 | } 12 | 13 | func lookupKeyWriteOrReply(c *redisClient, key *robj, reply *string) *robj { 14 | o := lookupKeyWrite(c.db, key) 15 | if o == nil { 16 | addReply(c, reply) 17 | } 18 | 19 | return o 20 | } 21 | 22 | func lookupKeyWrite(db *redisDb, key *robj) *robj { 23 | //check if the key has expired, and if so, delete it. 24 | expireIfNeeded(db, key) 25 | //query the dictionary for the value corresponding to the key. 26 | return lookupKey(db, key) 27 | } 28 | 29 | func expireIfNeeded(db *redisDb, key *robj) int { 30 | //get the expiration time of the key. 31 | //when, exists := db.expires[(*key.ptr).(string)] 32 | entry := dictFind(&db.expires, (*key.ptr).(string)) 33 | 34 | if entry == nil { 35 | return 0 36 | } 37 | 38 | when := (*entry.val.ptr).(int64) 39 | if when < 0 { 40 | return 0 41 | } 42 | now := time.Now().UnixMilli() 43 | //if the current time is less than the expiration time, it means the current key has not expired, so return directly. 44 | if now < when { 45 | return 0 46 | } 47 | //delete expired keys. 48 | dbDelete(db, key) 49 | 50 | return 1 51 | 52 | } 53 | 54 | func dbDelete(db *redisDb, key *robj) { 55 | //delete(db.expires, (*key.ptr).(string)) 56 | //delete(db.dict, (*key.ptr).(string)) 57 | dictDelete(&db.dict, (*key.ptr).(string)) 58 | dictDelete(&db.expires, (*key.ptr).(string)) 59 | } 60 | 61 | func lookupKeyRead(db *redisDb, key *robj) *robj { 62 | //check if the key has expired and delete it. 63 | expireIfNeeded(db, key) 64 | val := lookupKey(db, key) 65 | return val 66 | } 67 | 68 | func lookupKey(db *redisDb, key *robj) *robj { 69 | //val := db.dict[(*key.ptr).(string)] 70 | de := dictFind(&db.dict, (*key.ptr).(string)) 71 | if de == nil { 72 | return nil 73 | } 74 | return de.val 75 | } 76 | 77 | func lookupKeyReadOrReply(c *redisClient, key *robj, reply *string) *robj { 78 | //check if the key has expired to decide whether to delete the key from the dictionary, then query the dictionary for the result and return it. 79 | o := lookupKeyRead(c.db, key) 80 | if o == nil { 81 | addReply(c, reply) 82 | } 83 | return o 84 | } 85 | 86 | func dbAdd(db *redisDb, key *robj, val *robj) { 87 | //db.dict[(*key.ptr).(string)] = val 88 | dictAdd(&db.dict, key, val) 89 | } 90 | 91 | func dbOverwrite(db *redisDb, key *robj, val *robj) { 92 | de := dictFind(&db.dict, (*key.ptr).(string)) 93 | if de == nil { 94 | panic("de is null") 95 | } 96 | dictReplace(&db.dict, key, val) 97 | } 98 | -------------------------------------------------------------------------------- /t_hash.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "log" 4 | 5 | func hashTypeLookupWriteOrCreate(c *redisClient, key *robj) *robj { 6 | //check if the dictionary exists. 7 | o := lookupKeyWrite(c.db, key) 8 | //if it is nil, create a hash object and add to redisDb. 9 | if o == nil { 10 | o = createHashObject() 11 | dbAdd(c.db, key, o) 12 | return o 13 | } 14 | /** 15 | if it exists but is not a hash object, 16 | reply the user of a type error. 17 | */ 18 | if o.robjType != REDIS_HASH { 19 | addReply(c, shared.wrongtypeerr) 20 | return nil 21 | } 22 | return o 23 | } 24 | 25 | func createHashObject() *robj { 26 | o := new(robj) 27 | 28 | o.robjType = REDIS_HASH 29 | o.encoding = REDIS_ENCODING_HT 30 | 31 | dict := make(map[string]*robj) 32 | i := interface{}(dict) 33 | o.ptr = &i 34 | 35 | return o 36 | } 37 | 38 | func hashTypeTryObjectEncoding(subject *robj, o1 **robj, o2 **robj) { 39 | /** 40 | determine if the subject encoding is a dict . 41 | if it is, proceed with the logic processing 42 | */ 43 | if subject.encoding == REDIS_ENCODING_HT { 44 | //perform type conversion on the field. 45 | if o1 != nil { 46 | *o1 = tryObjectEncoding(*o1) 47 | } 48 | //perform type conversion on the field. 49 | if o2 != nil { 50 | *o2 = tryObjectEncoding(*o2) 51 | } 52 | } 53 | } 54 | 55 | func hashTypeSet(o *robj, field *robj, value *robj) int { 56 | 57 | if o.encoding == REDIS_ENCODING_ZIPLIST { 58 | //todo 59 | return 0 60 | } else if o.encoding == REDIS_ENCODING_HT { 61 | m := (*o.ptr).(map[string]*robj) 62 | /** 63 | if it is an add operation, return true, 64 | and then the function returns 1 . 65 | conversely,return 0 to inform the external system 66 | that the current operation is an update 67 | 68 | */ 69 | if dictReplace_new(m, field, value) { 70 | return 0 71 | } 72 | return 1 73 | } else { 74 | log.Panic("Unknown hash encoding") 75 | return -1 76 | } 77 | } 78 | 79 | func hashTypeExists(o *robj, field *robj) bool { 80 | /** 81 | because the robj pointer records the interface type, 82 | when storing, the field is forcefully cast to the interface type, 83 | and the same applies to the key 84 | */ 85 | dict := (*o.ptr).(map[string]*robj) 86 | key := (*field.ptr).(string) 87 | //if it exists, return true. 88 | if _, e := dict[key]; e { 89 | return true 90 | } 91 | return false 92 | } 93 | 94 | func hashTypeDelete(o *robj, field *robj) bool { 95 | var deleted bool 96 | if o.encoding == REDIS_ENCODING_ZIPLIST { 97 | //todo 98 | return false 99 | } else if o.encoding == REDIS_ENCODING_HT { 100 | dict := (*o.ptr).(map[string]*robj) 101 | key := (*field.ptr).(string) 102 | _, ok := dict[key] 103 | if ok { 104 | delete(dict, key) 105 | deleted = true 106 | } 107 | } else { 108 | log.Panic("Unknown hash encoding") 109 | } 110 | return deleted 111 | } 112 | -------------------------------------------------------------------------------- /object.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "math" 5 | "strconv" 6 | ) 7 | 8 | const ( 9 | /* Error codes */ 10 | REDIS_OK = 0 11 | REDIS_ERR = -1 12 | ) 13 | 14 | func createStringObjectFromLongLong(value int64) *robj { 15 | var o *robj 16 | if value >= 0 && value < REDIS_SHARED_INTEGERS { 17 | o = shared.integers[value] 18 | } else if value >= math.MinInt64 && value < math.MaxInt64 { 19 | o = createObject(REDIS_STRING, nil) 20 | o.encoding = REDIS_ENCODING_INT 21 | i := interface{}(value) 22 | o.ptr = &i 23 | } 24 | return o 25 | } 26 | 27 | func createStringObject(ptr *string, len int) *robj { 28 | return createEmbeddedStringObject(ptr, len) 29 | } 30 | 31 | func createEmbeddedStringObject(ptr *string, len int) *robj { 32 | o := new(robj) 33 | o.robjType = REDIS_STRING 34 | o.encoding = REDIS_ENCODING_EMBSTR 35 | i := interface{}(*ptr) 36 | o.ptr = &i 37 | return o 38 | } 39 | 40 | func tryObjectEncoding(o *robj) *robj { 41 | var value int64 42 | var s string 43 | var sLen int 44 | //get string value and length 45 | s = (*o.ptr).(string) 46 | sLen = len(s) 47 | /** 48 | If it can be converted into an integer and is between 0 and 10000, 49 | it is obtained from the constant pool. 50 | */ 51 | if sLen < 21 && string2l(&s, sLen, &value) { 52 | if value >= 0 && value < REDIS_SHARED_INTEGERS { 53 | return shared.integers[value] 54 | } else { 55 | /** 56 | If it is not within the scope of constant pool, 57 | it will be manually converted into an object of integer encoding type. 58 | */ 59 | o.encoding = REDIS_ENCODING_INT 60 | num := interface{}(value) 61 | o.ptr = &num 62 | } 63 | } 64 | 65 | return o 66 | } 67 | 68 | func createObject(oType int, ptr *interface{}) *robj { 69 | o := new(robj) 70 | o.robjType = oType 71 | o.encoding = REDIS_ENCODING_EMBSTR 72 | o.ptr = ptr 73 | return o 74 | } 75 | 76 | func createListObject() *robj { 77 | l := listCreate() 78 | i := interface{}(l) 79 | o := createObject(REDIS_LIST, &i) 80 | o.encoding = REDIS_ENCODING_LINKEDLIST 81 | return o 82 | } 83 | 84 | func createZsetObject() *robj { 85 | zs := new(zset) 86 | zs.dict = map[string]*float64{} 87 | zs.zsl = zslCreate() 88 | 89 | i := interface{}(zs) 90 | 91 | o := createObject(REDIS_ZSET, &i) 92 | o.encoding = REDIS_ENCODING_SKIPLIST 93 | return o 94 | } 95 | 96 | func getLongFromObjectOrReply(c *redisClient, o *robj, target *int64, msg *string) bool { 97 | value, err := strconv.ParseInt((*o.ptr).(string), 10, 64) 98 | 99 | if err != nil { 100 | errMsg := "value is not an integer or out of range" 101 | addReplyError(c, &errMsg) 102 | return false 103 | } 104 | 105 | if value < math.MinInt64 || value > math.MaxInt64 { 106 | if msg != nil { 107 | addReplyError(c, msg) 108 | } else { 109 | *msg = "value is not an integer or out of range" 110 | addReplyError(c, msg) 111 | } 112 | return false 113 | } 114 | *target = value 115 | return true 116 | } 117 | 118 | func getDoubleFromObjectOrReply(c *redisClient, o *robj, target *float64, msg *string) bool { 119 | value, err := strconv.ParseFloat((*o.ptr).(string), 64) 120 | 121 | if err != nil { 122 | errMsg := "value is not a valid float" 123 | addReplyError(c, &errMsg) 124 | return false 125 | } 126 | 127 | *target = value 128 | return true 129 | } 130 | 131 | func checkType(c *redisClient, o *robj, rType int) bool { 132 | //如果类型不一致,则输出-WRONGTYPE Operation against a key holding the wrong kind of value 133 | if o.robjType != rType { 134 | addReply(c, shared.wrongtypeerr) 135 | return true 136 | } 137 | return false 138 | } 139 | 140 | func getLongLongFromObjectOrReply(c *redisClient, expire string, target *int64, msg *string) int { 141 | var value int64 142 | 143 | if len(expire) == 0 { 144 | return 0 145 | } 146 | 147 | value, err := strconv.ParseInt(expire, 10, 64) 148 | if err != nil { 149 | addReply(c, shared.err) 150 | return REDIS_ERR 151 | } 152 | if value < 0 { 153 | *msg = "value is not an integer or out of range" 154 | addReplyError(c, msg) 155 | return REDIS_ERR 156 | } 157 | *target = value 158 | return REDIS_OK 159 | } 160 | -------------------------------------------------------------------------------- /dict_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "strconv" 7 | "testing" 8 | ) 9 | 10 | func TestDictCreate(t *testing.T) { 11 | d := dictCreate(&dbDictType, nil) 12 | 13 | ht := d.ht 14 | 15 | if ht[0].table != nil || ht[1].table != nil { 16 | log.Fatal("table is not nil") 17 | } 18 | if ht[0].size != 0 || ht[1].size != 0 { 19 | log.Fatal("size is not 0") 20 | } 21 | if ht[0].used != 0 || ht[1].used != 0 { 22 | log.Fatal("used is not 0") 23 | } 24 | if ht[0].sizemask != 0 || ht[1].sizemask != 0 { 25 | log.Fatal("sizemask is not 0") 26 | } 27 | 28 | if d.rehashidx != -1 { 29 | log.Fatal("rehashidx is not -1") 30 | } 31 | 32 | if d.iterators != 0 { 33 | log.Fatal("iterators is not 0") 34 | } 35 | 36 | } 37 | 38 | func TestDictAdd(t *testing.T) { 39 | d := dictCreate(&dbDictType, nil) 40 | k := "hello" 41 | v := "mini-redis" 42 | 43 | dictAdd(d, createStringObject(&k, len(k)), createStringObject(&v, len(v))) 44 | entry := dictFind(d, k) 45 | 46 | ptr := entry.key.ptr 47 | s := (*ptr).(string) 48 | if s != "hello" { 49 | log.Fatal("key is not hello") 50 | } 51 | 52 | k1 := "key-1" 53 | v1 := "value-1" 54 | dictAdd(d, createStringObject(&k1, len(k1)), createStringObject(&v1, len(v1))) 55 | entry1 := dictFind(d, k1) 56 | ptr1 := entry1.key.ptr 57 | s1 := (*ptr1).(string) 58 | if s1 != "key-1" { 59 | log.Fatal("key is not key-1") 60 | } 61 | 62 | } 63 | 64 | func TestSameBucketInsertion(t *testing.T) { 65 | d := dictCreate(&dbDictType, nil) 66 | bucketMap := createHashBucketMap() 67 | 68 | count := 0 69 | 70 | for _, v := range (*bucketMap)[0] { 71 | dictAdd(d, createStringObject(&v, len(v)), nil) 72 | count++ 73 | if count > 4 { 74 | break 75 | } 76 | } 77 | 78 | ht := d.ht[0] 79 | 80 | entries := (*ht.table)[0] 81 | for entries != nil { 82 | fmt.Println((*entries.key.ptr).(string)) 83 | entries = entries.next 84 | } 85 | k := "9" 86 | find := dictFind(d, k) 87 | if find == nil { 88 | log.Fatal("key is not find ") 89 | } 90 | } 91 | 92 | func createHashBucketMap() *map[int][]string { 93 | sizemask := 3 94 | m := make(map[int][]string) 95 | for i := 0; i < 100; i++ { 96 | idx := dictSdsHash(strconv.Itoa(i)) & sizemask 97 | m[idx] = append(m[idx], strconv.Itoa(i)) 98 | } 99 | for key, values := range m { 100 | fmt.Printf("%d: %v\n", key, len(values)) 101 | } 102 | return &m 103 | } 104 | 105 | func TestDictReplace(t *testing.T) { 106 | d := dictCreate(&dbDictType, nil) 107 | k := "hello" 108 | v := "mini-redis" 109 | dictAdd(d, createStringObject(&k, len(k)), createStringObject(&v, len(v))) 110 | 111 | k1 := "hello" 112 | v1 := "sharkchili" 113 | replace := dictReplace(d, createStringObject(&k1, len(k1)), createStringObject(&v1, len(v1))) 114 | 115 | if !replace { 116 | log.Fatal("replace fail") 117 | } 118 | 119 | } 120 | 121 | func TestDictDelete(t *testing.T) { 122 | 123 | d := dictCreate(&dbDictType, nil) 124 | bucketMap := createHashBucketMap() 125 | 126 | count := 0 127 | 128 | for _, v := range (*bucketMap)[0] { 129 | dictAdd(d, createStringObject(&v, len(v)), nil) 130 | count++ 131 | if count == 3 { 132 | break 133 | } 134 | } 135 | 136 | ht := &d.ht[0] 137 | printEntry((*ht.table)[0]) 138 | 139 | //17 12 9 都可以进行一次删除操作 140 | dictDelete(d, "9") 141 | printEntry((*ht.table)[0]) 142 | 143 | if ht.used != 2 { 144 | log.Fatal("delete fail ") 145 | } 146 | if dictDelete(d, "123123") != DICT_ERR { 147 | log.Fatal("delete fail ") 148 | } 149 | 150 | } 151 | 152 | func printEntry(entries *dictEntry) { 153 | fmt.Println("printEntry") 154 | for entries != nil { 155 | fmt.Println((*entries.key.ptr).(string)) 156 | entries = entries.next 157 | } 158 | } 159 | 160 | func TestDictRehash(t *testing.T) { 161 | d := dictCreate(&dbDictType, nil) 162 | bucketMap := createHashBucketMap() 163 | 164 | count := 0 165 | 166 | for _, v := range (*bucketMap)[0] { 167 | dictAdd(d, createStringObject(&v, len(v)), nil) 168 | count++ 169 | if count == 4 { 170 | break 171 | } 172 | } 173 | 174 | //sizemask := 7 175 | //m := make(map[int][]string) 176 | //for i := 0; i < 10000; i++ { 177 | // idx := dictSdsHash(strconv.Itoa(i)) & sizemask 178 | // m[idx] = append(m[idx], strconv.Itoa(i)) 179 | //} 180 | //for key, values := range m { 181 | // fmt.Printf("%d: %v\n", key, len(values)) 182 | //} 183 | k := "1000" 184 | dictAdd(d, createStringObject(&k, len(k)), nil) 185 | 186 | //查看19 17 12 9 1000是否存在 187 | if dictFind(d, "19") == nil || 188 | dictFind(d, "17") == nil || 189 | dictFind(d, "12") == nil || 190 | dictFind(d, "9") == nil || 191 | dictFind(d, "1000") == nil { 192 | log.Fatal("rehash fail") 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 前言 2 | 一直以来都来研究`redis`的设计理念和实现思路,随着时间的推移有了想自己实现一版的想法,由于笔者本身是一名`java`开发,对于`C语言`中的某些理念不是非常熟悉,于是折中选用`go语言`进行实现,而这个开源项目将会记录笔者实现`mini-redis`的一些开发思路和实现的介绍。 3 | 4 | `mini-redis`无论从函数名还是整体思路都基本沿用了原生`redis`的规范,唯一与C语言理念不同的是`go语言`本身对于`epoll`进行了封装,要想实现`redis`那种单线程处理所有指令的操作,笔者通过`channel`交互的方式来达到这一点。 5 | 6 | 同时,为了让读者更加直观的了解`redis`的实现核心脉络,笔者也在实现时也进行了一定简化,希望对那些想了解redis但是又不太熟悉`C语言`的开发朋友有所帮助。 7 | 8 | 9 | ## 如何部署运行mini-redis 10 | 11 | 这里笔者以`Linux`为例,我们找到项目中的`build-linux.sh`键入如下指令: 12 | 13 | ```bash 14 | ./build-linux.sh 15 | ``` 16 | 17 | 执行该指令之后,我们的项目会生成一个bin的文件夹,其内部一个`redis-server`的可执行文件,此时我们通过Linux服务器执行`./redis-server`,最终项目会输出如下内容,说明项目成功运行了: 18 | 19 | ```bash 20 | sharkchili@DESKTOPJ:~$ ./redis-server 21 | 2024/09/08 23:07:09 init redis server 22 | 2024/09/08 23:07:09 load redis server config 23 | 2024/09/08 23:07:09 this redis server address: localhost:6379 24 | 2024/09/08 23:07:09 event loop is listening and waiting for client connection. 25 | 26 | ``` 27 | 28 | 29 | 此时我们就可以通过redis-cli连接mini-redis了: 30 | 31 | ```bash 32 | sharkchili@DESKTOP-xxxx:~/redis/src$ ./redis-cli 33 | 127.0.0.1:6379> command 34 | 1) "COMMAND" 35 | 2) "PING" 36 | 127.0.0.1:6379> ping 37 | PONG 38 | 127.0.0.1:6379> 39 | 40 | ``` 41 | 42 | 43 | 44 | 45 | ## 目前的开发进度 46 | 47 | 截至目前,笔者完成了下面几个核心模块的开发: 48 | 49 | - `mini-redis`服务端ip端口绑定初始化。 50 | - `redis-cli`连接时将其封装为`redisClient`,并能够接收其命令请求。 51 | - 解析`redis-cli`通过`RESP`协议发送的命令请求。 52 | - 支持客户端键入command指令并回复当前服务端支持的指令集。 53 | - 支持客户端键入PING指令感知mini-redis是否可用。 54 | - 完成GET、SET指令指令解析和常规用法 55 | - 完成列表LINDEX、LPOP、RPUSH、LRANGE指令,可作为内存消息队列 56 | 57 | 58 | 后续开发计划: 59 | 60 | + [x] 字典数据结构开发 61 | + [x] 列表底层数据结构双向链表 62 | + [x] 有序集合等数据结构开发 63 | + [x] 字符串常规`GET`指令开发调测 64 | + [x] 字符串`SET`指令开发调测 65 | + [x] 列表操作LINDEX、LPOP、RPUSH、LRANGE指令开发 66 | + [x] 字典操作HSET、HMSET、HSETNX、HGET、HMGET、HGETALL、HDEL指令开发 67 | + [x] 有序集合所有操作指令开发 68 | + [ ] `AOF`持久化和重载机制 69 | + [ ] `LRU`缓存置换算法 70 | + [ ] 性能压测 71 | 72 | 73 | ## 如何阅读源码 74 | 75 | 本项目目录结构为: 76 | - `adlist.go` : redis底层双向链表实现 77 | - `adlist_test.go` : 双向链表测试单元 78 | - `client.go` : 处理redis-cli请求的客户端对象 79 | - `command.go` : redis所有操作指令实现 80 | - `db.go` : redis内存数据库 81 | - `dict.go` : 哈希对象操作实现 82 | - `networking.go` : 网络操作函数集 83 | - `object.go` : redis对象创建函数 84 | - `redis.conf` : 配置文件 85 | - `redis.go` : redis服务端 86 | - `t_hash.go` : 针对redis对象的哈希操作函数 87 | - `t_list.go` : 基于adlist双向链表对于redis对象的链表操作函数 88 | - `util.go` : mini-redis工具类 89 | - `main.go` : mini-redis启动入口 90 | - `go.mod` 91 | - `build-windows.sh` : Windows下程序启动脚本 92 | - `build-linux.sh` : Linux启动脚本 93 | - `README.md` 94 | 95 | 96 | 97 | 98 | 99 | 同时,在开发过程中的设计和实现也都会不断输出为文档,读者可以按需取用下面的文章来了解笔者的开发过程和实现思路: 100 | 101 | 102 | 来聊聊我用go手写redis这件事: 103 | 104 | mini-redis如何解析处理客户端请求 105 | : 106 | 107 | 实现mini-redis字符串操作: 108 | 109 | 110 | 硬核复刻redis底层双向链表核心实现: 111 | 112 | 聊聊我说如何用go语言实现redis列表操作: 113 | 114 | 动手复刻redis之go语言下的字典的设计与落地: 115 | 116 | Go 语言下的 Redis 跳表设计与实现: 117 | 118 | Go 语言版 Redis 有序集合指令复刻探索: 119 | 120 | mini-redis复刻Redis的INCR指令: 121 | 122 | 聊聊我基于dict优化mini-redis数据性能这件事: 123 | 124 | ## 关于我 125 | 126 | Hi,我是 **sharkChili** ,是个不断在硬核技术上作死的技术人,是 **CSDN的博客专家** ,也是开源项目 **Java Guide** 的维护者之一,熟悉 **Java** 也会一点 **Go** ,偶尔也会在 **C源码** 边缘徘徊。写过很多有意思的技术博客,也还在研究并输出技术的路上,希望我的文章对你有帮助,非常欢迎你关注我的公众号: **写代码的SharkChili** 。 127 | 128 | 因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 **“加群”** 即可和笔者和笔者的朋友们进行深入交流。 129 | 130 | 131 | ![image](https://github.com/user-attachments/assets/ed27dbdf-f0da-40b3-bb4e-cf948c4611a3) 132 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "log" 9 | "net" 10 | "strconv" 11 | ) 12 | 13 | const ( 14 | /* Client request types */ 15 | REDIS_REQ_INLINE = 1 16 | REDIS_REQ_MULTIBULK = 2 17 | ) 18 | 19 | type redisClient struct { 20 | //redis client connection info 21 | conn net.Conn 22 | argc uint64 23 | argv []*robj 24 | multibulklen int64 25 | reqType int 26 | queryBuf []byte 27 | cmd redisCommand 28 | lastCmd redisCommand 29 | db *redisDb 30 | } 31 | 32 | func readQueryFromClient(c *redisClient, CloseClientCh chan redisClient, commandCh chan redisClient) { 33 | //get the network reader through the redis client's connection. 34 | reader := bufio.NewReader(c.conn) 35 | //parse the string through the reader, and pass the parsing result to commandCh for Redis server to parse and execute. 36 | processInputBuffer(c, reader, CloseClientCh, commandCh) 37 | } 38 | 39 | func processInputBuffer(c *redisClient, reader *bufio.Reader, CloseClientCh chan redisClient, commandCh chan redisClient) { 40 | for { 41 | //initialize the array length to -1. 42 | c.multibulklen = -1 43 | //split each string by '\n' 44 | bytes, err := reader.ReadBytes('\n') 45 | c.queryBuf = bytes 46 | if err != nil { 47 | log.Println("the redis client has been closed") 48 | CloseClientCh <- *c 49 | break 50 | } 51 | //throw an exception if '\n' is not preceded by '\r'. 52 | if len(c.queryBuf) == 0 || (len(c.queryBuf) >= 2 && c.queryBuf[len(c.queryBuf)-2] != '\r') { 53 | _, _ = c.conn.Write([]byte("-ERR unknown command\r\n")) 54 | log.Println("ERR unknown command") 55 | continue 56 | } 57 | //If it starts with "*", it indicates a multiline string. 58 | if c.queryBuf[0] == '*' && c.multibulklen == -1 { 59 | //set the request type to multiline 60 | c.reqType = REDIS_REQ_MULTIBULK 61 | //get the length of the array based on the number following '*' 62 | c.multibulklen, err = strconv.ParseInt(string(c.queryBuf[1:len(c.queryBuf)-2]), 10, 32) 63 | if err != nil || c.multibulklen < 0 { 64 | _, _ = c.conn.Write([]byte("-ERR unknown command\r\n")) 65 | log.Println("ERR unknown command") 66 | continue 67 | } 68 | //based on the parsed length, initialize the size of the array. 69 | c.argv = make([]*robj, c.multibulklen) 70 | //based on the length indicated by "*", start parsing the string. 71 | res, e := processMultibulkBuffer(c, reader, CloseClientCh) 72 | if res == REDIS_ERR && e != nil { 73 | _, _ = c.conn.Write([]byte("-ERR unknown command\r\n")) 74 | log.Println("ERR unknown command") 75 | continue 76 | } else { 77 | commandCh <- *c 78 | } 79 | } else if c.queryBuf[0] == '*' && c.multibulklen > -1 { 80 | _, _ = c.conn.Write([]byte("-ERR unknown command\r\n")) 81 | log.Println("ERR unknown command") 82 | continue 83 | } else { 84 | //todo the processing logic for single-line instructions is to be completed subsequently 85 | c.multibulklen = REDIS_REQ_INLINE 86 | _, _ = c.conn.Write([]byte("-ERR unknown command\r\n")) 87 | continue 88 | } 89 | 90 | } 91 | 92 | } 93 | 94 | func processMultibulkBuffer(c *redisClient, reader *bufio.Reader, CloseClientCh chan redisClient) (int, error) { 95 | c.argc = 0 96 | //initialize "ll" to record the length following each "$", then fetch the string based on this length. 97 | ll := int64(-1) 98 | //perform a for loop based on "multibulklen". 99 | for i := 0; i < int(c.multibulklen); i++ { 100 | bytes, e := reader.ReadBytes('\n') 101 | c.queryBuf = bytes 102 | if e != nil && e == io.EOF { 103 | log.Println("the redis client has been closed") 104 | CloseClientCh <- *c 105 | break 106 | } else if e != nil { 107 | return REDIS_ERR, e 108 | } 109 | 110 | if len(c.queryBuf) == 0 || !(len(c.queryBuf)-2 >= 0 && c.queryBuf[len(c.queryBuf)-2] == '\r') { 111 | return REDIS_ERR, errors.New("ERR unknown command") 112 | } 113 | //if a "$" is intercepted in this line, store the following numerical value in "ll". 114 | if c.queryBuf[0] == '$' { 115 | ll, e = strconv.ParseInt(string(c.queryBuf[1:len(c.queryBuf)-2]), 10, 32) 116 | if e != nil || ll <= 0 { 117 | return REDIS_ERR, e 118 | } 119 | strBytes, e := reader.ReadBytes('\n') 120 | c.queryBuf = strBytes 121 | 122 | if e != nil { 123 | return REDIS_ERR, e 124 | } 125 | 126 | if len(c.queryBuf) == 0 || int64(len(c.queryBuf))-2 != ll { 127 | return REDIS_ERR, errors.New("ERR unknown command") 128 | } 129 | //parse and extract a string of specified length based on the value of "ll", store it in "argv", and then increment "argc". 130 | str := string(c.queryBuf[0 : len(c.queryBuf)-2]) 131 | c.argv[c.argc] = createStringObject(&str, len(str)) 132 | c.argc++ 133 | } else if c.queryBuf[0] != '$' && ll < 0 { //invalid str 134 | return REDIS_ERR, errors.New("ERR unknown command") 135 | } 136 | } 137 | 138 | return REDIS_OK, nil 139 | } 140 | 141 | func (c redisClient) string() string { 142 | return fmt.Sprintf("%#v", c) 143 | } 144 | -------------------------------------------------------------------------------- /adlist.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Definition of the listNode structure for a doubly linked list 4 | type listNode struct { 5 | //Node pointing to the previous node of the current node. 6 | prev *listNode 7 | //Node pointing to the successor node of the current node. 8 | next *listNode 9 | //Record information about the value stored in the current node. 10 | value *interface{} 11 | } 12 | 13 | type list struct { 14 | //Points to the first node of the doubly linked list 15 | head *listNode 16 | //points to the last node of the linked list. 17 | tail *listNode 18 | //Record the current length of the doubly linked list 19 | len int64 20 | } 21 | 22 | func listCreate() *list { 23 | //Allocate memory space for the doubly linked list 24 | var l *list 25 | l = new(list) 26 | 27 | //Initialize the head and tail pointers. 28 | l.head = nil 29 | l.tail = nil 30 | 31 | //Initialize the length to 0, indicating that the current linked list has no nodes 32 | l.len = 0 33 | 34 | return l 35 | } 36 | 37 | func listAddNodeHead(l *list, value *interface{}) *list { 38 | //Allocate memory for a new node and set its value. 39 | var node *listNode 40 | node = new(listNode) 41 | node.value = value 42 | //If the length is 0, then both the head and tail pointers point to the new node. 43 | if l.len == 0 { 44 | l.head = node 45 | l.tail = node 46 | } else { 47 | //Make the original head node the successor node of the new node, node. 48 | node.prev = nil 49 | node.next = l.head 50 | l.head.prev = node 51 | l.head = node 52 | } 53 | //Maintain the information about the length of the linked list. 54 | l.len++ 55 | return l 56 | } 57 | 58 | func listAddNodeTail(l *list, value *interface{}) *list { 59 | //Allocate memory for a new node and set its value. 60 | var node *listNode 61 | node = new(listNode) 62 | node.value = value 63 | //If the length is 0, then both the head and tail pointers point to the new node. 64 | if l.len == 0 { 65 | l.head = node 66 | l.tail = node 67 | } else { 68 | //Append the newly added node after the tail node to become the new tail node. 69 | node.prev = l.tail 70 | node.next = nil 71 | l.tail.next = node 72 | l.tail = node 73 | } 74 | //Maintain the information about the length of the linked list. 75 | l.len++ 76 | return l 77 | 78 | } 79 | 80 | func listInsertNode(l *list, old_node *listNode, value *interface{}, after bool) *list { 81 | //Allocate memory for a new node and set its value. 82 | var node *listNode 83 | node = new(listNode) 84 | node.value = value 85 | //If after is true, insert the new node after the old node. 86 | if after { 87 | node.prev = old_node 88 | node.next = old_node.next 89 | //If the old node was originally the tail node, after the modification, 90 | //make the node the new tail node. 91 | if l.tail == old_node { 92 | l.tail = node 93 | } 94 | } else { 95 | //Add the new node before the old node. 96 | node.next = old_node 97 | node.prev = old_node.prev 98 | //If the original node is the head, then set the new node as the head 99 | if l.head == old_node { 100 | l.head = node 101 | 102 | } 103 | } 104 | //If the node's predecessor node is not empty, then point the predecessor to the node. 105 | if node.prev != nil { 106 | node.prev.next = node 107 | } 108 | //If the node's successor node is not empty, make this successor point to the node. 109 | if node.next != nil { 110 | node.next.prev = node 111 | } 112 | //Maintain the information about the length of the linked list. 113 | l.len++ 114 | return l 115 | 116 | } 117 | 118 | func listDelNode(l *list, node *listNode) { 119 | //If the predecessor node is not empty, 120 | //then the predecessor node's next points to the successor node of the node being deleted 121 | if node.prev != nil { 122 | node.prev.next = node.next 123 | } else { 124 | //If the deleted node is the head node, set the head to point to the next node. 125 | l.head = node.next 126 | } 127 | 128 | //If next is not empty, then let next point to the node before the deleted node 129 | if node.next != nil { 130 | node.next.prev = node.prev 131 | } else { 132 | //If the deleted node is the tail node, make 133 | //the node before the deleted node the new tail node. 134 | l.tail = node.prev 135 | } 136 | //help gc 137 | node.prev = nil 138 | node.next = nil 139 | 140 | l.len-- 141 | 142 | } 143 | 144 | func listIndex(l *list, index int64) *listNode { 145 | var n *listNode 146 | //"If less than 0, calculate the index value as a positive number n, 147 | //then continuously jump to the node pointed to by prev based on this positive number n. 148 | if index < 0 { 149 | index = (-index) - 1 150 | n = l.tail 151 | 152 | for index > 0 && n != nil { 153 | n = n.prev 154 | index-- 155 | } 156 | } else { 157 | //Conversely, walk n steps from the front and reach the target node via next, then return. 158 | n = l.head 159 | for index > 0 && n != nil { 160 | n = n.next 161 | index-- 162 | } 163 | } 164 | 165 | return n 166 | } 167 | 168 | func listLength(l *list) int64 { 169 | return l.len 170 | } 171 | 172 | func listFirst(l *list) *listNode { 173 | return l.head 174 | 175 | } 176 | 177 | func listRelease(l **list) { 178 | *l = nil 179 | } 180 | -------------------------------------------------------------------------------- /adlist_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestListCreate(t *testing.T) { 8 | l := listCreate() 9 | if l.head != nil { 10 | t.Error("the head node is not nil") 11 | } 12 | if l.tail != nil { 13 | t.Error("the tail node is not nil") 14 | } 15 | if l.len != 0 { 16 | t.Error("linked list length initialization error.") 17 | } 18 | 19 | } 20 | 21 | func TestListAddNodeHead(t *testing.T) { 22 | l := listCreate() 23 | 24 | str := "first node" 25 | v := interface{}(&str) 26 | 27 | listAddNodeHead(l, &v) 28 | 29 | if l.head != l.tail { 30 | t.Error("inconsistency in initial insertion of head and tail nodes.") 31 | } 32 | var headValue *string 33 | headValue = (*l.head.value).(*string) 34 | 35 | if *headValue != "first node" { 36 | t.Error("failed to initialize value during head node insertion") 37 | } 38 | 39 | if l.len != 1 { 40 | t.Error("length remains unchanged after inserting an element") 41 | } 42 | 43 | secondNode := "second node" 44 | secV := interface{}(&secondNode) 45 | listAddNodeHead(l, &secV) 46 | 47 | headValue = (*l.head.value).(*string) 48 | if *headValue != "second node" { 49 | t.Error("failed to insert the head node.") 50 | } 51 | 52 | if l.len != 2 { 53 | t.Error("length remains unchanged after inserting an element") 54 | } 55 | 56 | } 57 | 58 | func TestListAddNodeTail(t *testing.T) { 59 | l := listCreate() 60 | 61 | secondNode := "second node" 62 | secV := interface{}(&secondNode) 63 | 64 | listAddNodeHead(l, &secV) 65 | 66 | firstNode := "first node" 67 | firstV := interface{}(&firstNode) 68 | 69 | listAddNodeHead(l, &firstV) 70 | 71 | thirdNode := "third node" 72 | thirdV := interface{}(&thirdNode) 73 | listAddNodeTail(l, &thirdV) 74 | 75 | if l.len != 3 { 76 | t.Error("failed to listAddNodeTail the tail node.") 77 | } 78 | 79 | tailValue := (*l.tail.value).(*string) 80 | 81 | if *tailValue != "third node" { 82 | t.Error("failed to listAddNodeTail the third node ") 83 | } 84 | 85 | tailPrevValue := (*l.tail.prev.value).(*string) 86 | if *tailPrevValue != "second node" { 87 | t.Error("failed to listAddNodeTail the third node ") 88 | } 89 | 90 | } 91 | 92 | func TestListIndex(t *testing.T) { 93 | l := listCreate() 94 | 95 | secondNode := "second node" 96 | secV := interface{}(&secondNode) 97 | 98 | listAddNodeHead(l, &secV) 99 | 100 | firstNode := "first node" 101 | firstV := interface{}(&firstNode) 102 | 103 | listAddNodeHead(l, &firstV) 104 | 105 | thirdNode := "third node" 106 | thirdV := interface{}(&thirdNode) 107 | listAddNodeTail(l, &thirdV) 108 | 109 | var node *listNode 110 | node = listIndex(l, 0) 111 | if *(*node.value).(*string) != "first node" { 112 | t.Error("incorrect query in listIndex.") 113 | } 114 | 115 | node = listIndex(l, 1) 116 | if *(*node.value).(*string) != "second node" { 117 | t.Error("incorrect query in listIndex.") 118 | } 119 | 120 | node = listIndex(l, 2) 121 | if *(*node.value).(*string) != "third node" { 122 | t.Error("incorrect query in listIndex.") 123 | } 124 | 125 | node = listIndex(l, -1) 126 | if *(*node.value).(*string) != "third node" { 127 | t.Error("incorrect query in listIndex.") 128 | } 129 | 130 | node = listIndex(l, -2) 131 | if *(*node.value).(*string) != "second node" { 132 | t.Error("incorrect query in listIndex.") 133 | } 134 | } 135 | 136 | func TestListDelNode(t *testing.T) { 137 | l := listCreate() 138 | 139 | secondNode := "second node" 140 | secV := interface{}(&secondNode) 141 | 142 | listAddNodeHead(l, &secV) 143 | 144 | firstNode := "first node" 145 | firstV := interface{}(&firstNode) 146 | 147 | listAddNodeHead(l, &firstV) 148 | 149 | thirdNode := "third node" 150 | thirdV := interface{}(&thirdNode) 151 | listAddNodeTail(l, &thirdV) 152 | 153 | fourthNode := "fourth node" 154 | fourthV := interface{}(&fourthNode) 155 | listAddNodeTail(l, &fourthV) 156 | 157 | delNode := listIndex(l, 1) 158 | listDelNode(l, delNode) 159 | 160 | if l.len != 3 { 161 | t.Fatal("listDelNode operation failed.") 162 | } 163 | 164 | listDelNode(l, l.head) 165 | if *(*l.head.value).(*string) != "third node" { 166 | t.Fatal("list del head node operation failed.") 167 | } 168 | 169 | listDelNode(l, l.tail) 170 | 171 | if *(*l.head.value).(*string) != "third node" { 172 | t.Fatal("list del tail node operation failed.") 173 | } 174 | } 175 | 176 | func TestListInsertNode(t *testing.T) { 177 | l := listCreate() 178 | 179 | secondNode := "second node" 180 | secV := interface{}(&secondNode) 181 | 182 | listAddNodeHead(l, &secV) 183 | 184 | firstNode := "first node" 185 | firstV := interface{}(&firstNode) 186 | 187 | listAddNodeHead(l, &firstV) 188 | 189 | thirdNode := "third node" 190 | thirdV := interface{}(&thirdNode) 191 | listAddNodeTail(l, &thirdV) 192 | 193 | fourthNode := "fourth node" 194 | fourthV := interface{}(&fourthNode) 195 | listAddNodeTail(l, &fourthV) 196 | 197 | var node *listNode 198 | node = listIndex(l, 0) 199 | 200 | insertNode := "insert node" 201 | insertNodeV := interface{}(&insertNode) 202 | listInsertNode(l, node, &insertNodeV, true) 203 | 204 | if *(*l.head.next.value).(*string) != "insert node" { 205 | t.Fatal("listInsertNode operation failed.") 206 | } 207 | 208 | } 209 | -------------------------------------------------------------------------------- /redis.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net" 7 | "strings" 8 | "sync" 9 | "sync/atomic" 10 | ) 11 | 12 | const ( 13 | REDIS_CMD_WRITE = 1 /* "w" flag */ 14 | REDIS_CMD_READONLY = 2 /* "r" flag */ 15 | REDIS_CMD_DENYOOM = 4 /* "m" flag */ 16 | REDIS_CMD_NOT_USED_1 = 8 /* no longer used flag */ 17 | REDIS_CMD_ADMIN = 16 /* "a" flag */ 18 | REDIS_CMD_PUBSUB = 32 /* "p" flag */ 19 | REDIS_CMD_NOSCRIPT = 64 /* "s" flag */ 20 | REDIS_CMD_RANDOM = 128 /* "R" flag */ 21 | REDIS_CMD_SORT_FOR_SCRIPT = 256 /* "S" flag */ 22 | REDIS_CMD_LOADING = 512 /* "l" flag */ 23 | REDIS_CMD_STALE = 1024 /* "t" flag */ 24 | REDIS_CMD_SKIP_MONITOR = 2048 /* "M" flag */ 25 | REDIS_CMD_ASKING = 4096 /* "k" flag */ 26 | REDIS_CMD_FAST = 8192 /* "F" flag */ 27 | /* Command call flags, see call() function */ 28 | REDIS_CALL_NONE = 0 29 | REDIS_CALL_SLOWLOG = 1 30 | REDIS_CALL_STATS = 2 31 | REDIS_CALL_PROPAGATE = 4 32 | REDIS_CALL_FULL = (REDIS_CALL_SLOWLOG | REDIS_CALL_STATS | REDIS_CALL_PROPAGATE) 33 | 34 | /* Units */ 35 | UNIT_SECONDS = 0 36 | UNIT_MILLISECONDS = 1 37 | 38 | REDIS_SET_NO_FLAGS = 0 39 | REDIS_SET_NX = (1 << 0) /* Set if key not exists. */ 40 | REDIS_SET_XX = (1 << 1) /* Set if key exists. */ 41 | 42 | REDIS_DEFAULT_DBNUM = 16 43 | 44 | /* Object types */ 45 | REDIS_STRING = 0 46 | REDIS_LIST = 1 47 | REDIS_SET = 2 48 | REDIS_ZSET = 3 49 | REDIS_HASH = 4 50 | 51 | /* Objects encoding. Some kind of objects like Strings and Hashes can be 52 | * internally represented in multiple ways. The 'encoding' field of the object 53 | * is set to one of this fields for this object. */ 54 | REDIS_ENCODING_RAW = 0 /* Raw representation */ 55 | REDIS_ENCODING_INT = 1 /* Encoded as integer */ 56 | REDIS_ENCODING_HT = 2 /* Encoded as hash table */ 57 | REDIS_ENCODING_ZIPMAP = 3 /* Encoded as zipmap */ 58 | REDIS_ENCODING_LINKEDLIST = 4 /* Encoded as regular linked list */ 59 | REDIS_ENCODING_ZIPLIST = 5 /* Encoded as ziplist */ 60 | REDIS_ENCODING_INTSET = 6 /* Encoded as intset */ 61 | REDIS_ENCODING_SKIPLIST = 7 /* Encoded as skiplist */ 62 | REDIS_ENCODING_EMBSTR = 8 /* Embedded sds string encoding */ 63 | 64 | /* List related stuff */ 65 | REDIS_HEAD = 0 66 | REDIS_TAIL = 1 67 | 68 | REDIS_SHARED_INTEGERS = 10000 69 | REDIS_SHARED_BULKHDR_LEN = 32 70 | 71 | REDIS_HASH_KEY = 1 72 | REDIS_HASH_VALUE = 2 73 | 74 | ZSKIPLIST_MAXLEVEL = 32 75 | ZSKIPLIST_P = 0.25 76 | ) 77 | 78 | type redisServer struct { 79 | //record the ip and port number of the redis server. 80 | ip string 81 | port int 82 | //semaphore used to notify shutdown. 83 | shutDownCh chan struct{} 84 | commandCh chan redisClient 85 | closeClientCh chan redisClient 86 | done atomic.Int32 87 | //record all connected clients. 88 | clients sync.Map 89 | //listen and process new connections. 90 | listen net.Listener 91 | commands map[string]redisCommand 92 | db []redisDb 93 | dbnum int 94 | } 95 | 96 | type robj = redisObject 97 | 98 | type redisObject struct { 99 | robjType int 100 | encoding int 101 | ptr *interface{} 102 | } 103 | 104 | /* 105 | * 106 | 跳表节点的定义 107 | */ 108 | type zskiplistNode struct { 109 | //记录元素的redis指针 110 | obj *robj 111 | //记录当前元素的数值,代表当前元素的优先级 112 | score float64 113 | //指向当前元素的前驱节点,即小于当前节点的元素 114 | backward *zskiplistNode 115 | //用一个zskiplistLevel数组维护本届点各层索引信息 116 | level []zskiplistLevel 117 | } 118 | 119 | type zskiplistLevel struct { 120 | //记录本层索引的前驱节点的指针 121 | forward *zskiplistNode 122 | //标识节点的本层索引需要跨几步才能到达该节点 123 | span int64 124 | } 125 | 126 | type zskiplist struct { 127 | //指向跳表的头节点 128 | header *zskiplistNode 129 | //指向跳表的尾节点 130 | tail *zskiplistNode 131 | //维护跳表的长度 132 | length int64 133 | //维护跳表当前索引的最高高度 134 | level int 135 | } 136 | 137 | /* 138 | 有序集合结构体 139 | */ 140 | type zset struct { 141 | dict map[string]*float64 142 | zsl *zskiplist 143 | } 144 | 145 | func initServer() { 146 | log.Println("init redis server") 147 | server.ip = "localhost" 148 | server.port = 6379 149 | server.shutDownCh = make(chan struct{}) 150 | server.closeClientCh = make(chan redisClient) 151 | server.commandCh = make(chan redisClient) 152 | 153 | createSharedObjects() 154 | server.db = make([]redisDb, server.dbnum) 155 | 156 | for j := 0; j < server.dbnum; j++ { 157 | server.db[j].id = j 158 | //server.db[j].dict = make(map[string]*robj) 159 | //server.db[j].expires = make(map[string]int64) 160 | server.db[j].dict = *dictCreate(&dbDictType, nil) 161 | server.db[j].expires = *dictCreate(&dbDictType, nil) 162 | } 163 | } 164 | 165 | func loadServerConfig() { 166 | log.Println("load redis server config") 167 | server.dbnum = REDIS_DEFAULT_DBNUM 168 | } 169 | 170 | func acceptTcpHandler(conn net.Conn) { 171 | //the current server is being or has been shut down, and no new connections are being processed. 172 | if server.done.Load() == 1 { 173 | log.Println("the current service is being shut down. The connection is denied.") 174 | _ = conn.Close() 175 | 176 | } 177 | //init the redis client and handles network read and write events. 178 | c := createClient(conn) 179 | server.clients.Store(c.string(), c) 180 | go readQueryFromClient(c, server.closeClientCh, server.commandCh) 181 | 182 | } 183 | 184 | func createClient(conn net.Conn) *redisClient { 185 | c := redisClient{conn: conn, argc: 0, argv: make([]*robj, 0), multibulklen: -1} 186 | selectDb(&c, 0) 187 | return &c 188 | } 189 | 190 | func closeRedisServer() { 191 | log.Println("close listen and all redis client") 192 | _ = server.listen.Close() 193 | server.clients.Range(func(key, value any) bool { 194 | client := value.(*redisClient) 195 | _ = client.conn.Close() 196 | server.clients.Delete(key) 197 | return true 198 | }) 199 | 200 | wg.Done() 201 | } 202 | 203 | func initServerConfig() { 204 | server.commands = make(map[string]redisCommand) 205 | 206 | populateCommandTable() 207 | } 208 | 209 | func populateCommandTable() { 210 | 211 | for i := 0; i < len(redisCommandTable); i++ { 212 | redisCommand := redisCommandTable[i] 213 | for _, f := range redisCommand.sflag { 214 | if f == 'w' { 215 | redisCommand.flag |= REDIS_CMD_WRITE 216 | } else if f == 'r' { 217 | redisCommand.flag |= REDIS_CMD_READONLY 218 | } else if f == 'm' { 219 | redisCommand.flag |= REDIS_CMD_DENYOOM 220 | } else if f == 'a' { 221 | redisCommand.flag |= REDIS_CMD_ADMIN 222 | } else if f == 'p' { 223 | redisCommand.flag |= REDIS_CMD_PUBSUB 224 | } else if f == 's' { 225 | redisCommand.flag |= REDIS_CMD_NOSCRIPT 226 | } else if f == 'R' { 227 | redisCommand.flag |= REDIS_CMD_RANDOM 228 | } else if f == 'S' { 229 | redisCommand.flag |= REDIS_CMD_SORT_FOR_SCRIPT 230 | } else if f == 'l' { 231 | redisCommand.flag |= REDIS_CMD_LOADING 232 | } else if f == 't' { 233 | redisCommand.flag |= REDIS_CMD_STALE 234 | } else if f == 'M' { 235 | redisCommand.flag |= REDIS_CMD_SKIP_MONITOR 236 | } else if f == 'K' { 237 | redisCommand.flag |= REDIS_CMD_ASKING 238 | } else if f == 'F' { 239 | redisCommand.flag |= REDIS_CMD_FAST 240 | } else { 241 | log.Panicln("Unsupported command flag") 242 | } 243 | 244 | server.commands[redisCommand.name] = redisCommand 245 | } 246 | 247 | } 248 | } 249 | 250 | func processCommand(c *redisClient) { 251 | //check the command table to see if the specified command exists. 252 | ptr := c.argv[0].ptr 253 | cmd, exists := server.commands[strings.ToUpper((*ptr).(string))] 254 | 255 | //assign the function of the command to "cmd". 256 | c.cmd = cmd 257 | c.lastCmd = cmd 258 | 259 | if !exists { 260 | c.conn.Write([]byte("-ERR unknown command\r\n")) 261 | return 262 | } else if (c.cmd.arity > 0 && c.cmd.arity != int64(c.argc)) || 263 | int64(c.argc) < -(c.cmd.arity) { 264 | reply := "wrong number of arguments for " + (*ptr).(string) + " command" 265 | addReplyError(c, &reply) 266 | return 267 | } 268 | 269 | //invoke "call" to pass the parameters to the function pointed to by "cmd" for processing. 270 | call(c, REDIS_CALL_FULL) 271 | } 272 | 273 | func call(c *redisClient, flags int) { 274 | c.cmd.proc(c) 275 | 276 | //todo aof use flags 277 | } 278 | 279 | func (o *robj) String() string { 280 | return fmt.Sprintf("%v", *o.ptr) 281 | } 282 | -------------------------------------------------------------------------------- /dict.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "math" 5 | ) 6 | 7 | const dict_hash_function_seed = 5381 8 | const DICT_OK = 0 9 | const DICT_ERR = 1 10 | const DICT_HT_INITIAL_SIZE = 4 11 | const dict_can_resize = 1 12 | const dict_force_resize_ratio = 5 13 | 14 | /** 15 | * 字典键值对定义 16 | */ 17 | type dictEntry struct { 18 | //存储key 19 | key *robj 20 | //存储value 21 | val *robj 22 | //存储后继节点 23 | next *dictEntry 24 | } 25 | 26 | /** 27 | * 字典哈希表定义 28 | */ 29 | type dictht struct { 30 | //存储键值对的数组 31 | table *[]*dictEntry 32 | //记录hash table的大小 33 | size uint64 34 | sizemask int 35 | //记录数组存储了多少个键值对 36 | used uint64 37 | } 38 | 39 | /** 40 | * 字典核心数据结构定义 41 | */ 42 | type dict struct { 43 | dType *dictType 44 | privdata *interface{} 45 | //存储键值对的两个数组 46 | ht *[2]dictht 47 | rehashidx int64 48 | iterators int 49 | } 50 | 51 | /** 52 | * 字典类型特定函数定义 53 | */ 54 | type dictType struct { 55 | hashFunction func(key string) int 56 | keyDup func(privdata *interface{}, key *interface{}) *interface{} 57 | valDup func(privdata *interface{}, obj *interface{}) *interface{} 58 | keyCompare func(privdata *interface{}, key1 string, key2 string) bool 59 | keyDestructor func(privdata *interface{}, key *interface{}) 60 | valDestructor func(privdata *interface{}, obj *interface{}) 61 | } 62 | 63 | func dictCreate(typePtr *dictType, privDataPtr *interface{}) *dict { 64 | //初始化字典及其ht数组空间 65 | d := dict{ht: &[2]dictht{}} 66 | _dictInit(&d, privDataPtr, typePtr) 67 | return &d 68 | } 69 | 70 | func _dictInit(d *dict, privDataPtr *interface{}, 71 | typePtr *dictType) int { 72 | //重置哈希表空间 73 | _dictReset(&(d.ht)[0]) 74 | _dictReset(&(d.ht)[1]) 75 | 76 | d.privdata = privDataPtr 77 | d.dType = typePtr 78 | //设置rehashidx为-1,代表当前不存在渐进式哈希 79 | d.rehashidx = -1 80 | //设置iterators为0,代表字典并不存在迭代 81 | d.iterators = 0 82 | 83 | return DICT_OK 84 | 85 | } 86 | 87 | func _dictReset(ht *dictht) { 88 | ht.table = nil 89 | ht.size = 0 90 | ht.sizemask = 0 91 | ht.used = 0 92 | } 93 | 94 | func _dictExpandIfNeeded(d *dict) int { 95 | if dictIsRehashing(d) { 96 | return DICT_OK 97 | } 98 | 99 | if d.ht[0].size == 0 { 100 | dictExpand(d, DICT_HT_INITIAL_SIZE) 101 | } 102 | 103 | if d.ht[0].used >= d.ht[0].size && 104 | (dict_can_resize == 1 || d.ht[0].used/d.ht[0].size > dict_force_resize_ratio) { 105 | dictExpand(d, d.ht[0].size<<1) 106 | } 107 | return DICT_OK 108 | } 109 | 110 | func dictAdd(d *dict, k *robj, v *robj) int { 111 | //将key存储到哈希表某个索引中,如果成功则返回这个key对应的entry的指针 112 | entry := dictAddRaw(d, k) 113 | if entry == nil { 114 | return DICT_ERR 115 | } 116 | //将entry的val设置为v 117 | entry.val = v 118 | return DICT_OK 119 | } 120 | 121 | func dictAddRaw(d *dict, k *robj) *dictEntry { 122 | //如果正处于渐进式哈希则会驱逐一部分元素到数组1中 123 | if dictIsRehashing(d) { 124 | _dictRehashStep(d) 125 | } 126 | //检查索引是否正确,若为-1则说明异常直接返回nil 127 | index := _dictKeyIndex(d, k) 128 | //检查key是否存在 129 | if index == -1 { 130 | return nil 131 | 132 | } 133 | //根据渐进式哈希表确定table 134 | var ht *dictht 135 | if dictIsRehashing(d) { 136 | ht = &d.ht[1] 137 | } else { 138 | ht = &d.ht[0] 139 | } 140 | 141 | //通过头插法将元素插入到数组中 142 | entry := &dictEntry{key: k} 143 | entry.next = (*(ht.table))[index] 144 | (*(ht.table))[index] = entry 145 | //累加used告知数组增加一个元素 146 | ht.used++ 147 | 148 | return entry 149 | } 150 | 151 | func dictDelete(ht *dict, key string) int { 152 | return dictGenericDelete(ht, key, 0) 153 | } 154 | 155 | // 删除字典中的key 156 | func dictGenericDelete(d *dict, k string, nofree int) int { 157 | //若size为0则说明没有bucket还未初始化,直接返回错误 158 | if d.ht[0].size == 0 { 159 | return DICT_ERR 160 | } 161 | 162 | if dictIsRehashing(d) { 163 | _dictRehashStep(d) 164 | } 165 | //哈希定位bucket 166 | h := dictGenHashFunction(k, len(k)) 167 | var preDe *dictEntry 168 | 169 | for i := 0; i < 2; i++ { 170 | idx := h & d.ht[i].sizemask 171 | he := (*(d.ht[i].table))[idx] 172 | //遍历链表直到找到这个key 173 | for he != nil { 174 | if (*he.key.ptr).(string) == k { 175 | //如果preDe非空则说明被删除元素he在中间,则将he前驱指向he后继 176 | if preDe != nil { 177 | preDe.next = he.next 178 | } else { //否则说明被删除元素he是数组的第一个元素,则直接让数组的第一个元素为he的后继节点 179 | (*(d.ht[0].table))[idx] = he.next 180 | } 181 | //减少used告知数组减少一个元素 182 | d.ht[i].used-- 183 | if nofree != 0 { 184 | //help gc 185 | he = nil 186 | } 187 | return DICT_OK 188 | } 189 | preDe = he 190 | he = he.next 191 | } 192 | 193 | if !dictIsRehashing(d) { 194 | break 195 | } 196 | } 197 | 198 | return DICT_ERR 199 | } 200 | 201 | // 原有go map字典操作更新函数,已废弃 202 | func dictReplace_new(d map[string]*robj, key *robj, val *robj) bool { 203 | k := (*key.ptr).(string) 204 | if _, e := d[k]; e { 205 | d[k] = val 206 | return false 207 | } else { 208 | d[k] = val 209 | return true 210 | } 211 | 212 | } 213 | 214 | func dictReplace(d *dict, key *robj, val *robj) bool { 215 | //先尝试用dictadd添加键值对,若成功则说明这个key不存在,完成后直接返回 216 | if dictAdd(d, key, val) == DICT_OK { 217 | return true 218 | } 219 | //否则通过dictFind定位到entry,修改值再返回true 220 | de := dictFind(d, (*key.ptr).(string)) 221 | if de == nil { 222 | return false 223 | } 224 | de.val = val 225 | return true 226 | 227 | } 228 | 229 | func dictFind(d *dict, key string) *dictEntry { 230 | //查看哈希表数组是否都为空,若都为空则直接返回 231 | if d.ht[0].used+d.ht[1].used == 0 { 232 | return nil 233 | } 234 | //若元素正处于渐进式哈希则进行一次元素驱逐 235 | if dictIsRehashing(d) { 236 | _dictRehashStep(d) 237 | } 238 | //定位查询key对应的哈希值 239 | h := dictGenHashFunction(key, len(key)) 240 | //执行最多两次的遍历(因为我们有两个哈希表,一个未扩容前使用,一个出发扩容后作为渐进式哈希的驱逐点) 241 | for i := 0; i < 2; i++ { 242 | //基于位运算定位索引 243 | idx := h & d.ht[i].sizemask 244 | //定位到对应bucket桶,通过遍历定位到本次要检索的key 245 | he := (*d.ht[0].table)[idx] 246 | for he != nil { 247 | if (*he.key.ptr).(string) == key { 248 | return he 249 | } 250 | he = he.next 251 | } 252 | //若未进行渐进式哈希则说明哈希表-1没有元素,直接结束循环,反之执行2次遍历 253 | if !dictIsRehashing(d) { 254 | break 255 | } 256 | } 257 | 258 | return nil 259 | 260 | } 261 | 262 | func dictIsRehashing(d *dict) bool { 263 | return d.rehashidx != -1 264 | } 265 | 266 | func _dictKeyIndex(d *dict, key *robj) int { 267 | 268 | var idx int 269 | if _dictExpandIfNeeded(d) == DICT_ERR { 270 | return -1 271 | } 272 | 273 | //计算索引位置 274 | h := d.dType.hashFunction((*key.ptr).(string)) 275 | 276 | //基于索引定位key 277 | for i := 0; i < 2; i++ { 278 | //通过位运算计算数组存储的索引 279 | idx = h & d.ht[i].sizemask 280 | he := (*(d.ht[i].table))[idx] 281 | //判断这个索引下是否存在相同的key,如果存在则返回-1,告知外部不用添加entry,有需要直接改dictentry的val即可 282 | for he != nil { 283 | if d.dType.keyCompare(nil, (*key.ptr).(string), (*he.key.ptr).(string)) { 284 | return -1 285 | } 286 | he = he.next 287 | } 288 | 289 | // 如果正在rehash,则检查ht[1] 290 | if !dictIsRehashing(d) { 291 | break 292 | } 293 | } 294 | 295 | return idx 296 | } 297 | 298 | func _dictRehashStep(d *dict) { 299 | if d.iterators == 0 { 300 | dictRehash(d, 1) 301 | } 302 | } 303 | 304 | // 渐进式哈希 305 | func dictRehash(d *dict, n int) int { 306 | //最大容错次数 307 | empty_visits := n * 10 308 | 309 | if !dictIsRehashing(d) { 310 | return 0 311 | } 312 | //循环n次的渐进式重试,在最大限制内完成 313 | for n > 0 && d.ht[0].used != 0 { 314 | n-- 315 | var de *dictEntry 316 | var nextde *dictEntry 317 | //定位到非空的bucket桶 318 | for (*(d.ht[0].table))[d.rehashidx] == nil { 319 | d.rehashidx++ 320 | empty_visits-- 321 | //一旦访问空bucket超过10次则返回 322 | if empty_visits == 0 { 323 | return 1 324 | } 325 | } 326 | //从非空的bucket桶开始 327 | de = (*(d.ht[0].table))[d.rehashidx] 328 | 329 | for de != nil { 330 | //再哈希定位元素通过头插法迁移元素到ht[1] 331 | nextde = de.next 332 | h := dictGenHashFunction((*de.key.ptr).(string), len((*de.key.ptr).(string))) & d.ht[1].sizemask 333 | de.next = (*(d.ht[1].table))[h] 334 | (*(d.ht[1].table))[h] = de 335 | 336 | d.ht[0].used-- 337 | d.ht[1].used++ 338 | 339 | de = nextde 340 | } 341 | //rehashidx+1告知下一次驱逐的索引位置 342 | (*(d.ht[0].table))[d.rehashidx] = nil 343 | d.rehashidx++ 344 | } 345 | //ht[0]为空则原子交换,将ht[1]变为ht[0] 346 | if d.ht[0].used == 0 { 347 | d.ht[0].table = nil 348 | d.ht[0] = d.ht[1] 349 | _dictReset(&(d.ht[1])) 350 | d.rehashidx = -1 351 | } 352 | 353 | return 1 354 | 355 | } 356 | 357 | // 字典扩容 358 | func dictExpand(d *dict, size uint64) int { 359 | var n dictht 360 | //获取实际空间 361 | realSize := _dictNextPower(size) 362 | //健壮性校验 363 | if dictIsRehashing(d) || d.ht[0].used > size { 364 | return DICT_ERR 365 | } 366 | 367 | if realSize == d.ht[0].size { 368 | return DICT_ERR 369 | } 370 | 371 | n.size = realSize 372 | n.sizemask = int(realSize - 1) 373 | n.table = &[]*dictEntry{} 374 | table := make([]*dictEntry, realSize) 375 | n.table = &table 376 | 377 | n.used = 0 378 | 379 | if d.ht[0].table == nil { 380 | d.ht[0] = n 381 | return DICT_OK 382 | } 383 | d.ht[1] = n 384 | d.rehashidx = 0 385 | 386 | return DICT_OK 387 | 388 | } 389 | 390 | func _dictNextPower(size uint64) uint64 { 391 | i := DICT_HT_INITIAL_SIZE 392 | 393 | if size >= math.MaxInt64 { 394 | return math.MaxInt64 + 1 395 | } 396 | 397 | for true { 398 | if uint64(i) >= size { 399 | break 400 | } 401 | i = i << 1 402 | } 403 | return uint64(i) 404 | } 405 | -------------------------------------------------------------------------------- /t_zset.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "math/rand" 5 | ) 6 | 7 | func zslCreate() *zskiplist { 8 | var j int 9 | //初始化跳表结构体 10 | zsl := new(zskiplist) 11 | //索引默认高度为1 12 | zsl.level = 1 13 | //跳表元素初始化为0 14 | zsl.length = 0 15 | //初始化一个头节点socre为0,元素为空 16 | zsl.header = zslCreateNode(ZSKIPLIST_MAXLEVEL, 0, nil) 17 | 18 | /** 19 | 基于跳表最大高度32初始化头节点的索引, 20 | 使得前驱指针指向null 跨度也设置为0 21 | */ 22 | for j = 0; j < ZSKIPLIST_MAXLEVEL; j++ { 23 | zsl.header.level[j].forward = nil 24 | zsl.header.level[j].span = 0 25 | } 26 | //头节点的前驱节点指向null,代表头节点之前没有任何元素 27 | zsl.header.backward = nil 28 | //初始化尾节点 29 | zsl.tail = nil 30 | return zsl 31 | } 32 | 33 | func zslCreateNode(level int, score float64, obj *robj) *zskiplistNode { 34 | zn := new(zskiplistNode) 35 | zn.level = make([]zskiplistLevel, level) 36 | zn.score = score 37 | zn.obj = obj 38 | return zn 39 | } 40 | 41 | func zslInsert(zsl *zskiplist, score float64, obj *robj) *zskiplistNode { 42 | //创建一个update数组,记录插入节点每层索引中小于该score的最大值 43 | update := make([]*zskiplistNode, ZSKIPLIST_MAXLEVEL) 44 | //记录各层索引走到小于score最大节点的跨区 45 | rank := make([]int64, ZSKIPLIST_MAXLEVEL) 46 | //x指向跳表走节点 47 | x := zsl.header 48 | var i int 49 | //从跳表当前最高层索引开始,查找每层小于当前score的节点的最大值节点 50 | for i = zsl.level - 1; i >= 0; i-- { 51 | //如果当前索引是最高层索引,那么rank从0开始算 52 | if i == zsl.level-1 { 53 | rank[i] = 0 54 | } else { //反之本层索引直接从上一层的跨度开始往后查找 55 | rank[i] = rank[i+1] 56 | } 57 | /** 58 | 如果前驱节点不为空,且符合以下条件,则指针前移: 59 | 1. 节点小于当前插入节点的score 60 | 2. 节点score一致,且元素值小于或者等于当前score 61 | */ 62 | for x.level[i].forward != nil && 63 | (x.level[i].forward.score < score || (x.level[i].forward.score == score && x.level[i].forward.obj.String() < obj.String())) { 64 | //记录本层索引前移跨度 65 | rank[i] += x.level[i].span 66 | //索引指针先前移动 67 | x = x.level[i].forward 68 | 69 | } 70 | //记录本层小于当前score的最大节点 71 | update[i] = x 72 | } 73 | //随机生成新插入节点的索引高度 74 | level := zslRandomLevel() 75 | /** 76 | 如果大于当前索引高度,则进行初始化,将这些高层索引的update数组都指向header节点,跨度设置为跳表中的元素数 77 | 意为这些高层索引小于插入节点的最大值就是header 78 | */ 79 | if level > zsl.level { 80 | for i := zsl.level; i < level; i++ { 81 | rank[i] = 0 82 | update[i] = zsl.header 83 | update[i].level[i].span = zsl.length 84 | } 85 | //更新一下跳表索引的高度 86 | zsl.level = level 87 | } 88 | //基于入参生成一个节点 89 | x = zslCreateNode(level, score, obj) 90 | //从底层到当前最高层索引处理节点关系 91 | for i = 0; i < level; i++ { 92 | //将小于当前节点的最大节点的forward指向插入节点x,同时x指向这个节点的前向节点 93 | x.level[i].forward = update[i].level[i].forward 94 | update[i].level[i].forward = x 95 | //维护x和update所指向节点之间的跨度信息 96 | x.level[i].span = update[i].level[i].span - (rank[0] - rank[i]) 97 | update[i].level[i].span = rank[0] - rank[i] + 1 98 | } 99 | /** 100 | 考虑到当前插入节点生成的level小于当前跳表最高level的情况 101 | 该逻辑会将这些区间的update索引中的元素到其前方节点的跨度+1,即代表这些层级索引虽然没有指向x节点, 102 | 但因为x节点插入的缘故跨度要加1 103 | */ 104 | for i = level; i < zsl.level; i++ { 105 | update[i].level[i].span++ 106 | } 107 | //如果1级索引是header,则x后继节点不指向该节点,反之指向 108 | if update[0] == zsl.header { 109 | x.backward = nil 110 | } else { 111 | x.backward = update[0] 112 | } 113 | //如果x前向节点不为空,则让前向节点指向x 114 | if x.level[0].forward != nil { 115 | x.level[0].forward.backward = x 116 | } else { //反之说明x是尾节点,tail指针指向它 117 | zsl.tail = x 118 | } 119 | //维护跳表长度信息 120 | zsl.length++ 121 | return x 122 | } 123 | 124 | func zslRandomLevel() int { 125 | level := 1 126 | for rand.Float64() < ZSKIPLIST_P && level < ZSKIPLIST_MAXLEVEL { 127 | level++ 128 | } 129 | return level 130 | } 131 | 132 | func zslGetRank(zsl *zskiplist, score float64, obj *robj) int64 { 133 | var rank int64 134 | //从索引最高节点开始进行查找 135 | x := zsl.header 136 | for i := zsl.level - 1; i >= 0; i-- { 137 | //如果前向节点不为空且score小于查找节点,或者score相等,但是元素字符序比值小于或者等于则前移,同时用rank记录跨度 138 | for x.level[i].forward != nil && 139 | (x.level[i].forward.score < score || (x.level[i].forward.score == score && x.level[i].forward.obj.String() <= obj.String())) { 140 | rank += x.level[i].span 141 | x = x.level[i].forward 142 | } 143 | //上述循环结束,比对一直,则返回经过的跨度 144 | if x.obj != nil && x.obj.String() == obj.String() { 145 | return rank 146 | } 147 | } 148 | return 0 149 | } 150 | 151 | func zslDelete(zsl *zskiplist, score float64, obj *robj) int64 { 152 | update := make([]*zskiplistNode, ZSKIPLIST_MAXLEVEL) 153 | //找到每层索引要删除节点的前一个节点 154 | x := zsl.header 155 | for i := zsl.level - 1; i >= 0; i-- { 156 | for x.level[i].forward != nil && 157 | (x.level[i].forward.score < score || (x.level[i].forward.score == score && x.level[i].forward.obj.String() < obj.String())) { 158 | x = x.level[i].forward 159 | } 160 | update[i] = x 161 | } 162 | //查看1级索引前面是否就是要删除的节点,如果是则直接调用zslDeleteNode删除节点,并断掉前后节点关系 163 | x = x.level[0].forward 164 | if x != nil && x.obj.String() == obj.String() { 165 | zslDeleteNode(zsl, x, update) 166 | return 1 167 | } 168 | return 0 169 | } 170 | 171 | func zslDeleteNode(zsl *zskiplist, x *zskiplistNode, update []*zskiplistNode) { 172 | 173 | var i int 174 | for i = 0; i < zsl.level; i++ { 175 | /* 176 | 如果索引前方就是删除节点,当前节点span为: 177 | 当前节点到x +x到x前向节点 -1 178 | */ 179 | if update[i].level[i].forward == x { 180 | update[i].level[i].span += x.level[i].span - 1 181 | update[i].level[i].forward = x.level[i].forward 182 | } else { 183 | //反之说明该节点前方不是x的索引,直接减去x的跨步1即 184 | update[i].level[i].span -= 1 185 | } 186 | } 187 | //维护删除后的节点前后关系 188 | if x.level[0].forward != nil { 189 | x.level[0].forward.backward = x.backward 190 | } else { 191 | zsl.tail = x.backward 192 | } 193 | //将全空层的索引删除 194 | for zsl.level > 1 && zsl.header.level[zsl.level-1].forward == nil { 195 | zsl.level-- 196 | } 197 | //维护跳表节点信息 198 | zsl.length-- 199 | 200 | } 201 | 202 | func zaddCommand(c *redisClient) { 203 | //传入0,即本次传入的score在元素存在情况下执行覆盖score而非累加score 204 | zaddGenericCommand(c, 0) 205 | } 206 | 207 | func zaddGenericCommand(c *redisClient, incr int) { 208 | //拿到有序集合的key 209 | key := c.argv[1] 210 | 211 | var ele *robj 212 | var zobj *robj 213 | var j uint64 214 | //var score float64 215 | //初始化变量记录本次操作添加和更新的元素数 216 | var added int64 217 | var updated int64 218 | 219 | //参数非偶数,入参异常直接输出错误后返回 220 | if c.argc%2 != 0 { 221 | addReplyError(c, shared.syntaxerr) 222 | return 223 | } 224 | //减去zadd和key 再除去2 得到本次插入的元素数 225 | elements := (c.argc - 2) / 2 226 | 227 | //创建scores记录每个元素对应的score值 228 | scores := make([]float64, elements) 229 | for j = 0; j < elements; j++ { 230 | //对score进行转换,若报错直接返回 231 | if !getDoubleFromObjectOrReply(c, c.argv[2+j*2], &scores[j], nil) { 232 | return 233 | } 234 | } 235 | 236 | //若为空则创建一个有序集合,并添加到数据库中 237 | zobj = lookupKeyWrite(c.db, c.argv[1]) 238 | if zobj == nil { 239 | zobj = createZsetObject() 240 | dbAdd(c.db, key, zobj) 241 | } else if zobj.robjType != REDIS_ZSET { //若类型不对则返回异常 242 | addReply(c, shared.wrongtypeerr) 243 | return 244 | } 245 | 246 | zs := (*zobj.ptr).(*zset) 247 | 248 | //基于元素数遍历集合 249 | for j = 0; j < elements; j++ { 250 | //拿到本次元素对应的score 251 | //score = scores[j] 252 | //拿到对应的元素 253 | ele = c.argv[3+j*2] 254 | k := (*ele.ptr).(string) 255 | 256 | //如果该元素存在于字典中 257 | if zs.dict[k] != nil { 258 | //拿到当前元素对应的score 259 | curScore := zs.dict[k] 260 | //若不一样则更新字典中对应元素的score,并将该元素从跳表中删除再插入 261 | if *curScore != scores[j] { 262 | zslDelete(zs.zsl, *curScore, c.argv[3+j*2]) 263 | zslInsert(zs.zsl, scores[j], c.argv[3+j*2]) 264 | zs.dict[k] = &scores[j] 265 | //维护更新数 266 | updated++ 267 | } 268 | 269 | } else { //若是新增则插入到有序集合对应的跳表和字典中 270 | zslInsert(zs.zsl, scores[j], c.argv[3+j*2]) 271 | zs.dict[k] = &scores[j] 272 | //维护添加数 273 | added++ 274 | } 275 | 276 | } 277 | 278 | //返回本次插入数 279 | addReplyLongLong(c, added) 280 | 281 | } 282 | 283 | func zcardCommand(c *redisClient) { 284 | //限定为有序集合是否存在且类型是否为有序集合 285 | zobj := lookupKeyReadOrReply(c, c.argv[1], shared.czero) 286 | if zobj == nil || checkType(c, zobj, REDIS_ZSET) { 287 | return 288 | } 289 | //拿到其底层的跳表返回元素数 290 | zs := (*zobj.ptr).(*zset) 291 | addReplyLongLong(c, zs.zsl.length) 292 | } 293 | 294 | func zrankCommand(c *redisClient) { 295 | zrankGenericCommand(c, 0) 296 | } 297 | 298 | func zrankGenericCommand(c *redisClient, reverse int) { 299 | //从参数中拿到有序集合的key和本次要查看排名的元素 300 | key := c.argv[1] 301 | ele := c.argv[2] 302 | 303 | //查看有序集合是否存在 304 | o := lookupKeyReadOrReply(c, key, nil) 305 | if o == nil || checkType(c, o, REDIS_ZSET) { 306 | return 307 | } 308 | //获取有序集合底层的跳表的长度 309 | zs := (*o.ptr).(*zset) 310 | llen := zs.zsl.length 311 | //查看元素在字典中是否存在 312 | k := (*ele.ptr).(string) 313 | score, exists := zs.dict[k] 314 | //如果存在则查看其在跳表中的排名 315 | if exists { 316 | //zslGetRank返回元素从头节点开始算经过的步数,例如aa是第一个元素,那么header走到它需要跨1步,所以返回1 317 | rank := zslGetRank(zs.zsl, *score, ele) 318 | //如果要返回倒叙结果则基于长度减去rank 319 | if reverse == 1 { 320 | addReplyLongLong(c, llen-rank) 321 | } else { 322 | //将rank减去1得到元素实际的索引值 323 | addReplyLongLong(c, rank-1) 324 | } 325 | } else { //不存在返回空 326 | addReply(c, shared.nullbulk) 327 | } 328 | 329 | } 330 | 331 | func zremCommand(c *redisClient) { 332 | var deleted int64 333 | //检查有序集合是否存在且类型是否是有序集合类型,如果为空或者类型不一致则返回 334 | o := lookupKeyWriteOrReply(c, c.argv[1], shared.czero) 335 | if o == nil || checkType(c, o, REDIS_ZSET) { 336 | return 337 | } 338 | zs := (*o.ptr).(*zset) 339 | 340 | var j uint64 341 | //遍历元素 342 | for j = 2; j < c.argc; j++ { 343 | //拿到元素字符串 344 | ele := (*c.argv[j].ptr).(string) 345 | //如果不为空则将其从底层字典和跳表中删除 346 | if zs.dict[ele] != nil { 347 | //更新删除结果 348 | deleted++ 349 | zslDelete(zs.zsl, *zs.dict[ele], c.argv[j]) 350 | delete(zs.dict, ele) 351 | 352 | //如果发现字典为空,说明有序集合没有元素了,直接将该有序集合从字典中期删除 353 | if len(zs.dict) == 0 { 354 | dbDelete(c.db, c.argv[1]) 355 | } 356 | } 357 | } 358 | //返回删除数 359 | addReplyLongLong(c, deleted) 360 | 361 | } 362 | -------------------------------------------------------------------------------- /command.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "math" 6 | "strconv" 7 | "strings" 8 | "time" 9 | ) 10 | 11 | type redisCommandProc func(redisClient *redisClient) 12 | 13 | type redisCommand struct { 14 | name string 15 | proc redisCommandProc 16 | arity int64 17 | sflag string 18 | flag int 19 | } 20 | 21 | var redisCommandTable = []redisCommand{ 22 | {name: "COMMAND", proc: commandCommand, arity: 0, sflag: "rlt", flag: 0}, 23 | {name: "PING", proc: pingCommand, arity: 0, sflag: "rtF", flag: 0}, 24 | {name: "SET", proc: setCommand, sflag: "rtF", flag: 0}, 25 | {name: "GET", proc: getCommand, sflag: "rtF", flag: 0}, 26 | {name: "RPUSH", proc: rpushCommand, sflag: "wmF", flag: 0}, 27 | {name: "LRANGE", proc: lrangeCommand, sflag: "r", flag: 0}, 28 | {name: "LINDEX", proc: lindexCommand, sflag: "r", flag: 0}, 29 | {name: "LPOP", proc: lpopCommand, sflag: "wF", flag: 0}, 30 | {name: "HSET", proc: hsetCommand, arity: 4, sflag: "wmF", flag: 0}, 31 | {name: "HMSET", proc: hmsetCommand, arity: -4, sflag: "wm", flag: 0}, 32 | {name: "HSETNX", proc: hsetnxCommand, arity: 4, sflag: "wm", flag: 0}, 33 | {name: "HGET", proc: hgetCommand, arity: 3, sflag: "rF", flag: 0}, 34 | {name: "HMGET", proc: hmgetCommand, arity: -3, sflag: "r", flag: 0}, 35 | {name: "HGETALL", proc: hgetallCommand, arity: 2, sflag: "r", flag: 0}, 36 | {name: "HDEL", proc: hdelCommand, arity: -3, sflag: "wF", flag: 0}, 37 | {name: "ZADD", proc: zaddCommand, arity: -4, sflag: "wmF", flag: 0}, 38 | {name: "ZREM", proc: zremCommand, arity: -3, sflag: "wF", flag: 0}, 39 | {name: "ZCARD", proc: zcardCommand, arity: 2, sflag: "rF", flag: 0}, 40 | {name: "ZRANK", proc: zrankCommand, arity: 3, sflag: "rF", flag: 0}, 41 | {name: "INCR", proc: incrCommand, arity: 2, sflag: "wmF", flag: 0}, 42 | {name: "DECR", proc: decrCommand, arity: 2, sflag: "wmF", flag: 0}, 43 | {name: "SCAN", proc: scanCommand, arity: -2, sflag: "rR", flag: 0}, 44 | } 45 | var shared sharedObjectsStruct 46 | 47 | type sharedObjectsStruct struct { 48 | crlf *string 49 | ok *string 50 | err *string 51 | pong *string 52 | syntaxerr *string 53 | nullbulk *string 54 | wrongtypeerr *string 55 | czero *string 56 | cone *string 57 | colon *string 58 | emptymultibulk *string 59 | integers [REDIS_SHARED_INTEGERS]*robj //通用0~9999常量数值池 60 | bulkhdr [REDIS_SHARED_BULKHDR_LEN]*robj 61 | } 62 | 63 | func commandCommand(c *redisClient) { 64 | reply := "*" + strconv.Itoa(len(server.commands)) + *shared.crlf 65 | for _, command := range server.commands { 66 | reply += "$" + strconv.Itoa(len(command.name)) + *shared.crlf + command.name + *shared.crlf 67 | } 68 | 69 | addReply(c, &reply) 70 | } 71 | 72 | func pingCommand(c *redisClient) { 73 | addReply(c, shared.pong) 74 | } 75 | 76 | func setCommand(c *redisClient) { 77 | var j uint64 78 | var expire string 79 | unit := UNIT_SECONDS 80 | flags := REDIS_SET_NO_FLAGS 81 | //traverse the arguments after setting the key-value pair in a set. 82 | for j = 3; j < c.argc; j++ { 83 | ptr := c.argv[j].ptr 84 | a := (*ptr).(string) 85 | var next string 86 | if j == c.argc-1 { 87 | next = "" 88 | } else { 89 | nextPtr := c.argv[j+1].ptr 90 | next = (*nextPtr).(string) 91 | } 92 | // if the string "nx" is included, mark through bitwise operations that the current key can only be set when it does not exist. 93 | if strings.ToLower(a) == "nx" { 94 | flags |= REDIS_SET_NX 95 | } else if strings.ToLower(a) == "xx" { //if "xx" is included, mark the flags to indicate that the key can only be set if it already exists. 96 | flags |= REDIS_SET_XX 97 | } else if strings.ToLower(a) == "ex" { //if it is "ex", set the unit to seconds and read the next parameter. 98 | unit = UNIT_SECONDS 99 | expire = next 100 | j++ 101 | } else if strings.ToLower(a) == "px" { //if it is "px", set the unit to milliseconds and read the next parameter. 102 | unit = UNIT_MILLISECONDS 103 | expire = next 104 | j++ 105 | } else { // Treat all other cases as exceptions. 106 | addReply(c, shared.syntaxerr) 107 | return 108 | } 109 | } 110 | //pass the key, value, instruction identifier flags, and expiration time unit into `setGenericCommand` for memory persistence operation. 111 | setGenericCommand(c, flags, c.argv[1], c.argv[2], expire, unit, "", "") 112 | } 113 | 114 | func setGenericCommand(c *redisClient, flags int, key *robj, val *robj, expire string, unit int, ok_reply string, abort_reply string) { 115 | //initialize a pointer to record the expiration time in milliseconds. 116 | var milliseconds *int64 117 | milliseconds = new(int64) 118 | //if `expire` is not empty, parse it as an int64 and store it in `milliseconds`. 119 | if expire != "" { 120 | if getLongLongFromObjectOrReply(c, expire, milliseconds, nil) != REDIS_OK { 121 | return 122 | } 123 | 124 | if unit == UNIT_SECONDS { 125 | *milliseconds = *milliseconds * 1000 126 | } 127 | } 128 | /** 129 | the following two cases will no longer undergo key-value persistence operations: 130 | 1. if the command contains "nx" and the data exists for this value. 131 | 2. if the command contains "xx" and the data for this value does not exist. 132 | */ 133 | if (flags&REDIS_SET_NX > 0 && lookupKeyWrite(c.db, key) != nil) || 134 | (flags&REDIS_SET_XX > 0 && lookupKeyWrite(c.db, key) == nil) { 135 | addReply(c, shared.nullbulk) 136 | return 137 | } 138 | //if `expire` is not empty, add the converted value to the current time to obtain the expiration time. Then, 139 | //use the passed key as the key and the expiration time as the value to store in the `expires` dictionary. 140 | if expire != "" { 141 | //c.db.expires[(*key.ptr).(string)] = 142 | when := createStringObjectFromLongLong(time.Now().UnixMilli() + *milliseconds) 143 | dictReplace(&c.db.expires, key, when) 144 | } 145 | //store the key-value pair in a dictionary. 146 | //c.db.dict[(*key.ptr).(string)] = val 147 | dbAdd(c.db, key, val) 148 | addReply(c, shared.ok) 149 | } 150 | 151 | func incrCommand(c *redisClient) { 152 | //累加1 153 | incrDecrCommand(c, 1) 154 | } 155 | 156 | func decrCommand(c *redisClient) { 157 | //递减1 158 | incrDecrCommand(c, -1) 159 | } 160 | 161 | func incrDecrCommand(c *redisClient, incr int64) { 162 | var value int64 163 | var oldValue int64 164 | var newObj *robj 165 | //查看键值对是否存在 166 | o := lookupKeyWrite(c.db, c.argv[1]) 167 | //如果键值对存在且类型非字符串类型,直接响应错误并返回 168 | if o != nil && checkType(c, o, REDIS_STRING) { 169 | return 170 | } 171 | /** 172 | 针对字符串类型的值进行如下判断的和转换: 173 | 1. 如果为空,说明本次的key不存在,直接初始化一个空字符串,后续会直接初始化一个0值使用 174 | 2. 如果是字符串类型,则转为字符串类型 175 | 3. 如果是数值类型,则先转为字符串类型进行后续的通用数值转换操作保证一致性 176 | */ 177 | var s string 178 | if o == nil { 179 | s = "" 180 | } else if isString(*o.ptr) { 181 | s = (*o.ptr).(string) 182 | } else { 183 | s = strconv.FormatInt((*o.ptr).(int64), 10) 184 | } 185 | //进行类型强转为数值,如果失败,直接输出错误并返回 186 | if getLongLongFromObjectOrReply(c, s, &value, nil) != REDIS_OK { 187 | return 188 | } 189 | 190 | oldValue = value 191 | 192 | if (incr < 0 && oldValue < 0 && incr < (math.MinInt64-oldValue)) || 193 | (incr > 0 && oldValue > 0 && incr > (math.MaxInt64-oldValue)) { 194 | errReply := "increment or decrement would overflow" 195 | addReplyError(c, &errReply) 196 | return 197 | } 198 | //基于incr累加的值生成value 199 | value += incr 200 | //如果超常量池范围则封装一个对象使用 201 | if o != nil && 202 | (value < 0 || value >= REDIS_SHARED_INTEGERS) && 203 | (value > math.MinInt64 || value < math.MaxInt64) { 204 | newObj = o 205 | 206 | i := interface{}(value) 207 | o.ptr = &i 208 | } else if o != nil { //如果对象存在,且累加结果没超范围则调用createStringObjectFromLongLong获取常量对象 209 | newObj = createStringObjectFromLongLong(value) 210 | //将写入结果覆盖 211 | dbOverwrite(c.db, c.argv[1], newObj) 212 | } else { //从常量池获取数值,然后添加键值对到数据库中 213 | newObj = createStringObjectFromLongLong(value) 214 | dbAdd(c.db, c.argv[1], newObj) 215 | } 216 | //将累加后的结果返回给客户端,按照RESP格式即 :数值\r\n,例如返回10 那么格式就是:10\r\n 217 | reply := *shared.colon + strconv.FormatInt(value, 10) + *shared.crlf 218 | addReply(c, &reply) 219 | 220 | } 221 | 222 | func isString(x interface{}) bool { 223 | _, ok := x.(string) 224 | return ok 225 | } 226 | 227 | func createSharedObjects() { 228 | crlf := "\r\n" 229 | ok := "+OK\r\n" 230 | err := "-ERR\r\n" 231 | pong := "+PONG\r\n" 232 | syntaxerr := "-ERR syntax error\r\n" 233 | nullbulk := "$-1\r\n" 234 | wrongtypeerr := "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n" 235 | czero := ":0\r\n" 236 | cone := ":1\r\n" 237 | colon := ":" 238 | emptymultibulk := "*0\r\n" 239 | 240 | shared = sharedObjectsStruct{ 241 | crlf: &crlf, 242 | ok: &ok, 243 | err: &err, 244 | pong: &pong, 245 | syntaxerr: &syntaxerr, 246 | nullbulk: &nullbulk, 247 | wrongtypeerr: &wrongtypeerr, 248 | czero: &czero, 249 | cone: &cone, 250 | colon: &colon, 251 | emptymultibulk: &emptymultibulk, 252 | } 253 | 254 | var i int64 255 | //初始化常量池对象 256 | for i = 0; i < REDIS_SHARED_INTEGERS; i++ { 257 | //基于接口封装数值 258 | num := interface{}(i) 259 | //生成string对象 260 | shared.integers[i] = createObject(REDIS_STRING, &num) 261 | //声明编码类型为int 262 | shared.integers[i].encoding = REDIS_ENCODING_INT 263 | } 264 | 265 | for i := 0; i < REDIS_SHARED_BULKHDR_LEN; i++ { 266 | s := "*" + strconv.Itoa(i) + "\r\n" 267 | intf := interface{}(s) 268 | shared.bulkhdr[i] = createObject(REDIS_STRING, &intf) 269 | } 270 | } 271 | 272 | func selectDb(c *redisClient, id int) { 273 | c.db = &server.db[id] 274 | } 275 | 276 | func getCommand(c *redisClient) { 277 | getGenericCommand(c) 278 | } 279 | 280 | func getGenericCommand(c *redisClient) int { 281 | //check if the key exists, and if it does not, return a null bulk response from the constant values. 282 | o := lookupKeyReadOrReply(c, c.argv[1], shared.nullbulk) 283 | if o == nil { 284 | return REDIS_OK 285 | } 286 | //return the value to the client if it exists. 287 | addReplyBulk(c, o) 288 | return REDIS_OK 289 | } 290 | 291 | func rpushCommand(c *redisClient) { 292 | /** 293 | pass in the REDIS_TAIL flag to indicate that 294 | the current element should be appended to the tail of the list. 295 | */ 296 | pushGenericCommand(c, REDIS_TAIL) 297 | } 298 | 299 | func pushGenericCommand(c *redisClient, where int) { 300 | //check if the corresponding key exists. 301 | o := lookupKeyWrite(c.db, c.argv[1]) 302 | var lobj *robj 303 | //if the key exists, then determine if it is a list. 304 | //if it is not, then throw an error exception. 305 | if o != nil && o.encoding != REDIS_ENCODING_LINKEDLIST { 306 | addReply(c, shared.wrongtypeerr) 307 | return 308 | } else if o != nil { //if it exists and is a list, then retrieve the Redis object for the list. 309 | lobj = o 310 | } 311 | //foreach element starting from index 2. 312 | var j uint64 313 | for j = 2; j < c.argc; j++ { 314 | //call `tryObjectEncoding` to perform special processing on the elements. 315 | c.argv[j] = tryObjectEncoding(c.argv[j]) 316 | 317 | /** 318 | If the list is empty, then initialize it, create it, and store it in the Redis database. 319 | */ 320 | if lobj == nil { 321 | lobj = createListObject() 322 | dbAdd(c.db, c.argv[1], lobj) 323 | } 324 | /** 325 | pass in the list pointer, element pointer, 326 | and add flag to append the element to the head or tail of the list. 327 | */ 328 | listTypePush(lobj, c.argv[j], where) 329 | } 330 | //return the current length of the list. 331 | addReplyLongLong(c, (*lobj.ptr).(*list).len) 332 | } 333 | 334 | func lrangeCommand(c *redisClient) { 335 | var o *robj 336 | var start int64 337 | var end int64 338 | var llen int64 339 | var rangelen int64 340 | /** 341 | convert the strings at indexes 2 and 3 to numerical values. 342 | If an error occurs, respond with an exception and return. 343 | */ 344 | if !getLongFromObjectOrReply(c, c.argv[2], &start, nil) || 345 | !getLongFromObjectOrReply(c, c.argv[3], &end, nil) { 346 | return 347 | } 348 | /** 349 | check if the linked list exists, 350 | and if it doesn't, respond with a null value. 351 | */ 352 | o = lookupKeyReadOrReply(c, c.argv[1], shared.emptymultibulk) 353 | 354 | /** 355 | check if the type is a linked list; if it is not, return a type error. 356 | */ 357 | if o == nil || checkType(c, o, REDIS_LIST) { 358 | return 359 | } 360 | //get the start and end values of a range query. If they are negative, add the length of the linked list. 361 | llen = (*o.ptr).(*list).len 362 | if start < 0 { 363 | start += llen 364 | } 365 | 366 | if start < 0 { 367 | start += llen 368 | } 369 | 370 | if end < 0 { 371 | end += llen 372 | } 373 | //if start is still less than 0, set it to 0. 374 | if start < 0 { 375 | start = 0 376 | } 377 | /** 378 | check if start is greater than the length of the linked list or if start is greater than end. 379 | If either of these exceptions occurs, respond with an error. 380 | */ 381 | if start >= llen || start > end { 382 | addReplyError(c, shared.emptymultibulk) 383 | return 384 | } 385 | //if end is greater than list length, set it to the list length. 386 | if end > llen { 387 | end = llen - 1 388 | } 389 | 390 | rangelen = end - start + 1 391 | addReplyMultiBulkLen(c, rangelen) 392 | 393 | if o.encoding == REDIS_ENCODING_ZIPLIST { 394 | //todo 395 | } else if o.encoding == REDIS_ENCODING_LINKEDLIST { 396 | lobj := (*o.ptr).(*list) 397 | node := listIndex(lobj, start) 398 | //foreach the linked list starting from "start" based on "rangelen." 399 | for rangelen > 0 { 400 | rObj := (*node.value).(*robj) 401 | addReplyBulk(c, rObj) 402 | node = node.next 403 | rangelen-- 404 | } 405 | 406 | } else { 407 | log.Panic("List encoding is not LINKEDLIST nor ZIPLIST!") 408 | } 409 | 410 | } 411 | 412 | func lindexCommand(c *redisClient) { 413 | /** 414 | check if the linked list exists; if it doesn't, return empty. 415 | */ 416 | o := lookupKeyReadOrReply(c, c.argv[1], shared.nullbulk) 417 | 418 | //verify if the type is a linked list. 419 | if o == nil || checkType(c, o, REDIS_LIST) { 420 | return 421 | } 422 | 423 | if o.encoding == REDIS_ENCODING_ZIPLIST { 424 | //todo 425 | } else if o.encoding == REDIS_ENCODING_LINKEDLIST { 426 | /** 427 | retrieve the parameter at index 2 to obtain the index position, 428 | then fetch the element from the linked list at that index and return it. 429 | */ 430 | lobj := (*o.ptr).(*list) 431 | s := (*c.argv[2].ptr).(string) 432 | idx, _ := strconv.ParseInt(s, 10, 64) 433 | ln := listIndex(lobj, idx) 434 | 435 | if ln != nil { 436 | value := (*ln.value).(*robj) 437 | addReplyBulk(c, value) 438 | } else { 439 | addReply(c, shared.nullbulk) 440 | } 441 | } else { 442 | log.Panic("Unknown list encoding") 443 | } 444 | 445 | } 446 | 447 | func lpopCommand(c *redisClient) { 448 | // params is REDIS_HEAD, which means to retrieve the head element. 449 | popGenericCommand(c, REDIS_HEAD) 450 | } 451 | 452 | func popGenericCommand(c *redisClient, where int) { 453 | //check if the key exists, and if it doesn't, respond with an empty response. 454 | o := lookupKeyReadOrReply(c, c.argv[1], shared.nullbulk) 455 | 456 | //If the type is not a linked list, throw an exception and return. 457 | if o == nil || checkType(c, o, REDIS_LIST) { 458 | return 459 | } 460 | 461 | value := listTypePop(o, where) 462 | //retrieve the first element of the linked list based on the WHERE identifier. 463 | if value == nil { 464 | addReply(c, shared.nullbulk) 465 | } else { 466 | /** 467 | return the element value, and check if the linked list is empty. 468 | If it is empty, delete the key-value pair in the Redis database. 469 | */ 470 | addReplyBulk(c, value) 471 | if listTypeLength(o) == 0 { 472 | dbDelete(c.db, c.argv[1]) 473 | } 474 | } 475 | } 476 | 477 | func hsetCommand(c *redisClient) { 478 | /** 479 | check if the dict object exists, and if it does not exist, create hash obj 480 | if it exists, then determine whether it is a hash obj. If not, return a type error. 481 | */ 482 | o := hashTypeLookupWriteOrCreate(c, c.argv[1]) 483 | 484 | if o == nil { 485 | return 486 | } 487 | //try to convert strings that can be converted to numerical types into numerical types. 488 | hashTypeTryObjectEncoding(o, &c.argv[2], &c.argv[3]) 489 | /** 490 | pass the information to the database to find the dictionary object along with the fields and values, 491 | return the dict update count 492 | */ 493 | update := hashTypeSet(o, c.argv[2], c.argv[3]) 494 | //if it is an update operation, return 0; if it is the first insertion of a field, return 1. 495 | if update == 1 { 496 | addReply(c, shared.czero) 497 | } else { 498 | addReply(c, shared.cone) 499 | } 500 | } 501 | 502 | func hmsetCommand(c *redisClient) { 503 | /** 504 | determine if the command params is singular 505 | if it is, respond with wrong number 506 | */ 507 | if c.argc%2 == 1 { 508 | errMsg := "wrong number of arguments for HMSET" 509 | addReplyError(c, &errMsg) 510 | return 511 | } 512 | /** 513 | starting from index 2,foreach key-value pair, 514 | perform encoding conversion, and save to dict obj 515 | */ 516 | var i uint64 517 | o := hashTypeLookupWriteOrCreate(c, c.argv[1]) 518 | for i = 2; i < c.argc; i += 2 { 519 | hashTypeTryObjectEncoding(o, &c.argv[i], &c.argv[i+1]) 520 | hashTypeSet(o, c.argv[i], c.argv[i+1]) 521 | } 522 | 523 | addReply(c, shared.ok) 524 | } 525 | 526 | func hsetnxCommand(c *redisClient) { 527 | //perform dict lookup, type validation, and creation if it does not exist. 528 | o := hashTypeLookupWriteOrCreate(c, c.argv[1]) 529 | //if it does not exist, return 0 and do not perform any operation. 530 | if hashTypeExists(o, c.argv[2]) { 531 | addReply(c, shared.czero) 532 | return 533 | } 534 | /** 535 | 1. perform the field type and value type conversion. 536 | 2. save field(argv[2])、value(argv[3]) to the dict obj 537 | 3. respond to the client with the result 1 538 | */ 539 | hashTypeTryObjectEncoding(o, &c.argv[2], &c.argv[3]) 540 | hashTypeSet(o, c.argv[2], c.argv[3]) 541 | addReply(c, shared.cone) 542 | } 543 | 544 | func hgetCommand(c *redisClient) { 545 | //check if the dictionary exists, and if it does not exist, return null. 546 | o := lookupKeyReadOrReply(c, c.argv[1], shared.nullbulk) 547 | //if it is not a hash object, return a type error 548 | if o == nil || checkType(c, o, REDIS_HASH) { 549 | return 550 | } 551 | //if the corresponding field in the dictionary exists, return this value 552 | addHashFieldToReply(c, o, c.argv[2]) 553 | 554 | } 555 | 556 | func addHashFieldToReply(c *redisClient, o *robj, field *robj) { 557 | //If the dictionary is empty, return nullbulk 558 | if o == nil { 559 | addReply(c, shared.nullbulk) 560 | return 561 | } 562 | 563 | if o.encoding == REDIS_ENCODING_ZIPLIST { 564 | //todo something 565 | } else if o.encoding == REDIS_ENCODING_HT { 566 | value := new(robj) 567 | /** 568 | pass the secondary pointer of the value to record the value corresponding to the field in the dictionary. 569 | if it is not null, return value; otherwise, return nullbulk. 570 | */ 571 | if hashTypeGetFromHashTable(o, field, &value) { 572 | addReplyBulk(c, value) 573 | } else { 574 | addReply(c, shared.nullbulk) 575 | } 576 | } 577 | 578 | } 579 | 580 | func hashTypeGetFromHashTable(o *robj, field *robj, value **robj) bool { 581 | dict := (*o.ptr).(map[string]*robj) 582 | key := (*field.ptr).(string) 583 | if v, e := dict[key]; e { 584 | *value = v 585 | return true 586 | 587 | } 588 | 589 | return false 590 | } 591 | 592 | func hmgetCommand(c *redisClient) { 593 | o := lookupKeyReadOrReply(c, c.argv[1], shared.nullbulk) 594 | if o == nil || checkType(c, o, REDIS_HASH) { 595 | return 596 | } 597 | 598 | addReplyMultiBulkLen(c, int64(c.argc-2)) 599 | 600 | var i uint64 601 | //starting from the first field, read the fields from the dictionary and return them. 602 | for i = 2; i < c.argc; i++ { 603 | addHashFieldToReply(c, o, c.argv[i]) 604 | } 605 | } 606 | 607 | func hgetallCommand(c *redisClient) { 608 | /** 609 | Use the XOR operation between the key and value to indicate that 610 | the current function needs to retrieve all keys and values from the dict. 611 | */ 612 | genericHgetallCommand(c, REDIS_HASH_KEY|REDIS_HASH_VALUE) 613 | 614 | } 615 | 616 | func genericHgetallCommand(c *redisClient, flags int) { 617 | multiplier := 0 618 | 619 | o := lookupKeyReadOrReply(c, c.argv[1], shared.emptymultibulk) 620 | if o == nil || checkType(c, o, REDIS_HASH) { 621 | return 622 | } 623 | /** 624 | if the result of the bitwise AND operation between the key and REDIS_HASH_KEY is greater than 0, 625 | increment the multiplier. 626 | */ 627 | if flags&REDIS_HASH_KEY > 0 { 628 | multiplier++ 629 | } 630 | /** 631 | if the result of the bitwise AND operation between the key and REDIS_HASH_VALUE is greater than 0, 632 | increment the multiplier. 633 | */ 634 | if flags&REDIS_HASH_VALUE > 0 { 635 | multiplier++ 636 | } 637 | /** 638 | response the client to return the value of the dictionary size multiplied by multiplier. 639 | 640 | */ 641 | dict := (*o.ptr).(map[string]*robj) 642 | l := len(dict) 643 | addReplyMultiBulkLen(c, int64(l*multiplier)) 644 | //return the key-value pairs as required. 645 | for key, value := range dict { 646 | if flags&REDIS_HASH_KEY > 0 { 647 | i := interface{}(key) 648 | object := createObject(REDIS_STRING, &i) 649 | addReplyBulk(c, object) 650 | } 651 | 652 | if flags&REDIS_HASH_VALUE > 0 { 653 | addReplyBulk(c, value) 654 | } 655 | } 656 | 657 | } 658 | 659 | func hdelCommand(c *redisClient) { 660 | var deleted int64 661 | o := lookupKeyWriteOrReply(c, c.argv[1], shared.czero) 662 | if o == nil || checkType(c, o, REDIS_HASH) { 663 | return 664 | } 665 | //starting from index 2, locate all the keys. 666 | var i uint64 667 | for i = 2; i < c.argc; i++ { 668 | if hashTypeDelete(o, c.argv[i]) { //If the deletion is successful, increment the deleted counter. 669 | deleted++ 670 | } 671 | } 672 | //If the dictionary has no key-value pairs after deletion, delete it directly. 673 | dict := (*o.ptr).(map[string]*robj) 674 | if len(dict) == 0 { 675 | dbDelete(c.db, c.argv[1]) 676 | } 677 | 678 | addReplyLongLong(c, deleted) 679 | 680 | } 681 | 682 | func scanCommand(c *redisClient) { 683 | var cursor uint64 684 | if !parseScanCursorOrReply(c, c.argv[1], &cursor) { 685 | return 686 | } 687 | scanGenericCommand(c, nil, &cursor) 688 | 689 | } 690 | 691 | func parseScanCursorOrReply(c *redisClient, o *robj, cursor *uint64) bool { 692 | 693 | u, err := strconv.ParseUint((*o.ptr).(string), 10, 64) 694 | if err != nil { 695 | errReply := "invalid cursor" 696 | addReplyError(c, &errReply) 697 | return false 698 | } 699 | *cursor = u 700 | return true 701 | } 702 | 703 | func scanGenericCommand(c *redisClient, o *robj, cursor *uint64) { 704 | // keys := listCreate() 705 | // var count int64 706 | // count = 10 707 | // var ht map[string]*robj 708 | // 709 | // var i uint64 710 | // //判断是scan还是hscan等标识 711 | // if o == nil { 712 | // i = 2 713 | // } else { 714 | // i = 3 715 | // } 716 | // //参数解析 717 | // for ; i < c.argc; i += 2 { 718 | // j := c.argc - i 719 | // if "count" == (*c.argv[i].ptr).(string) && j >= 2 { 720 | // //解析count的值,若不合法直接执行后置清理 721 | // if !getLongFromObjectOrReply(c, c.argv[i+1], &count, nil) { 722 | // goto cleanup 723 | // } 724 | // 725 | // if count < 1 { 726 | // addReply(c, shared.syntaxerr) 727 | // goto cleanup 728 | // } 729 | // 730 | // } 731 | // } 732 | // //迭代key值 733 | // ht = c.db.dict 734 | // if ht != nil { 735 | // j := int64(0) 736 | // skip := int64(0) 737 | // 738 | // for k := range ht { 739 | // if skip < int64(*cursor) { 740 | // skip++ 741 | // continue 742 | // } 743 | // key := interface{}(k) 744 | // listAddNodeTail(keys, &key) 745 | // j++ 746 | // if j == count { 747 | // *cursor = uint64(skip + count) 748 | // break 749 | // } 750 | // } 751 | // } 752 | // 753 | // addReplyMultiBulkLen(c, 2) 754 | // if listLength(keys) != 0 { 755 | // addReplyLongLong(c, int64(*cursor)) 756 | // } else { 757 | // addReplyLongLong(c, 0) 758 | // } 759 | // 760 | // addReplyMultiBulkLen(c, listLength(keys)) 761 | // for true { 762 | // node := listFirst(keys) 763 | // if node == nil { 764 | // break 765 | // } 766 | // s := (*node.value).(string) 767 | // o := createEmbeddedStringObject(&s, len(s)) 768 | // addReplyBulk(c, o) 769 | // listDelNode(keys, node) 770 | // } 771 | // 772 | //cleanup: 773 | // //清理资源 774 | // listRelease(&keys) 775 | } 776 | --------------------------------------------------------------------------------