GO實現Redis:GO實現記憶體資料庫(3)

来源:https://www.cnblogs.com/csgopher/archive/2023/03/24/17249335.html
-Advertisement-
Play Games

實現Redis的database層(核心層:處理命令並返回) https://github.com/csgopher/go-redis 本文涉及以下文件: dict:定義字典的一些方法 sync_dict:實現dict db:分資料庫 command:定義指令 ping,keys,string:指令 ...



  • 實現Redis的database層(核心層:處理命令並返回)
  • https://github.com/csgopher/go-redis
  • 本文涉及以下文件:
    dict:定義字典的一些方法
    sync_dict:實現dict
    db:分資料庫
    command:定義指令
    ping,keys,string:指令的具體處理邏輯
    database:單機版資料庫

datastruct/dict/dict.go

type Consumer func(key string, val interface{}) bool

type Dict interface {
   Get(key string) (val interface{}, exists bool)
   Len() int
   Put(key string, val interface{}) (result int)
   PutIfAbsent(key string, val interface{}) (result int)
   PutIfExists(key string, val interface{}) (result int)
   Remove(key string) (result int)
   ForEach(consumer Consumer)
   Keys() []string
   RandomKeys(limit int) []string
   RandomDistinctKeys(limit int) []string
   Clear()
}

Dict介面:Redis數據結構的介面。這裡我們使用sync.Map作為字典的實現,如果想用別的數據結構,換一個實現即可
Consumer:遍歷字典所有的鍵值對,返回值是布爾,true繼續遍歷,false停止遍歷

datastruct/dict/sync_dict.go

type SyncDict struct {
   m sync.Map
}

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

func (dict *SyncDict) Get(key string) (val interface{}, exists bool) {
   val, ok := dict.m.Load(key)
   return val, ok
}

func (dict *SyncDict) Len() int {
   length := 0
   dict.m.Range(func(k, v interface{}) bool {
      length++
      return true
   })
   return length
}

func (dict *SyncDict) Put(key string, val interface{}) (result int) {
   _, existed := dict.m.Load(key)
   dict.m.Store(key, val)
   if existed {
      return 0
   }
   return 1
}

func (dict *SyncDict) PutIfAbsent(key string, val interface{}) (result int) {
   _, existed := dict.m.Load(key)
   if existed {
      return 0
   }
   dict.m.Store(key, val)
   return 1
}

func (dict *SyncDict) PutIfExists(key string, val interface{}) (result int) {
   _, existed := dict.m.Load(key)
   if existed {
      dict.m.Store(key, val)
      return 1
   }
   return 0
}

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

func (dict *SyncDict) ForEach(consumer Consumer) {
   dict.m.Range(func(key, value interface{}) bool {
      consumer(key.(string), value)
      return true
   })
}

func (dict *SyncDict) Keys() []string {
   result := make([]string, dict.Len())
   i := 0
   dict.m.Range(func(key, value interface{}) bool {
      result[i] = key.(string)
      i++
      return true
   })
   return result
}

func (dict *SyncDict) RandomKeys(limit int) []string {
   result := make([]string, limit)
   for i := 0; i < limit; i++ {
      dict.m.Range(func(key, value interface{}) bool {
         result[i] = key.(string)
         return false
      })
   }
   return result
}

func (dict *SyncDict) RandomDistinctKeys(limit int) []string {
   result := make([]string, limit)
   i := 0
   dict.m.Range(func(key, value interface{}) bool {
      result[i] = key.(string)
      i++
      if i == limit {
         return false
      }
      return true
   })
   return result
}

func (dict *SyncDict) Clear() {
   *dict = *MakeSyncDict()
}

使用sync.Map實現Dict介面

database/db.go

type DB struct {
	index int
	data  dict.Dict
}

type ExecFunc func(db *DB, args [][]byte) resp.Reply

type CmdLine = [][]byte

func makeDB() *DB {
	db := &DB{
		data: dict.MakeSyncDict(),
	}
	return db
}

