go手写Redis(8)之数据库核心层及指令实现

文章详细描述了数据库核心层的实现,包括顶级接口Database、Dict接口及其实现SyncDict,以及DB和Database的封装。Dict接口定义了字典数据结构的操作,如Get、Put、Remove等。SyncDict使用sync.Map保证数据存储的线程安全。DB作为数据库存储层,对Dict进行包装,并提供Exec函数执行用户指令。Database进一步封装DB,处理数据库选择和消息持久化。最后提到了命令的注册和执行机制,以及keys和string相关指令的实现。
摘要由CSDN通过智能技术生成

数据库核心层

前面实现完了处理器的逻辑,现在到了核心的数据层实现了,核心的数据库主要是来执行用户发送的指令并且进行数据存储

1. Database

数据层的顶级接口定义,在 interface/database/database.go 文件中定义,
其中定义了 Database 接口以及 DataEntity 结构体用于包装数据,其中 interface 类型表示可以存放任何类型的数据

//Database 数据库的业务层
type Database interface {
	//Exec 核心层执行指令,返回一个响应体
	Exec(client resp.Connection, args [][]byte) resp.Reply
	//Close 关闭
	Close()
	//AfterClientClose 客户端关闭之后需要做的一些操作
	AfterClientClose(c resp.Connection)
}

//DataEntity 数据结构,用于包装任何的数据类型
type DataEntity struct {
	//数据可以指代任何的数据
	Data interface{}
}

2. Dict

redis中存储字典的数据结构,我们会实现这个接口来进行数据的存储,在 datastruct/dict/dict.go 中定义接口

/**
Dict redis中存储字典的数据结构
*/
type Dict interface {
	//Get 返回value值,exist代表是否存在
	Get(key string) (val interface{}, exist bool)
	//Len 字典存在的数据
	Len() int
	//Put 存入数据,返回存入的个数
	Put(key string, value interface{}) (result int)
	//PutIfAbsent 如果不存在则设置
	PutIfAbsent(key string, value interface{}) (result int)
	//PutIfExists 如果存在则设置,如果不存在就不设置
	PutIfExists(key string, value interface{}) (result int)
	//Remove 移除
	Remove(key string) (result int)
	//ForEach 遍历方法
	ForEach(consumer Consumer)
	//Keys 返回所有的key
	Keys() []string
	//RandomKeys 需要随机返回多少个key
	RandomKeys(limit int) []string
	//RandomDistinctKeys 返回指定个数不重复的key
	RandomDistinctKeys(limit int) []string
	//Clear 清空字典表
	Clear()
}

//Consumer 自定义的遍历函数
type Consumer func(key string, val interface{}) bool

3. 实现类

3.1 SyncDict

Dict 接口进行实现,正常存储数据的地方,我们采用的 sync.Map 来进行数据的存储线程安全的

//SyncDict 使用并发安全的字典,为什么需要包装一层,因为底层的数据接口可能会进行更换
type SyncDict struct {
	//m 并发安全的map
	m sync.Map
}

//Get get获取方法
func (dict *SyncDict) Get(key string) (val interface{}, exist bool) {
	return dict.m.Load(key)
}

//Len 默认使用 map 的range方法来进行遍历个数
func (dict *SyncDict) Len() int {
	length := 0
	dict.m.Range(func(key, value any) bool {
		length++
		return true
	})
	return length
}

//Put 存入数据
func (dict *SyncDict) Put(key string, value interface{}) (result int) {
	_, existed := dict.m.Load(key)
	dict.m.Store(key, value)
	if existed {
		return 0
	}
	return 1
}

//PutIfAbsent 如果不存在则存入数据
func (dict *SyncDict) PutIfAbsent(key string, value interface{}) (result int) {
	_, existed := dict.m.Load(key)
	if !existed {
		dict.m.Store(key, value)
		return 1
	}
	return 0
}

//PutIfExists 如果存在则插入数据
func (dict *SyncDict) PutIfExists(key string, value interface{}) (result int) {
	_, existed := dict.m.Load(key)
	if existed {
		dict.m.Store(key, value)
		return 1
	}
	return 0
}

//Remove 移除数据
func (dict *SyncDict) Remove(key string) (result int) {
	_, existed := dict.m.Load(key)
	dict.m.Delete(key)
	if existed {
		return 1
	}
	return 0
}

//ForEach 遍历数据
func (dict *SyncDict) ForEach(consumer Consumer) {
	dict.m.Range(func(key, value any) bool {
		consumer(key.(string), value)
		return true
	})
}

