runningwater 3 місяців тому
батько
коміт
eb26b31a51

+ 29 - 15
config/config.go

@@ -11,25 +11,34 @@ import (
 	"github.com/runningwater/go-redis/lib/logger"
 )
 
-// ServerProperties defines global config properties
+// ServerProperties 定义了服务器的全局配置属性
 type ServerProperties struct {
-	Bind           string `cfg:"bind"`
-	Port           int    `cfg:"port"`
-	AppendOnly     bool   `cfg:"appendOnly"`
+	// Bind 服务器绑定的IP地址
+	Bind string `cfg:"bind"`
+	// Port 服务器监听的端口号
+	Port int `cfg:"port"`
+	// AppendOnly 是否启用AOF持久化模式
+	AppendOnly bool `cfg:"appendOnly"`
+	// AppendFilename AOF文件名
 	AppendFilename string `cfg:"appendFilename"`
-	MaxClients     int    `cfg:"maxclients"`
-	RequirePass    string `cfg:"requirepass"`
-	Databases      int    `cfg:"databases"`
+	// MaxClients 最大客户端连接数
+	MaxClients int `cfg:"maxclients"`
+	// RequirePass 访问服务器所需的密码
+	RequirePass string `cfg:"requirepass"`
+	// Databases 数据库数量
+	Databases int `cfg:"databases"`
 
+	// Peers 集群中其他节点地址列表
 	Peers []string `cfg:"peers"`
-	Self  string   `cfg:"self"`
+	// Self 当前节点在集群中的地址
+	Self string `cfg:"self"`
 }
 