func (db *DB) Exec(c resp.Connection, cmdLine [][]byte) resp.Reply {
	cmdName := strings.ToLower(string(cmdLine[0]))
	cmd, ok := cmdTable[cmdName]
	if !ok {
		return reply.MakeErrReply("ERR unknown command '" + cmdName + "'")
	}
	if !validateArity(cmd.arity, cmdLine) {
		return reply.MakeArgNumErrReply(cmdName)
	}
	fun := cmd.executor
	return fun(db, cmdLine[1:]) // 把 set k v 中的set切掉
}

func validateArity(arity int, cmdArgs [][]byte) bool {
	argNum := len(cmdArgs)
	if arity >= 0 {
		return argNum == arity
	}
	return argNum >= -arity
}

func (db *DB) GetEntity(key string) (*database.DataEntity, bool) {
	raw, ok := db.data.Get(key)
	if !ok {
		return nil, false
	}
	entity, _ := raw.(*database.DataEntity)
	return entity, true
}

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

func (db *DB) PutIfExists(key string, entity *database.DataEntity) int {
	return db.data.PutIfExists(key, entity)
}

func (db *DB) PutIfAbsent(key string, entity *database.DataEntity) int {
	return db.data.PutIfAbsent(key, entity)
}

func (db *DB) Remove(key string) {
	db.data.Remove(key)
}

func (db *DB) Removes(keys ...string) (deleted int) {
	deleted = 0
	for _, key := range keys {
		_, exists := db.data.Get(key)
		if exists {
			db.Remove(key)
			deleted++
		}
	}
	return deleted
}

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

實現Redis中的分資料庫
ExecFunc:所有Redis的指令都寫成這樣的類型
validateArity方法:

  • 定長:set k v => arity=3;
  • 變長:exists k1 k2 k3 ... => arity=-2,表示參數>=2個

database/command.go

var cmdTable = make(map[string]*command)

type command struct {
   executor ExecFunc
   arity    int 
}

func RegisterCommand(name string, executor ExecFunc, arity int) {
   name = strings.ToLower(name)
   cmdTable[name] = &command{
      executor: executor,
      arity:    arity,
   }
}

command:每一個command結構體都是一個指令,例如ping,keys等等
arity:參數數量
cmdTable:記錄所有指令和command結構體的關係
RegisterCommand:註冊指令的實現,在程式

database/ping.go

func Ping(db *DB, args [][]byte) resp.Reply {
    if len(args) == 0 {
        return &reply.PongReply{}
    } else if len(args) == 1 {
        return reply.MakeStatusReply(string(args[0]))
    } else {
        return reply.MakeErrReply("ERR wrong number of arguments for 'ping' command")
    }
}

func init() {
    RegisterCommand("ping", Ping, 1)
}

init方法:在啟動程式時就會調用這個方法,用於初始化

database/keys.go

func execDel(db *DB, args [][]byte) resp.Reply {
   keys := make([]string, len(args))
   for i, v := range args {
      keys[i] = string(v)
   }

   deleted := db.Removes(keys...)
   return reply.MakeIntReply(int64(deleted))
}

func execExists(db *DB, args [][]byte) resp.Reply {
   result := int64(0)
   for _, arg := range args {
      key := string(arg)
      _, exists := db.GetEntity(key)
      if exists {
         result++
      }
   }
   return reply.MakeIntReply(result)
}

func execFlushDB(db *DB, args [][]byte) resp.Reply {
   db.Flush()
   return &reply.OkReply{}
}

func execType(db *DB, args [][]byte) resp.Reply {
   key := string(args[0])
   entity, exists := db.GetEntity(key)
   if !exists {
      return reply.MakeStatusReply("none")
   }
   switch entity.Data.(type) {
   case []byte:
      return reply.MakeStatusReply("string")
   }
   return &reply.UnknownErrReply{}
}

func execRename(db *DB, args [][]byte) resp.Reply {
   if len(args) != 2 {
      return reply.MakeErrReply("ERR wrong number of arguments for 'rename' command")
   }
   src := string(args[0])
   dest := string(args[1])
   
   entity, ok := db.GetEntity(src)
   if !ok {
      return reply.MakeErrReply("no such key")
   }
   db.PutEntity(dest, entity)
   db.Remove(src)
   return &reply.OkReply{}
}