//Keys 获取所有的key
func (dict *SyncDict) Keys() []string {
	//创建一个切片,长度为当前字典的长度
	keys := make([]string, dict.Len())
	dict.m.Range(func(key, _ any) bool {
		keys = append(keys, key.(string))
		return true
	})
	return keys
}

//RandomKeys 随机返回指定的key值
func (dict *SyncDict) RandomKeys(limit int) []string {
	keys := make([]string, limit)
	//遍历传进来的次数,每一次进入dict都只作用一个key上面,因为map每次遍历是无序的
	for i := 0; i < limit; i++ {
		dict.m.Range(func(key, _ any) bool {
			keys[i] = key.(string)
			//直接返回false,关闭当前遍历
			return false
		})
	}

	return keys
}

//RandomDistinctKeys 随机取不重复的值
func (dict *SyncDict) RandomDistinctKeys(limit int) []string {
	keys := make([]string, limit)
	i := 0
	dict.m.Range(func(key, _ any) bool {
		keys[i] = key.(string)
		i++
		if i == limit {
			//直接返回false,关闭当前遍历
			return false
		}
		return true
	})

	return keys
}

//clear 直接换一个新的即可
func (dict *SyncDict) Clear() {
	dict = MakeSyncDict()
}

func MakeSyncDict() *SyncDict {
	return &SyncDict{}
}

3.2 DB

DB作为最底层支持的数据库存储层,DB对数据存储的 Dict 进行了一层包装对外提供了支持

字段说明
  • index:数据库的索引,每个数据库都会有一个对应的索引id用于进行切换标记
  • data:数据存储的地方,这里我们定义了一个 Dict 的结构用于底层数据的存储
  • addAof:定义的一个aof操作的匿名函数,后续来进行实现
//DB 每一个redis的分数据库
type DB struct {
	//数据库的索引
	index int
	//数据存储的实现
	data dict.Dict
	//赋值一个addAof的方法,可以让指令执行的时候写入数据
	addAof func(line CmdLine)
}

//Close 关闭数据库进行清空
func (db *DB) Close() {
	db.data.Clear()
}

//AfterClientClose 关闭数据库需要做的操作
func (db *DB) AfterClientClose(c resp.Connection) {

}

//ExecFunc 后续所有的指令执行都要实现当前函数
type ExecFunc func(db *DB, args [][]byte) resp.Reply

//CmdLine 给二维字节组取一个别名
type CmdLine = [][]byte

//makeDB 创建数据库
func makeDB() *DB {
	return &DB{
		data:   dict.MakeSyncDict(),
		addAof: func(line CmdLine) {}, //设置一个空的默认方法,防止初始化开始的时候出现问题
	}
}

//Exec 在数据库上进行执行
func (db *DB) Exec(c resp.Connection, args [][]byte) resp.Reply {
	//获取到一个数据,存储的应该是指令 例如:setnx或者set;这里统一一下按照小写指令进行处理
	cmdName := strings.ToLower(string(args[0]))
	//获取到执行器
	cmd, ok := cmdTable[cmdName]
	if !ok {
		//如果没有实现这个命令,恢复一个错误
		return reply.MakeStandardErrReply("ERR unknown command " + cmdName)
	}
	//校验一下参数的个数是否合法,例如:set k,缺少了value
	if !validateArity(cmd.arity, args) {
		return reply.MakeArgNumErrReply(cmdName)
	}
	//获取到执行器
	fun := cmd.executor
	//set k,v --> 这里传入的参数就是 k,v
	return fun(db, args[1:])
}

//validateArity 验证参数,参数分为两种情况 :set K v --> arity = 3 ; 如果是 exists k1 k2 k3 ---> arity 的参数最少都需要2个(属于变长的指令),统一定义为 -2,负号执行标记变长的
func validateArity(arity int, cmdLine CmdLine) bool {
	argNum := len(cmdLine)
	//指令为定长的
	if arity >= 0 {
		return argNum == arity
	}
	//这里负arity可以将变长的参数个数转换为正数进行判断,在注册指令的时候就指定好变长的参数
	return argNum >= -arity
}

//GetEntity 公共的方法获取数据,db将dict进行包装了一层
func (db *DB) GetEntity(key string) (*database.DataEntity, bool) {
	//获取到最原始的值
	raw, ok := db.data.Get(key)
	if !ok {
		return nil, false
	}
	//将raw原始的格式需要转换为 DataEntity
	dataEntity, _ := raw.(*database.DataEntity)
	return dataEntity, true
}

