最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Golang實現(xiàn)自己的Redis數(shù)據(jù)庫內(nèi)存實例探究

 更新時間:2024年01月24日 10:16:58   作者:紹納 nullbody筆記  
這篇文章主要為大家介紹了Golang實現(xiàn)自己的Redis數(shù)據(jù)庫內(nèi)存實例探究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

引言

用11篇文章實現(xiàn)一個可用的Redis服務(wù),姑且叫EasyRedis吧,希望通過文章將Redis掰開撕碎了呈現(xiàn)給大家,而不是僅僅停留在八股文的層面,并且有非常爽的感覺,歡迎持續(xù)關(guān)注學(xué)習(xí)。

[x] easyredis之TCP服務(wù)

[x] easyredis之網(wǎng)絡(luò)請求序列化協(xié)議(RESP)

[x] easyredis之內(nèi)存數(shù)據(jù)庫

[ ] easyredis之過期時間 (時間輪實現(xiàn))

[ ] easyredis之持久化 (AOF實現(xiàn))

[ ] easyredis之發(fā)布訂閱功能

[ ] easyredis之有序集合(跳表實現(xiàn))

[ ] easyredis之 pipeline 客戶端實現(xiàn)

[ ] easyredis之事務(wù)(原子性/回滾)

[ ] easyredis之連接池

[ ] easyredis之分布式集群存儲

EasyRedis之內(nèi)存數(shù)據(jù)庫篇

上篇文章已經(jīng)可以解析出Redis serialization protocol,本篇基于解析出來的命令,進行代碼處理過程: 這里以5個常用命令作為本篇文章的切入口: 命令官方文檔 https://redis.io/commands

# ping服務(wù)器
PING [message]
# 授權(quán)密碼設(shè)置
AUTH <password>
# 選擇數(shù)據(jù)庫
SELECT index
# 設(shè)置key
SET key value [NX | XX]  [EX seconds | PX milliseconds]
# 獲取key
GET key

ping服務(wù)器

代碼路徑: engine/engine.go這個功能算是小試牛刀的小功能,讓大家對基本的套路有個簡單的認識

// redisCommand 待執(zhí)行的命令  protocal.Reply 執(zhí)行結(jié)果
func (e *Engine) Exec(c *connection.KeepConnection, redisCommand [][]byte) (result protocal.Reply) {
	 //... 省略...
	commandName := strings.ToLower(string(redisCommand[0]))
	if commandName == "ping" { // https://redis.io/commands/ping/
		return Ping(redisCommand[1:])
	}
	 //... 省略...
}

Exec函數(shù)就是進行命令處理的總?cè)肟诤瘮?shù),通過從協(xié)議中解析出來的redisCommand,我們可以提取出命令名commandName變量,然后在Ping(redisCommand[1:])函數(shù)中進行邏輯處理。

func Ping(redisArgs [][]byte) protocal.Reply {

	iflen(redisArgs) == 0 { // 不帶參數(shù)
		return protocal.NewPONGReply()
	} elseiflen(redisArgs) == 1 { // 帶參數(shù)1個
		return protocal.NewBulkReply(redisArgs[0])
	}
    // 否則,回復(fù)命令格式錯誤
	return protocal.NewArgNumErrReply("ping")
}

Ping函數(shù)的本質(zhì)就是基于PING [message]這個redis命令的基本格式,進行不同的數(shù)據(jù)響應(yīng)。

這里建議大家看下Ping命令的文檔 https://redis.io/commands/ping/ 

以上圖為例

  • 如果是直接的PING命令,后面不帶參數(shù),我們要回復(fù)PONG

  • 如果帶參"Hello world",我們原樣回復(fù)Hello world

  • 如果帶了兩個參數(shù)helloworld,直接回復(fù)錯誤。

有了這個處理套路,那么其他的命令也可依葫蘆畫瓢了。

授權(quán)密碼設(shè)置

啟動redis服務(wù)的時候,如果有設(shè)定需要密碼,那么客戶端連接上來以后,需要先執(zhí)行一次 Auth password的授權(quán)命令

// redisCommand 待執(zhí)行的命令  protocal.Reply 執(zhí)行結(jié)果
func (e *Engine) Exec(c *connection.KeepConnection, redisCommand [][]byte) (result protocal.Reply) {

	//... 省略...
	commandName := strings.ToLower(string(redisCommand[0]))
	if commandName == "auth" {
		return Auth(c, redisCommand[1:])
	}
	// 校驗密碼
	if !checkPasswd(c) {
		return protocal.NewGenericErrReply("Authentication required")
	}

	//... 省略...
}