func execRenameNx(db *DB, args [][]byte) resp.Reply {
   src := string(args[0])
   dest := string(args[1])

   _, exist := db.GetEntity(dest)
   if exist {
      return reply.MakeIntReply(0)
   }

   entity, ok := db.GetEntity(src)
   if !ok {
      return reply.MakeErrReply("no such key")
   }
   db.Removes(src, dest)
   db.PutEntity(dest, entity)
   return reply.MakeIntReply(1)
}

func execKeys(db *DB, args [][]byte) resp.Reply {
   pattern := wildcard.CompilePattern(string(args[0]))
   result := make([][]byte, 0)
   db.data.ForEach(func(key string, val interface{}) bool {
      if pattern.IsMatch(key) {
         result = append(result, []byte(key))
      }
      return true
   })
   return reply.MakeMultiBulkReply(result)
}

func init() {
   RegisterCommand("Del", execDel, -2)
   RegisterCommand("Exists", execExists, -2)
   RegisterCommand("Keys", execKeys, 2)
   RegisterCommand("FlushDB", execFlushDB, -1)
   RegisterCommand("Type", execType, 2)
   RegisterCommand("Rename", execRename, 3)
   RegisterCommand("RenameNx", execRenameNx, 3)
}

keys.go實現以下指令:
execDel:del k1 k2 k3 ...
execExists:exist k1 k2 k3 ...
execFlushDB:flushdb
execType:type k1
execRename:rename k1 k2
execRenameNx:renamenx k1 k2
execKeys:keys(依賴lib包的工具類wildcard.go)

database/string.go

func execGet(db *DB, args [][]byte) resp.Reply {
   key := string(args[0])
   bytes, err := db.getAsString(key)
   if err != nil {
      return err
   }
   if bytes == nil {
      return &reply.NullBulkReply{}
   }
   return reply.MakeBulkReply(bytes)
}

func (db *DB) getAsString(key string) ([]byte, reply.ErrorReply) {
   entity, ok := db.GetEntity(key)
   if !ok {
      return nil, nil
   }
   bytes, ok := entity.Data.([]byte)
   if !ok {
      return nil, &reply.WrongTypeErrReply{}
   }
   return bytes, nil
}

func execSet(db *DB, args [][]byte) resp.Reply {
   key := string(args[0])
   value := args[1]
   entity := &database.DataEntity{
      Data: value,
   }
   db.PutEntity(key, entity)
   return &reply.OkReply{}
}

func execSetNX(db *DB, args [][]byte) resp.Reply {
   key := string(args[0])
   value := args[1]
   entity := &database.DataEntity{
      Data: value,
   }
   result := db.PutIfAbsent(key, entity)
   return reply.MakeIntReply(int64(result))
}

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()
   }
   old := entity.Data.([]byte)
   return reply.MakeBulkReply(old)
}

func execStrLen(db *DB, args [][]byte) resp.Reply {
   key := string(args[0])
   entity, exists := db.GetEntity(key)
   if !exists {
      return reply.MakeNullBulkReply()
   }
   old := entity.Data.([]byte)
   return reply.MakeIntReply(int64(len(old)))
}

func init() {
   RegisterCommand("Get", execGet, 2)
   RegisterCommand("Set", execSet, -3)
   RegisterCommand("SetNx", execSetNX, 3)
   RegisterCommand("GetSet", execGetSet, 3)
   RegisterCommand("StrLen", execStrLen, 2)
}

string.go實現以下指令:
execGet:get k1
execSet:set k v
execSetNX:setnex k v
execGetSet:getset k v 返回舊值
execStrLen:strlen k

database/database.go

type Database struct {
   dbSet []*DB
}

func NewDatabase() *Database {
   mdb := &Database{}
   if config.Properties.Databases == 0 {
      config.Properties.Databases = 16
   }
   mdb.dbSet = make([]*DB, config.Properties.Databases)
   for i := range mdb.dbSet {
      singleDB := makeDB()
      singleDB.index = i
      mdb.dbSet[i] = singleDB
   }
   return mdb
}