//PutEntity 存入entity对象
func (db *DB) PutEntity(key string, entity *database.DataEntity) int {
	return db.data.Put(key, entity)
}

//PutIfExists 存入entity对象,如果存在
func (db *DB) PutIfExists(key string, entity *database.DataEntity) int {
	return db.data.PutIfExists(key, entity)
}

//PutIfAbsent 如果不存在进行存入
func (db *DB) PutIfAbsent(key string, entity *database.DataEntity) int {
	return db.data.PutIfAbsent(key, entity)
}

//Remove 移除
func (db *DB) Remove(key string) int {
	return db.data.Remove(key)
}

//Removes 批量移除
func (db *DB) Removes(keys ...string) int {
	result := 0
	for _, key := range keys {
		_, ok := db.data.Get(key)
		if ok {
			result += db.Remove(key)
		}
	}
	return result
}

//Flush 清空
func (db *DB) Flush() {
	db.data.Clear()
}

3.3 Database

database/database.go 文件中定义的一个 Database 结构体主要是对 DB 进行了一层封装

字段说明
  • dbSet:创建的数据库,根据配置文件中的初始化数据库的数量进行创建
  • aofHandler:自定义的 AofHandler 接口,后续用于消息持久化
type Database struct {
	//存储多个数据库
	dbSet []*DB
	//aof处理器
	aofHandler *aof.AofHandler
}

//NewDatabase 创建一个db数据库内核,初始化数据,根据配置文件中的数据库数量进行初始化
func NewDatabase() *Database {
	database := &Database{}
	if config.Properties.Databases == 0 {
		config.Properties.Databases = 16
	}
	//初始化16个数据库db
	database.dbSet = make([]*DB, config.Properties.Databases)
	for i := range database.dbSet {
		db := makeDB()
		db.index = i
		database.dbSet[i] = db
	}
	//判断是否开启消息落盘的功能
	if config.Properties.AppendOnly {
		aofHandler, err := aof.NewAofHandler(database)
		if err != nil {
			panic(err)
		}
		database.aofHandler = aofHandler
		//初始化db里面的匿名处理器的方法
		for _, db := range database.dbSet {
			tmpDb := db
			db.addAof = func(line CmdLine) {
				/**
				匿名函数会出现闭包的问题,如果使用 db.index 会一直是15索引
				*/
				database.aofHandler.AddAof(tmpDb.index, line)
			}
		}
	}
	return database
}

//Exec 内核层进行执行
func (d *Database) Exec(client resp.Connection, args [][]byte) resp.Reply {
	defer func() {
		if err := recover(); err != nil {
			logger.Error(err)
		}
	}()
	//需要处理 select 1 命令来切换数据库,后面的db核心数据库不需要处理select
	cmdName := strings.ToLower(string(args[0]))
	if cmdName == "select" {
		//select命令
		if len(args) != 2 {
			return reply.MakeArgNumErrReply("select")
		}
		return execSelect(client, d, args)
	}
	dbIndex := client.GetDBIndex()
	db := d.dbSet[dbIndex]
	if db == nil {
		return reply.MakeStandardErrReply("ERR DB is nil")
	}
	return db.Exec(client, args)
}

//Close 关闭数据库
func (d *Database) Close() {
	for _, db := range d.dbSet {
		db.Close()
	}
}

//AfterClientClose 关闭连接之后需要执行的回调
func (d *Database) AfterClientClose(c resp.Connection) {

}

//execSelect 根据用户发送的指令,来修改数据库的索引,例如:select 1
func execSelect(c resp.Connection, database *Database, args [][]byte) resp.Reply {
	//参数进行转换为数字
	dbIndex, err := strconv.Atoi(string(args[1]))
	if err != nil {
		return reply.MakeStandardErrReply("ERR invalid DB index")
	}
	if dbIndex >= len(database.dbSet) {
		return reply.MakeStandardErrReply("ERR DB index is out of range")
	}
	c.SelectDB(dbIndex)
	return reply.MakeOkReply()
}

以上就是数据库层的实现,主要是 Database --> DB --> Dict 这么一个层次的结构,Database获取到用户需要在哪个数据库进行指令的执行,然后交给对应索引的 DB 进行执行,DB然后去回去到对应的指令执行器进行执行,下面是指令的实现