服務(wù)器接收到命令后,依據(jù)commandName的變量值為auth,則執(zhí)行 Auth(c, redisCommand[1:])函數(shù)

func Auth(c *connection.KeepConnection, redisArgs [][]byte) protocal.Reply {
	iflen(redisArgs) != 1 {
		return protocal.NewArgNumErrReply("auth")
	}

	if conf.GlobalConfig.RequirePass == "" {
		return protocal.NewGenericErrReply("No authorization is required")
	}

	password := string(redisArgs[0])
	if conf.GlobalConfig.RequirePass != password {
		return protocal.NewGenericErrReply("Auth failed, password is wrong")
	}

	c.SetPassword(password)
	return protocal.NewOkReply()
}

這里的解析過程我們是按照 AUTH <password>這個命令格式進行解析,解析出來密碼以后,我們需要將密碼保存在c *connection.KeepConnection對象的成員變量中。這里就類似session的原理,存儲以后,當(dāng)前連接接下來的命令就不需要繼續(xù)帶上密碼了。在每次處理其他命令之前,校驗下當(dāng)前連接的密碼是否有效:

func checkPasswd(c *connection.KeepConnection) bool {
	// 如果沒有配置密碼
	if conf.GlobalConfig.RequirePass == "" {
		returntrue
	}
	// 密碼是否一致
	return c.GetPassword() == conf.GlobalConfig.RequirePass
}

選擇數(shù)據(jù)庫

這個命令雖然用的比較少,但是這個涉及到服務(wù)端結(jié)構(gòu)的設(shè)計。redis的服務(wù)端是支持多個數(shù)據(jù)庫,每個數(shù)據(jù)庫就是一個CRUD的基本存儲單元,不同的數(shù)據(jù)庫(存儲單元)之間的數(shù)據(jù)是不共享的。默認情況下,我們使用的都是select 0數(shù)據(jù)庫。

代碼結(jié)構(gòu)如下圖:(這個圖很重要,配合代碼好好理解下)

我們需要在Engine結(jié)構(gòu)體中創(chuàng)建多個 *DB對象

func NewEngine() *Engine {
	engine := &Engine{}
	// 多個dbSet
	engine.dbSet = make([]*atomic.Value, conf.GlobalConfig.Databases)
	for i := 0; i < conf.GlobalConfig.Databases; i++ {
		// 創(chuàng)建 *db
		db := newDB()
		db.SetIndex(i)
		// 保存到 atomic.Value中
		dbset := &atomic.Value{}
		dbset.Store(db)
		// 賦值到 dbSet中
		engine.dbSet[i] = dbset
	}
	return engine
}

在用戶端發(fā)送來 select index 命令,服務(wù)端需要記錄下來當(dāng)前連接選中的數(shù)據(jù)庫索引

// redisCommand 待執(zhí)行的命令  protocal.Reply 執(zhí)行結(jié)果
func (e *Engine) Exec(c *connection.KeepConnection, redisCommand [][]byte) (result protocal.Reply) {
	//....忽略....
	// 基礎(chǔ)命令
	switch commandName {
	case"select": // 表示當(dāng)前連接,要選中哪個db https://redis.io/commands/select/
		return execSelect(c, redisCommand[1:])
	}
	//....忽略....
}
// 這里會對 選中的索引 越界的判斷,如果一切都正常,就保存到 c *connection.KeepConnection 連接的成員變量 index
func execSelect(c *connection.KeepConnection, redisArgs [][]byte) protocal.Reply {
	iflen(redisArgs) != 1 {
		return protocal.NewArgNumErrReply("select")
	}
	dbIndex, err := strconv.ParseInt(string(redisArgs[0]), 10, 64)
	if err != nil {
		return protocal.NewGenericErrReply("invaild db index")
	}
	if dbIndex < 0 || dbIndex >= int64(conf.GlobalConfig.Databases) {
		return protocal.NewGenericErrReply("db index out of range")
	}
	c.SetDBIndex(int(dbIndex))
	return protocal.NewOkReply()
}

設(shè)置key

在用戶要求執(zhí)行 set key value命令的時候,我們需要先選中執(zhí)行功能*DB對象,就是上面的select index命令要求選中的對象,默認是0