func (mdb *Database) Exec(c resp.Connection, cmdLine [][]byte) (result resp.Reply) {
   defer func() {
      if err := recover(); err != nil {
         logger.Warn(fmt.Sprintf("error occurs: %v\n%s", err, string(debug.Stack())))
      }
   }()

   cmdName := strings.ToLower(string(cmdLine[0]))
   if cmdName == "select" {
      if len(cmdLine) != 2 {
         return reply.MakeArgNumErrReply("select")
      }
      return execSelect(c, mdb, cmdLine[1:])
   }
   dbIndex := c.GetDBIndex()
   selectedDB := mdb.dbSet[dbIndex]
   return selectedDB.Exec(c, cmdLine)
}

func execSelect(c resp.Connection, mdb *Database, args [][]byte) resp.Reply {
   dbIndex, err := strconv.Atoi(string(args[0]))
   if err != nil {
      return reply.MakeErrReply("ERR invalid DB index")
   }
   if dbIndex >= len(mdb.dbSet) {
      return reply.MakeErrReply("ERR DB index is out of range")
   }
   c.SelectDB(dbIndex)
   return reply.MakeOkReply()
}

func (mdb *Database) Close() {
}

func (mdb *Database) AfterClientClose(c resp.Connection) {
}

Database:一組db的集合
Exec:執行切換db指令或者其他指令
execSelect方法:選擇db(指令:select 2)

resp/handler/handler.go

import (
	database2 "go-redis/database"
)

func MakeHandler() *RespHandler {
   var db database.Database
   db = database2.NewDatabase()
   return &RespHandler{
      db: db,
   }
}

修改實現協議層handler的database實現

架構小結

TCP層服務TCP的連接,然後將連接交給RESP協議層的handler,handler監聽客戶端的連接,將指令解析後發給管道,管道轉給database層(database/database.go),核心層根據命令類型執行不同的方法,然後返回。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 該工程為在保存時執行開發的功能,函數入口點ufput。其他還有新建、打開、另存等都可以加入開發的操作,具體看UF_EXIT下的介紹。 用戶出口是一個可選特性,允許你在NX中某些預定義的位置(或出口)自動運行Open C API程式。如果你進入其中一個出口,NX會檢查你是否定義了指向Open C AP ...
  • 網站介紹 基於 python 開發的電子商城網站,平臺採用 B/S 結構,後端採用主流的 Python 語言進行開發,前端採用主流的 Vue.js 進行開發。這是給師弟開發的畢業設計。 整個平臺包括前臺和後臺兩個部分。 前臺功能包括:首頁、商品詳情頁、用戶中心模塊。 後臺功能包括:總覽、訂單管理、商 ...
  • SpringBoot資料庫操作 1.JDBC+HikariDataSource 在SpringBoot 2.x項目中,預設使用Hikari連接池管理數據源。相比於傳統的 C3P0 、DBCP、Tomcat jdbc 等連接池更加優秀。 當項目pom.xml引入spring-boot-starter- ...
  • 一個新應用 房地產廣告模塊 假設需要開發一個房地產模塊,該模塊覆蓋未包含在標準模塊集中特定業務領域。 以下為包含一些廣告的主列表視圖 form視圖頂層區域概括了房產的重要信息,比如name,Property Type, Postcode等等。 列表記錄詳情頁中,第一個tab包含了房產的描述信息,比如 ...
  • 生產者消費者問題 簡介 生產者消費者模式並不是GOF提出的23種設計模式之一,23種設計模式都是建立在面向對象的基礎之上的,但其實面向過程的編程中也有很多高效的編程模式,生產者消費者模式便是其中之一,它是我們編程過程中最常用的一種設計模式。 在實際的軟體開發過程中,經常會碰到如下場景:某個模塊負責產 ...
  • 之前用過NXOpen PDM的命名空間下的類,現在記錄一下通過PDM命名空間下的類查詢Teamcenter零組件的信息,也可以用來判斷該零組件是否存在。 1-該工程為DLL工程,直接在NX界面調用,所以直接獲取NXSession。 2-查詢函數advanced用到的查詢為:__NX_STD_ANY_ ...
  • 本文已收錄至Github,推薦閱讀 👉 Java隨想錄 微信公眾號:Java隨想錄 摘要 Redis是一款性能強勁的記憶體資料庫,但是在使用過程中,我們可能會遇到Big Key問題,這個問題就是Redis中某個key的value過大,所以Big Key問題本質是Big Value問題,導致Redis ...
  • 9 月 15 日,Figma 的 CEO Dylan Field 發佈消息:今天,Figma 宣佈接受 Adobe 的收購... Adobe 以約 200 億美元收購 Figma,這也是 Adobe 該公司在其歷史上的最大一筆收購。那是什麼樣的魔力,讓 Figma 被 Adobe 收購呢?下麵以定位 ...