4. 指令实现

4.1 command

这里定义一个 command 的命令结构体,主要用于对执行命令的函数进行包装

参数说明
  • executor :执行器函数,这个函数的定义是在 database/db.go 中进行定义就是上面定义 DB 结构体文件中的函数,后续我们只需要将对应的函数注册到一个 map 中跟命令进行关联上就可以方便的取到
//ExecFunc 后续所有的指令执行都要实现当前函数
type ExecFunc func(db *DB, args [][]byte) resp.Reply
  • arity:当前指令需要几个参数,例如:set key value,就是需要3个参数;这里的 arity 有正负两种情况,负数代表变长指令,例如:keys key1 key2,那么arity就是 -2 也就是说指令最少都得两个参数
方法说明
  • RegisterCommand:提供的一个公共方法可以将执行的函数注册到map中,跟命令相互关联上
//用于记录所有指令跟 command 的关系
var cmdTable = make(map[string]*command)

//command 用于区分指令的类型以及执行方式
type command struct {
	//执行器
	executor ExecFunc
	//需要几个参数
	arity int
}

//RegisterCommand 注册指令的执行方法,存储的时候就是 一个指令对应一个执行器
func RegisterCommand(name string, executor ExecFunc, arity int) {
	name = strings.ToLower(name)
	cmdTable[name] = &command{
		executor: executor,
		arity:    arity,
	}
}

4.2 keys指令集

database/keys.go 中我们定义了 keys相关指令集的实现,其中方法名称随意定义,主要是在下面的 init() 方法中进行注册,go在启动时,会自动执行每个文件中的 init() 方法,这样就可以将执行的函数跟命令相互关联上了。每个方法都需要 *DBargs [][]bytes 两个参数,上面说了 DB 是对 Dict 进行封装的,Dict 又是真正存储数据的地方,所以命令执行的方法只需要去调用 DB 中对应的方法即可

/**
实现以下keys指令集:
	DEL
	EXISTS
	KEYS
	FLUSHDB
	TYPE
	RENAME
	RENAMENX
*/

//execDel DEL k1 k2 k3,外面就已经切掉了DEL
func execDel(db *DB, args [][]byte) resp.Reply {
	keys := make([]string, len(args))
	for i, v := range keys {
		keys[i] = string(v)
	}
	deleted := db.Removes(keys...)
	if deleted > 0 {
		//前面指令是被切掉了,这里需要恢复回来
		db.addAof(utils.ToCmdLine2("del", args...))
	}
	return reply.MakeIntReply(int64(deleted))
}

//execExists Exists存在几个key
func execExists(db *DB, args [][]byte) resp.Reply {
	keys := make([]string, len(args))
	result := int64(0)
	for _, v := range keys {
		_, exists := db.GetEntity(v)
		if exists {
			result++
		}
	}
	return reply.MakeIntReply(result)
}

//execKeys keys k1 k2 k3
func execKeys(db *DB, args [][]byte) resp.Reply {
	//获取到第一个参数是否是通配符
	pattern := wildcard.CompilePattern(string(args[0]))
	//用于所有匹配的key
	result := make([][]byte, 0)
	db.data.ForEach(func(key string, val interface{}) bool {
		match := pattern.IsMatch(key)
		if match {
			result = append(result, []byte(key))
		}
		return true
	})
	return reply.MakeMultiBulkReply(result)
}

//execType 查询key的类型,例如:type key1
func execType(db *DB, args [][]byte) resp.Reply {
	key := string(args[0])
	entity, ok := db.GetEntity(key)
	if !ok {
		return reply.MakeStatusReply("none") //tcp报文里面就是 :none\r\n
	}
	//类型断言
	switch entity.Data.(type) {
	case []byte:
		return reply.MakeStatusReply("string")
		//todo: 判断后续其它的类型
	}
	return reply.MakeUnknownErrReply()
}

//execRename 改名,例如:rename old newKey
func execRename(db *DB, args [][]byte) resp.Reply {
	old := string(args[0])
	newKey := string(args[1])
	entity, exists := db.GetEntity(old)
	if !exists {
		return reply.MakeStandardErrReply("no such key")
	}
	//将新的key存入进行
	db.PutEntity(newKey, entity)
	//删除老的key
	db.Remove(old)
	db.addAof(utils.ToCmdLine2("rename", args...))
	return reply.MakeOkReply()
}