-// Properties holds global config properties
+// Properties 持有全局配置属性的实例
 var Properties *ServerProperties
 
 func init() {
-	// default config
+	// 默认配置
 	Properties = &ServerProperties{
 		Bind:       "127.0.0.1",
 		Port:       6379,
@@ -37,19 +46,22 @@ func init() {
 	}
 }
 
+// parse 解析配置文件,将配置项映射到ServerProperties结构体中
 func parse(src io.Reader) *ServerProperties {
 	config := &ServerProperties{}
 
-	// read config file
+	// 读取配置文件
 	rawMap := make(map[string]string)
 	scanner := bufio.NewScanner(src)
 	for scanner.Scan() {
 		line := scanner.Text()
+		// 跳过注释行(以#开头的行)
 		if len(line) > 0 && line[0] == '#' {
 			continue
 		}
+		// 查找第一个空格位置,分离键和值
 		pivot := strings.IndexAny(line, " ")
-		if pivot > 0 && pivot < len(line)-1 { // separator found
+		if pivot > 0 && pivot < len(line)-1 { // 找到分隔符
 			key := line[0:pivot]
 			value := strings.Trim(line[pivot+1:], " ")
 			rawMap[strings.ToLower(key)] = value
@@ -59,20 +71,21 @@ func parse(src io.Reader) *ServerProperties {
 		logger.Fatal(err)
 	}
 
-	// parse format
+	// 使用反射解析配置格式
 	t := reflect.TypeOf(config)
 	v := reflect.ValueOf(config)
 	n := t.Elem().NumField()
 	for i := 0; i < n; i++ {
 		field := t.Elem().Field(i)
 		fieldVal := v.Elem().Field(i)
+		// 获取cfg标签作为配置键名,如果没有则使用字段名
 		key, ok := field.Tag.Lookup("cfg")
 		if !ok {
 			key = field.Name
 		}
 		value, ok := rawMap[strings.ToLower(key)]
 		if ok {
-			// fill config
+			// 根据字段类型填充配置值
 			switch field.Type.Kind() {
 			case reflect.String:
 				fieldVal.SetString(value)
@@ -90,6 +103,7 @@ func parse(src io.Reader) *ServerProperties {
 					fieldVal.Set(reflect.ValueOf(slice))
 				}
 			default:
+				// 未处理的类型
 				panic("unhandled default case")
 			}
 		}
@@ -97,7 +111,7 @@ func parse(src io.Reader) *ServerProperties {
 	return config
 }
 
-// SetupConfig read config file and store properties into Properties
+// SetupConfig 读取配置文件并将属性存储到Properties变量中
 func SetupConfig(configFilename string) {
 	file, err := os.Open(configFilename)
 	if err != nil {

+ 11 - 2
database/command.go

@@ -1,11 +1,13 @@
 // Author: simon (ynwdlxm@163.com)
 // Date: 2025/9/15 18:08
-// Desc:
+// Desc: 命令列表
 
 package database
 
 import (
 	"strings"
+
+	"github.com/runningwater/go-redis/lib/logger"
 )
 
 // 命令表
@@ -13,11 +15,18 @@ var cmdTable = make(map[string]*command)
 
 type command struct {
 	executor ExecFunc // 执行函数
-	arity    int      // 参数个数
+	arity    int      // 参数个数, >0表示确切数量, <0表示最少数量, =0表示无参数也不接受参数
 }
 
+// RegisterCommand 将命令注册到命令表中
+// 参数:
+//
+//	name: 命令名称
+//	executor: 命令执行函数
+//	arity: 命令参数个数,=0表示无参数,>0表示确切的参数个数,<0表示至少有N个参数(N=-arity
 func RegisterCommand(name string, executor ExecFunc, arity int) {
 	name = strings.ToLower(name)
+	logger.Debug("Register command:", strings.ToUpper(name))
 	cmdTable[name] = &command{
 		executor: executor,
 		arity:    arity,

+ 82 - 13
database/db.go

@@ -1,6 +1,6 @@
 // Author: simon (ynwdlxm@163.com)
 // Date: 2025/9/15 18:04
-// Desc:
+// Desc: Redis 数据库核心实现
 
 package database
 
@@ -13,19 +13,30 @@ import (
 	"github.com/runningwater/go-redis/resp/reply"
 )
 
-// DB 数据库
+// DB 表示一个 Redis 数据库实例
 type DB struct {
-	index int
-	data  dict.Dict
+	index int       // 数据库索引
+	data  dict.Dict // 存储键值对的数据结构
 }
 
+// ExecFunc 定义命令执行函数的类型
+// 参数:
+//   - db: 数据库实例
+//   - args: 命令参数列表(不包括命令名称)
+//
+// 返回值:
+//   - resp.Reply: 命令执行结果的回复
 type ExecFunc func(db *DB, args [][]byte) resp.Reply
+
+// CmdLine 定义命令行类型,是字节切片的切片
 type CmdLine = [][]byte
 
+// NewDB 创建一个新的数据库实例
+// 返回值:
+//   - *DB: 新创建的数据库实例
 func NewDB() *DB {
 	return &DB{
-		index: 0,
-		data:  dict.NewSyncDict(),
+		data: dict.NewSyncDict(),
 	}
 }
 
@@ -41,7 +52,7 @@ func (d *DB) Exec(c resp.Connection, cmdLine CmdLine) resp.Reply {
 	if len(cmdLine) == 0 {
 		return reply.NewErrReply("empty command")
 	}
-	// ping set setnx get
+	// 获取命令名称并转换为小写
 	cmdName := strings.ToLower(string(cmdLine[0]))
 	// 查找命令
 	cmd, ok := cmdTable[cmdName]
@@ -49,7 +60,7 @@ func (d *DB) Exec(c resp.Connection, cmdLine CmdLine) resp.Reply {
 		return reply.NewUnknownErrReply(cmdName)
 	}
 	// 参数校验
-	if !validateArity(cmd.arity, cmdLine[1:]) {
+	if !validateArity(cmd.arity, cmdLine) {
 		return reply.NewArgNumErrReply(cmdName)
 	}
 
@@ -59,6 +70,15 @@ func (d *DB) Exec(c resp.Connection, cmdLine CmdLine) resp.Reply {
 	return cmd.executor(d, cmdLine[1:])
 }
 
+// validateArity 验证命令参数数量是否正确
+// 参数:
+//   - arity: 命令期望的参数数量
+//   - 正数表示精确匹配该数量
+//   - 负数表示至少需要 -arity 个参数
+//   - args: 实际传入的参数列表
+//
+// 返回值:
+//   - bool: 参数数量是否符合要求
 func validateArity(arity int, args [][]byte) bool {
 	if arity >= 0 {
 		return arity == len(args)
@@ -67,6 +87,13 @@ func validateArity(arity int, args [][]byte) bool {
 	return len(args) >= -arity
 }
 
+// GetEntity 根据键获取数据实体
+// 参数:
+//   - key: 要获取的键
+//
+// 返回值:
+//   - *database.DataEntity: 键对应的数据实体
+//   - bool: 键是否存在
 func (d *DB) GetEntity(key string) (*database.DataEntity, bool) {
 	raw, ok := d.data.Get(key)
 	if !ok {
@@ -76,25 +103,67 @@ func (d *DB) GetEntity(key string) (*database.DataEntity, bool) {
 	return entity, true
 }
 
+// PutEntity 插入或更新键值对
+// 参数:
+//   - key: 键
+//   - entity: 数据实体
+//
+// 返回值:
+//   - int: 操作结果,1表示新增,0表示更新
 func (d *DB) PutEntity(key string, entity *database.DataEntity) int {
 	return d.data.Put(key, entity)
 }
 
-func (d *DB) Remove(key string) {
-	d.data.Remove(key)
+// PuIfExists 当键存在时才更新键值对
+// 参数:
+//   - key: 键
+//   - entity: 数据实体
+//
+// 返回值:
+//   - int: 操作结果,1表示更新成功,0表示键不存在未更新
+func (d *DB) PuIfExists(key string, entity *database.DataEntity) int {
+	return d.data.PutIfExists(key, entity)
+}
+
+// PutIfAbsent 当键不存在时才插入键值对
+// 参数:
+//   - key: 键
+//   - entity: 数据实体
+//
+// 返回值:
+//   - int: 操作结果,1表示插入成功,0表示键已存在未插入
+func (d *DB) PutIfAbsent(key string, entity *database.DataEntity) int {
+	return d.data.PutIfAbsent(key, entity)
 }
 
+// Remove 根据键删除键值对
+// 参数:
+//   - key: 要删除的键
+//
+// 返回值:
+//   - int: 操作结果,1表示删除成功,0表示键不存在
+func (d *DB) Remove(key string) int {
+	return d.data.Remove(key)
+}
+
+// Removes 批量删除多个键
+// 参数:
+//   - keys: 要删除的键列表
+//
+// 返回值:
+//   - deleted: 成功删除的键数量
 func (d *DB) Removes(keys ...string) (deleted int) {
 	deleted = 0
 	for _, key := range keys {
-		if _, exists := d.data.Get(key); exists {
-			d.Remove(key)
+		// 使用Remove方法的返回值判断是否删除成功,避免两次查找
+		if d.data.Remove(key) > 0 {
 			deleted++
 		}
 	}
-	return
+	return deleted
 }
 
+// Flush 清空数据库中的所有键值对
 func (d *DB) Flush() {
 	d.data.Clear()
 }

+ 178 - 0
database/generic.go

@@ -0,0 +1,178 @@
+// Author: simon (ynwdlxm@163.com)
+// Date: 2025/9/16 16:56
+// Desc:
+
+package database
+
+import (
+	"github.com/runningwater/go-redis/interface/resp"
+	"github.com/runningwater/go-redis/lib/wildcard"
+	"github.com/runningwater/go-redis/resp/reply"
+)
+
+// 语法:
+//
+//	DEL key [key ...]
+//
+// 返回:
+//
+//	Integer reply: the number of keys that were removed.
+func execDel(db *DB, args [][]byte) resp.Reply {
+	keys := make([]string, len(args))
+	for i, v := range args {
+		keys[i] = string(v)
+	}
+	return reply.NewIntReply(int64(db.Removes(keys...)))
+}
+
+// 语法:
+//
+//	EXISTS key [key ...]
+//
+// 返回:
+//
+//	Integer reply: the number of keys that exists.
+func execExists(db *DB, args [][]byte) resp.Reply {
+	result := int64(0)
+	for _, key := range args {
+		if _, exists := db.GetEntity(string(key)); exists {
+			result++
+		}
+	}
+	return reply.NewIntReply(result)
+}
+
+// 语法:
+//
+//	FLUSHDB [ASYNC | SYNC]
+//
+// 返回:
+//
+//	Simple string reply: OK.
+func execFlushDb(db *DB, args [][]byte) resp.Reply {
+	db.Flush()
+	return reply.NewOkReply()
+}
+
+// 语法:
+//
+//	 KEYS pattern
+//
+//		Supported glob-style patterns:
+//
+//		 h?llo matches hello, hallo and hxllo
+//		 h*llo matches hllo and heeeello
+//		 h[ae]llo matches hello and hallo, but not hillo
+//		 h[^e]llo matches hallo, hbllo, ... but not hello
+//		 h[a-b]llo matches hallo and hbllo
+//
+// Use \ to escape special characters if you want to match them verbatim.
+//
+// 返回:
+//
+//	Array reply: a list of keys matching pattern.
+func execKeys(db *DB, args [][]byte) resp.Reply {
+	pattern := string(args[0])
+	compilePattern := wildcard.CompilePattern(pattern)
+
+	result := make([][]byte, 0)
+	db.data.ForEach(func(key string, value any) bool {
+		if compilePattern.IsMatch(key) {
+			result = append(result, []byte(key))
+		}
+		return true
+	})
+	return reply.NewMultiBulkReply(result)
+}
+
+// 语法:
+//
+//	TYPE key
+//
+// 返回:
+//
+//	Returns the string representation of the type of the value stored at key.
+//	The different types that can be returned are: string, list, set, zset, hash, stream, and vectorset.
+func execType(db *DB, args [][]byte) resp.Reply {
+	key := string(args[0])
+	entity, exists := db.GetEntity(key)
+	if !exists {
+		return reply.NewStatusReply("none")
+	}
+
+	switch entity.Data.(type) {
+	case []byte:
+		return reply.NewStatusReply("string")
+		// : TODO 类型判断: 只实现了 string  类型, 其他类型未实现
+	default:
+		return reply.NewUnknownErrReply("don't know the type")
+	}
+}
+
+// 语法:
+//
+//	RENAME key newkey
+//
+// 返回:
+//
+//	returns an error when key does not exist.
+//	If newkey already exists it is overwritten,
+//	when this happens RENAME executes an implicit DEL operation,
+//	so if the deleted key contains a very big value it may cause
+//	high latency even if RENAME itself is usually a constant-time operation.
+func execRename(db *DB, args [][]byte) resp.Reply {
+	src := string(args[0])
+	dest := string(args[1])
+	entity, exists := db.GetEntity(src)
+	if !exists {
+		return reply.NewErrReply("no such key")
+	}
+	db.PutEntity(dest, entity)
+	db.Remove(src)
+	return reply.NewOkReply()
+}
+
+// 语法:
+//
+//	RENAMENX key newkey
+//
+// 返回:
+//
+//	Integer reply: 1 if key was renamed to newkey.
+//	Integer reply: 0 if newkey already exists.
+func execRenamenx(db *DB, args [][]byte) resp.Reply {
+	src := string(args[0])
+	dest := string(args[1])
+
+	_, ok := db.GetEntity(dest)
+	if ok {
+		return reply.NewIntReply(0)
+	}
+
+	entity, exists := db.GetEntity(src)
+	if !exists {
+		return reply.NewErrReply("no such key")
+	}
+	db.PutEntity(dest, entity)
+	db.Remove(src)
+	return reply.NewIntReply(1)
+}
+
+// init 将键相关命令注册到命令表中
+// 此函数在包初始化期间自动调用
+func init() {
+	// 注册 DEL 命令,至少需要 2 个参数(命令名称 + 至少 1 个键)
+	RegisterCommand("DEL", execDel, -2)
+	// 注册 EXISTS 命令,至少需要 2 个参数(命令名称 + 至少 1 个键)
+	RegisterCommand("EXISTS", execExists, -2)
+	// 注册 FLUSHDB 命令,至少需要 1 个参数(命令名称)
+	RegisterCommand("FLUSHDB", execFlushDb, -1)
+	// 注册 RENAME 命令,需要 2 个参数(命令名称 + 源键 + 目标键)
+	RegisterCommand("KEYS", execKeys, 2)
+	// 注册 TYPE 命令,需要 2 个参数(命令名称 + 键)
+	RegisterCommand("TYPE", execType, 2)
+	// 注册 TYPE 命令,需要 3 个参数(命令名称 + 键)
+	RegisterCommand("RENAME", execRename, 3)
+	// 注册 RENAMENX 命令,需要 3 个参数(命令名称 + 源键 + 目标键)
+	RegisterCommand("RENAMENX", execRenamenx, 3)
+}

+ 21 - 0
database/ping.go

@@ -0,0 +1,21 @@
+// Author: simon (ynwdlxm@163.com)
+// Date: 2025/9/16 16:48
+// Desc:
+
+package database
+
+import (
+	"github.com/runningwater/go-redis/interface/resp"
+	"github.com/runningwater/go-redis/resp/reply"
+)
+
+func ping(db *DB, args [][]byte) resp.Reply {
+	return reply.NewPongReply()
+}
+
+// init 将键相关命令注册到命令表中
+// 此函数在包初始化期间自动调用
+func init() {
+	// 注册 PING 命令,至少需要 1 个参数(命令名称)
+	RegisterCommand("ping", ping, 1)
+}

+ 78 - 0
database/resp_database.go

@@ -0,0 +1,78 @@
+// Author: simon (ynwdlxm@163.com)
+// Date: 2025/9/25 11:27
+// Desc: 处理 RESP-SPEC 命令
+
+package database
+
+import (
+	"strconv"
+	"strings"
+
+	"github.com/runningwater/go-redis/config"
+	"github.com/runningwater/go-redis/interface/resp"
+	"github.com/runningwater/go-redis/lib/logger"
+	"github.com/runningwater/go-redis/resp/reply"
+)
+
+type Database struct {
+	dbSet []*DB
+}
+
+func NewDatabase() *Database {
+	if config.Properties.Databases == 0 {
+		config.Properties.Databases = 16
+	}
+	dbs := &Database{}
+	dbs.dbSet = make([]*DB, config.Properties.Databases)
+	for i := range dbs.dbSet {
+		db := NewDB()
+		db.index = i
+		dbs.dbSet[i] = db
+	}
+
+	return dbs
+}
+
+func (d *Database) Exec(client resp.Connection, args [][]byte) resp.Reply {
+	defer func() {
+		if err := recover(); err != nil {
+			logger.Error(err)
+		}
+	}()
+
+	cmdName := strings.ToLower(string(args[0]))
+	if cmdName == "select" {
+		if len(args) != 2 {
+			return reply.NewErrReply("ERR wrong number of arguments for 'select' command")
+		}
+		return execSelect(client, d, args[1:])
+	} else if cmdName == "command" {
+		return reply.NewOkReply()
+	}
+
+	index := client.GetDBIndex()
+	return d.dbSet[index].Exec(client, args)
+}
+
+func (d *Database) Close() {
+	// TODO implement me
+
+}
+
+func (d *Database) AfterClientClose(client resp.Connection) {
+	// TODO implement me
+
+}
+
+// select db 命令
+func execSelect(c resp.Connection, database *Database, args [][]byte) resp.Reply {
+	dbIndex, err := strconv.Atoi(string(args[0]))
+	if err != nil {
+		return reply.NewErrReply("ERR invalid DB index")
+	}
+	if dbIndex >= len(database.dbSet) {
+		return reply.NewErrReply("ERR DB index is out of range")
+	}
+	c.SelectDB(dbIndex)
+	return reply.NewOkReply()
+}

+ 123 - 0
database/string.go

@@ -0,0 +1,123 @@
+// Author: simon (ynwdlxm@163.com)
+// Date: 2025/9/18 15:47
+// Desc: GET SET SETNX GETSET STRLEN
+
+package database
+
+import (
+	"github.com/runningwater/go-redis/interface/database"
+	"github.com/runningwater/go-redis/interface/resp"
+	"github.com/runningwater/go-redis/resp/reply"
+)
+
+// 语法:
+//
+//	GET key
+//
+// 返回:
+//  1. key不存在时,返回nil
+//  2. key存在时,返回value
+func execGet(db *DB, args [][]byte) resp.Reply {
+	key := string(args[0])
+	entity, exists := db.GetEntity(key)
+	if !exists {
+		return reply.NewNullBulkReply()
+	}
+	// 获取 value, 转换为 []byte
+	bytes := entity.Data.([]byte)
+	return reply.NewBulkReply(bytes)
+}
+
+// 语法:
+//
+//	SET key value [NX] [XX] [EX seconds] [PX milliseconds] [KEEPTTL]
+//
+// 命令返回:
+//  1. 设置成功时,返回 OK
+//  2. 设置失败时,返回 NIL
+//
+// 命令参数:
+//  1. NX:如果 key 已经存在,则返回 NIL
+//  2. XX:如果 key 不存在,则返回 NIL
+//  3. EX seconds:设置 key 的过期时间,单位为秒
+//  4. PX milliseconds:设置 key 的过期时间,单位为毫秒
+func execSet(db *DB, args [][]byte) resp.Reply {
+	// : TODO 暂只实现了 set key value
+	key := string(args[0])
+	value := args[1]
+	entity := &database.DataEntity{
+		Data: value,
+	}
+	_ = db.PutEntity(key, entity)
+	return reply.NewOkReply()
+}
+
+// 语法:
+//
+//	SETNX key value
+//
+// 命令返回:
+//  1. 设置成功时,返回 1
+//  2. 设置失败时,返回 0
+func execSetNX(db *DB, args [][]byte) resp.Reply {
+	key := string(args[0])
+	value := args[1]
+	entity := &database.DataEntity{
+		Data: value,
+	}
+	absent := db.PutIfAbsent(key, entity)
+	return reply.NewIntReply(int64(absent))
+}
+
+// 语法:
+//
+//	GETSET key value
+//
+// 命令返回:
+//  1. 获取成功时,返回旧值
+//  2. 获取失败时,返回 NIL
+func execGetSet(db *DB, args [][]byte) resp.Reply {
+	key := string(args[0])
+	value := args[1]
+	entity, exists := db.GetEntity(key)
+	_ = db.PutEntity(key, &database.DataEntity{Data: value})
+	if !exists {
+		return reply.NewNullBulkReply()
+	}
+	// 获取旧值
+	bytes := entity.Data.([]byte)
+	return reply.NewBulkReply(bytes)
+}
+
+// 语法:
+//
+//	STRLEN key
+//
+// 命令返回:
+//  1. 获取成功时,返回字符串的长度
+//  2. 获取失败时,返回 0
+func execStrLen(db *DB, args [][]byte) resp.Reply {
+	key := string(args[0])
+	entity, exists := db.GetEntity(key)
+	if !exists {
+		return reply.NewIntReply(0)
+	}
+	// 获取字符串长度
+	bytes := entity.Data.([]byte)
+	return reply.NewIntReply(int64(len(bytes)))
+}
+
+// init 将键相关命令注册到命令表中
+// 此函数在包初始化期间自动调用
+func init() {
+	// 注册 GET 命令,需要 2 个参数(命令名称 + 键)
+	RegisterCommand("get", execGet, 2)
+	// 注册 SET 命令,需要 3 个参数(命令名称 + 键 + 值)
+	RegisterCommand("set", execSet, 3)
+	// 注册 SETNX 命令,需要 3 个参数(命令名称 + 键 + 值)
+	RegisterCommand("setnx", execSetNX, 3)
+	// 注册 GETSET 命令,需要 3 个参数(命令名称 + 键 + 值)
+	RegisterCommand("getset", execGetSet, 3)
+	// 注册 STRLEN 命令,需要 2 个参数(命令名称 + 键)
+	RegisterCommand("strlen", execStrLen, 2)
+}

+ 171 - 0
lib/wildcard/wildcard.go

@@ -0,0 +1,171 @@
+// Package wildcard 实现了通配符模式匹配功能
+package wildcard
+
+const (
+	normal     = iota // 普通字符
+	all               // * 匹配任意数量字符
+	anySymbol         // ? 匹配单个字符
+	setSymbol         // [] 字符集匹配
+	rangSymbol        // [a-b] 范围匹配
+	negSymbol         // [^a] 否定匹配
+)
+
+// item 表示模式中的一个元素
+type item struct {
+	character byte          // 字符值
+	set       map[byte]bool // 字符集合
+	typeCode  int           // 类型码
+}
+
+// contains 检查字符是否匹配当前元素
+func (i *item) contains(c byte) bool {
+	switch i.typeCode {
+	case setSymbol:
+		_, ok := i.set[c]
+		return ok
+	case rangSymbol:
+		if _, ok := i.set[c]; ok {
+			return true
+		}
+		var (
+			_min byte = 255
+			_max byte = 0
+		)
+		for k := range i.set {
+			if _min > k {
+				_min = k
+			}
+			if _max < k {
+				_max = k
+			}
+		}
+		return c >= _min && c <= _max
+	case negSymbol:
+		_, ok := i.set[c]
+		return !ok
+	default:
+		return false
+	}
+}
+
+// Pattern 表示一个通配符模式
+type Pattern struct {
+	items []*item
+}
+
+// CompilePattern 将通配符字符串转换为Pattern对象
+//
+//	支持的通配符:
+//	* - 匹配任意数量的字符(包括空字符)
+//	? - 匹配单个字符
+//	[] - 匹配括号内任意一个字符,如[abc]
+//	[a-b] - 匹配范围内的任意字符
+//	[^a] - 匹配除括号内字符外的任意字符
+//	\ - 转义字符
+func CompilePattern(src string) *Pattern {
+	items := make([]*item, 0, len(src)) // 预分配容量
+	escape := false
+	inSet := false
+	var set map[byte]bool
+
+	for _, v := range src {
+		c := byte(v)
+		if escape {
+			// 处理转义字符
+			items = append(items, &item{typeCode: normal, character: c})
+			escape = false
+		} else if c == '*' {
+			// 匹配任意数量字符
+			items = append(items, &item{typeCode: all})
+		} else if c == '?' {
+			// 匹配单个字符
+			items = append(items, &item{typeCode: anySymbol})
+		} else if c == '\\' {
+			// 转义下一个字符
+			escape = true
+		} else if c == '[' {
+			// 开始字符集定义
+			if !inSet {
+				inSet = true
+				set = make(map[byte]bool)
+			} else {
+				set[c] = true
+			}
+		} else if c == ']' {
+			// 结束字符集定义
+			if inSet {
+				inSet = false
+				typeCode := setSymbol
+				if _, ok := set['-']; ok {
+					typeCode = rangSymbol
+					delete(set, '-')
+				}
+				if _, ok := set['^']; ok {
+					typeCode = negSymbol
+					delete(set, '^')
+				}
+				items = append(items, &item{typeCode: typeCode, set: set})
+			} else {
+				items = append(items, &item{typeCode: normal, character: c})
+			}
+		} else {
+			if inSet {
+				set[c] = true
+			} else {
+				items = append(items, &item{typeCode: normal, character: c})
+			}
+		}
+	}
+	return &Pattern{
+		items: items,
+	}
+}
+
+// IsMatch 检查给定字符串是否匹配该模式
+// 使用动态规划算法实现
+func (p *Pattern) IsMatch(s string) bool {
+	if len(p.items) == 0 {
+		return len(s) == 0
+	}
+
+	m := len(s)
+	n := len(p.items)
+
+	// 使用二维布尔数组进行动态规划
+	table := make([][]bool, m+1)
+	for i := range table {
+		table[i] = make([]bool, n+1)
+	}
+
+	// 初始化基本情况
+	table[0][0] = true
+
+	// 处理空字符串与模式的匹配情况
+	for j := 1; j <= n; j++ {
+		if p.items[j-1].typeCode == all {
+			table[0][j] = table[0][j-1]
+		}
+	}
+
+	// 填充动态规划表
+	for i := 1; i <= m; i++ {
+		for j := 1; j <= n; j++ {
+			switch p.items[j-1].typeCode {
+			case all:
+				// * 可以匹配空字符串或多个字符
+				table[i][j] = table[i-1][j] || table[i][j-1]
+			case anySymbol:
+				// ? 匹配任意单个字符
+				table[i][j] = table[i-1][j-1]
+			case normal:
+				// 普通字符精确匹配
+				table[i][j] = table[i-1][j-1] && s[i-1] == p.items[j-1].character
+			default:
+				// 集合类匹配 [abc], [a-z], [^a] 等
+				table[i][j] = table[i-1][j-1] && p.items[j-1].contains(s[i-1])
+			}
+		}
+	}
+
+	return table[m][n]
+}

+ 1 - 0
redis.conf

@@ -1,6 +1,7 @@
 bind 0.0.0.0
 protected-mode no
 port 6379
+databases 16
 daemonize yes
 pidfile /var/run/redis_6379.pid
 logfile "/var/log/redis_6379.log"

+ 2 - 1
resp/handler/handler.go

@@ -31,7 +31,8 @@ type RespHandler struct {
 // NewHandler creates a new RespHandler
 func NewHandler() *RespHandler {
 	return &RespHandler{
-		db: database.NewEchoDatabase(),
+		// db: database.NewEchoDatabase(),
+		db: database.NewDatabase(),
 	}
 }
 

+ 1 - 1
resp/parser/parser.go

@@ -174,7 +174,7 @@ func readLine(reader *bufio.Reader, state *currentState) ([]byte, bool, error) {
 	if state.bulkLen == 0 { // \r\n split
 		// Read a line of data
 		msg, err = reader.ReadBytes('\n')
-		logger.Info("\r\n***readLine: ", string(msg))
+		logger.Debug("\r\n***readLine: ", string(msg))
 		if err != nil {
 			return nil, true, err
 		}