一周排行
    -Advertisement-
    Play Games
  • 概述:本文代碼示例演示瞭如何在WPF中使用LiveCharts庫創建動態條形圖。通過創建數據模型、ViewModel和在XAML中使用`CartesianChart`控制項,你可以輕鬆實現圖表的數據綁定和動態更新。我將通過清晰的步驟指南包括詳細的中文註釋,幫助你快速理解並應用這一功能。 先上效果: 在 ...
  • openGauss(GaussDB ) openGauss是一款全面友好開放,攜手伙伴共同打造的企業級開源關係型資料庫。openGauss採用木蘭寬鬆許可證v2發行,提供面向多核架構的極致性能、全鏈路的業務、數據安全、基於AI的調優和高效運維的能力。openGauss深度融合華為在資料庫領域多年的研 ...
  • openGauss(GaussDB ) openGauss是一款全面友好開放,攜手伙伴共同打造的企業級開源關係型資料庫。openGauss採用木蘭寬鬆許可證v2發行,提供面向多核架構的極致性能、全鏈路的業務、數據安全、基於AI的調優和高效運維的能力。openGauss深度融合華為在資料庫領域多年的研 ...
  • 概述:本示例演示了在WPF應用程式中實現多語言支持的詳細步驟。通過資源字典和數據綁定,以及使用語言管理器類,應用程式能夠在運行時動態切換語言。這種方法使得多語言支持更加靈活,便於維護,同時提供清晰的代碼結構。 在WPF中實現多語言的一種常見方法是使用資源字典和數據綁定。以下是一個詳細的步驟和示例源代 ...
  • 描述(做一個簡單的記錄): 事件(event)的本質是一個委托;(聲明一個事件: public event TestDelegate eventTest;) 委托(delegate)可以理解為一個符合某種簽名的方法類型;比如:TestDelegate委托的返回數據類型為string,參數為 int和 ...
  • 1、AOT適合場景 Aot適合工具類型的項目使用,優點禁止反編 ,第一次啟動快,業務型項目或者反射多的項目不適合用AOT AOT更新記錄: 實實在在經過實踐的AOT ORM 5.1.4.117 +支持AOT 5.1.4.123 +支持CodeFirst和非同步方法 5.1.4.129-preview1 ...
  • 總說周知,UWP 是運行在沙盒裡面的,所有許可權都有嚴格限制,和沙盒外交互也需要特殊的通道,所以從根本杜絕了 UWP 毒瘤的存在。但是實際上 UWP 只是一個應用模型,本身是沒有什麼許可權管理的,許可權管理全靠 App Container 沙盒控制,如果我們脫離了這個沙盒,UWP 就會放飛自我了。那麼有沒... ...
  • 目錄條款17:讓介面容易被正確使用,不易被誤用(Make interfaces easy to use correctly and hard to use incorrectly)限制類型和值規定能做和不能做的事提供行為一致的介面條款19:設計class猶如設計type(Treat class de ...
  • title: 從零開始:Django項目的創建與配置指南 date: 2024/5/2 18:29:33 updated: 2024/5/2 18:29:33 categories: 後端開發 tags: Django WebDev Python ORM Security Deployment Op ...
  • 1、BOM對象 BOM:Broswer object model,即瀏覽器提供我們開發者在javascript用於操作瀏覽器的對象。 1.1、window對象 視窗方法 // BOM Browser object model 瀏覽器對象模型 // js中最大的一個對象.整個瀏覽器視窗出現的所有東西都 ...