// redisCommand 待執(zhí)行的命令  protocal.Reply 執(zhí)行結(jié)果
func (e *Engine) Exec(c *connection.KeepConnection, redisCommand [][]byte) (result protocal.Reply) {

	//....忽略....

	// redis 命令處理
	dbIndex := c.GetDBIndex()
	logger.Debugf("db index:%d", dbIndex)
	db, errReply := e.selectDB(dbIndex)
	if errReply != nil {
		return errReply
	}
	return db.Exec(c, redisCommand)
}

可以看到,最終代碼執(zhí)行execNormalCommand函數(shù),該函數(shù)會從命令注冊中心獲取命令的執(zhí)行函數(shù)

func (db *DB) Exec(c *connection.KeepConnection, redisCommand [][]byte) protocal.Reply {
	return db.execNormalCommand(c, redisCommand)
}
func (db *DB) execNormalCommand(c *connection.KeepConnection, redisCommand [][]byte) protocal.Reply {
	cmdName := strings.ToLower(string(redisCommand[0]))
	// 從命令注冊中心,獲取命令的執(zhí)行函數(shù)
	command, ok := commandCenter[cmdName]
	if !ok {
		return protocal.NewGenericErrReply("unknown command '" + cmdName + "'")
	}
	fun := command.execFunc
	return fun(db, redisCommand[1:])
}

最終 set命令的實際執(zhí)行函數(shù)代碼路徑為engine/string.go中的func cmdSet(db *DB, args [][]byte) protocal.Reply函數(shù)。代碼的的本質(zhì)其實還是解析字符串,按照官方文檔https://redis.io/commands/set/ 要求的格式獲取對應(yīng)的參數(shù),執(zhí)行數(shù)據(jù)的存儲db.PutEntity(key, &entity)

func (db *DB) PutEntity(key string, entity *payload.DataEntity) int {
	return db.dataDict.Put(key, entity)
}

dataDict*ConcurrentDict類型的并發(fā)安全的字典

// 并發(fā)安全的字典
type ConcurrentDict struct {
	shds  []*shard      // 底層shard切片
	mask  uint32// 掩碼
	count *atomic.Int32 // 元素個數(shù)
}

ConcurrentDict通過分片的模式,將數(shù)據(jù)分散在不同的*shard對象中,shard的本質(zhì)就是map+讀寫鎖mu

type shard struct {
	m  map[string]interface{}
	mu sync.RWMutex
}

所以內(nèi)存數(shù)據(jù)庫的本質(zhì)就是操作map

  • key就是set命令的key

  • value我們額外包裝了一個DataEntity對象,將實際的值保存在 RedisObject

type DataEntity struct {
	RedisObject interface{} // 字符串 跳表 鏈表 quicklist 集合 etc...
}

代碼中已經(jīng)注釋的很清晰,建議直接看代碼

獲取key

代碼只是額外多調(diào)用了幾層函數(shù),本質(zhì)就是調(diào)用db.dataDict.Get(key) 函數(shù),其實又是*ConcurrentDict,代碼可能感覺有點繞,把上面的代碼結(jié)構(gòu)圖好好理解一下。

func cmdGet(db *DB, args [][]byte) protocal.Reply {
	iflen(args) != 1 {
		return protocal.NewSyntaxErrReply()
	}
	key := string(args[0])
	bytes, reply := db.getStringObject(key)
	if reply != nil {
		return reply
	}
	return protocal.NewBulkReply(bytes)
}
// 獲取底層存儲對象【字節(jié)流】
func (db *DB) getStringObject(key string) ([]byte, protocal.Reply) {
	payload, exist := db.GetEntity(key)
	if !exist {
		returnnil, protocal.NewNullBulkReply()
	}
	// 判斷底層對象是否為【字節(jié)流】
	bytes, ok := payload.RedisObject.([]byte)
	if !ok {
		returnnil, protocal.NewWrongTypeErrReply()
	}
	return bytes, nil
}
// 獲取內(nèi)存中的數(shù)據(jù)
func (db *DB) GetEntity(key string) (*payload.DataEntity, bool) {
	// key 不存在
	val, exist := db.dataDict.Get(key)
	if !exist {
		returnnil, false
	}
	dataEntity, ok := val.(*payload.DataEntity)
	if !ok {
		returnnil, false
	}
	return dataEntity, true
}

效果演示

項目代碼地址: https://github.com/gofish2020/easyredis 