//execRenameNx renamenx:在改到新名称的时候,判断新的名称会不会把原来已经存在的key给干掉,例如:renamenx K1 K2 ,需要判断原来K2是否存在
func execRenameNx(db *DB, args [][]byte) resp.Reply {
	old := string(args[0])
	newKey := string(args[1])
	//判断新的key是否存在了,如果存在就什么都不做
	_, ok := db.GetEntity(newKey)
	if ok {
		//如果什么都没有操作就返回0
		return reply.MakeIntReply(0)
	}
	//继续判断原来的逻辑
	entity, exists := db.GetEntity(old)
	if !exists {
		return reply.MakeStandardErrReply("no such key")
	}
	//将新的key存入进行
	db.PutEntity(newKey, entity)
	//删除老的key
	db.Remove(old)
	db.addAof(utils.ToCmdLine2("renamenx", args...))
	//如果执行了就返回一个1
	return reply.MakeIntReply(1)
}

//execFlushDb
func execFlushDb(db *DB, args [][]byte) resp.Reply {
	db.Flush()
	db.addAof(utils.ToCmdLine2("flushdb", args...))
	return reply.MakeOkReply()
}

//init 初始化注册命令
func init() {
	RegisterCommand("del", execDel, -2)
	RegisterCommand("exists", execExists, -2)
	RegisterCommand("flushdb", execFlushDb, -1) //-1的参数无论 flushdb后面跟随什么都直接忽略
	RegisterCommand("type", execType, 2)
	RegisterCommand("rename", execRename, 3)
	RegisterCommand("renamenx", execRenameNx, 3)
	RegisterCommand("keys", execKeys, -2)
}

4.3 string指令集

string 指令集在 database\string.go 中进行定义,跟上面的 keys集一样的实现方式

/**
实现string类型的指令集
	GET
	SET
	SETNX
	GETSET
	STRLEN
*/

//init go语言在启动的时候就会执行init方法
func init() {
	RegisterCommand("get", execGet, 2)
	RegisterCommand("set", execSet, 3)
	RegisterCommand("setnx", execSetnx, 3)
	RegisterCommand("getset", execGetset, 3)
	RegisterCommand("strlen", execStrLen, 2)
}

//execGet 获取数据
func execGet(db *DB, args [][]byte) resp.Reply {
	key := string(args[0])
	entity, ok := db.GetEntity(key)
	if !ok {
		return reply.MakeNullBulkReply()
	}
	//目前存入数据都是存入 []byte
	bytes, b := entity.Data.([]byte)
	//如果存的类型有其它的,所以需要判断是否转换成功
	if !b {
		//todo:转换其它的类型
	}
	return reply.MakeBulkReply(bytes)
}

//execSet set key value
func execSet(db *DB, args [][]byte) resp.Reply {
	key := string(args[0])
	//按照字节数组的方式存储数据
	value := args[1]
	data := &database.DataEntity{
		Data: value, //数据存储依照字节数组的方式进行存储
	}
	result := db.PutEntity(key, data)
	db.addAof(utils.ToCmdLine2("set", args...))
	return reply.MakeIntReply(int64(result))
}

//execSetnx setnx key value
func execSetnx(db *DB, args [][]byte) resp.Reply {
	key := string(args[0])
	//按照字节数组的方式存储数据
	value := args[1]
	data := &database.DataEntity{
		Data: value, //数据存储依照字节数组的方式进行存储
	}
	result := db.PutIfAbsent(key, data)
	db.addAof(utils.ToCmdLine2("setnx", args...))
	return reply.MakeIntReply(int64(result))
}

//execGetset getset key value
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.MakeNullBulkReply()
	}
	db.addAof(utils.ToCmdLine2("getset", args...))
	return reply.MakeBulkReply(entity.Data.([]byte))
}

//execStrLen strlen key 获取到的key的value长度
func execStrLen(db *DB, args [][]byte) resp.Reply {
	key := string(args[0])
	//先获取原来的值,再设置现在的值
	entity, exists := db.GetEntity(key)
	if !exists {
		return reply.MakeNullBulkReply()
	}
	return reply.MakeIntReply(int64(len(entity.Data.([]byte))))
}

4.4 ping命令

ping 命令实现比较简单

//Ping ping的命令
func Ping(db *DB, args [][]byte) resp.Reply {
	return reply.MakePongReply()
}

//init 随便写在哪个包下面,go语言在启动的时候都会调用这个方法
func init() {
	RegisterCommand("ping", Ping, 1)
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值