以上就是Golang實現(xiàn)自己的Redis數(shù)據(jù)庫內(nèi)存實例探究的詳細內(nèi)容,更多關(guān)于Golang Redis數(shù)據(jù)庫內(nèi)存的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • go語言區(qū)塊鏈學(xué)習(xí)調(diào)用以太坊

    go語言區(qū)塊鏈學(xué)習(xí)調(diào)用以太坊

    這篇文章主要為大家介紹了go語言區(qū)塊鏈學(xué)習(xí)如何調(diào)用以太坊的示例實現(xiàn)過程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步
    2021-10-10
  • Go  slog使用入門示例教程

    Go  slog使用入門示例教程

    slog是Go 1.21 引入的官方結(jié)構(gòu)化日志庫(Structured Logging),它結(jié)束了Go標(biāo)準庫只有簡單log包的歷史,讓我們可以直接輸出JSON或 Key-Value 格式的日志,接下來通過本文介紹Go  slog使用入門教程,感興趣的朋友一起看看吧
    2026-02-02
  • Go語言題解LeetCode1260二維網(wǎng)格遷移示例詳解

    Go語言題解LeetCode1260二維網(wǎng)格遷移示例詳解

    這篇文章主要為大家介紹了Go語言題解LeetCode1260二維網(wǎng)格遷移示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-01-01
  • 詳解Go語言中如何高效遍歷目錄

    詳解Go語言中如何高效遍歷目錄

    目錄遍歷是一個很常見的操作,它的使用場景有如文件目錄查看、文件系統(tǒng)清理、日志分析、項目構(gòu)建等,本文將詳細介紹在Go中幾種遍歷目錄文件的方法,需要的可以參考下
    2024-02-02
  • go如何使用gin結(jié)合jwt做登錄功能簡單示例

    go如何使用gin結(jié)合jwt做登錄功能簡單示例

    jwt全稱Json web token,是一種認證和信息交流的工具,這篇文章主要給大家介紹了關(guān)于go如何使用gin結(jié)合jwt做登錄功能的相關(guān)資料,文中通過代碼介紹的非常詳細,需要的朋友可以參考下
    2024-01-01
  • golang生成RSA公鑰和密鑰的實現(xiàn)方法

    golang生成RSA公鑰和密鑰的實現(xiàn)方法

    本文主要介紹了golang生成RSA公鑰和密鑰的實現(xiàn)方法,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-08-08
  • Go語言結(jié)合grpc和protobuf實現(xiàn)去中心化的聊天室

    Go語言結(jié)合grpc和protobuf實現(xiàn)去中心化的聊天室

    這篇文章主要為大家詳細介紹了Go語言如何結(jié)合grpc和protobuf實現(xiàn)去中心化的聊天室,文中的示例代碼講解詳細,有需要的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2024-03-03
  • 淺談Golang的GC垃圾回收機制

    淺談Golang的GC垃圾回收機制

    本文介紹了Go語言的垃圾回收機制,通過使用對象池、減少短生命周期對象的創(chuàng)建和優(yōu)化內(nèi)存布局等方法,可以有效地減少GC壓力,提高程序的性能,感興趣的可以了解一下
    2024-11-11
  • Go語言的隊列和堆棧實現(xiàn)方法

    Go語言的隊列和堆棧實現(xiàn)方法

    這篇文章主要介紹了Go語言的隊列和堆棧實現(xiàn)方法,涉及container/list包的使用技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-02-02
  • 詳解Golang中Channel的用法

    詳解Golang中Channel的用法

    如果說goroutine是Go語言程序的并發(fā)體的話,那么channels則是它們之間的通信機制。這篇文章主要介紹Golang中Channel的用法,需要的朋友可以參考下
    2020-11-11

最新評論

馆陶县| 玉林市| 海原县| 遂宁市| 思茅市| 靖边县| 日喀则市| 巴林左旗| 萍乡市| 义乌市| 昌江| 友谊县| 沈阳市| 瑞金市| 清苑县| 玛多县| 镶黄旗| 斗六市| 贵德县| 府谷县| 霍邱县| 井研县| 专栏| 资兴市| 临清市| 穆棱市| 大埔县| 榆树市| 修水县| 郁南县| 河曲县| 宁波市| 济宁市| 宜昌市| 韶山市| 铁岭县| 通化县| 常熟市| 新余市| 双鸭山市| 文昌市|