自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

詳解 mini-redis 復(fù)刻 Redis 的 I NCR 指令

數(shù)據(jù)庫 Redis
本文將針對字符串操作中介紹筆者近期所復(fù)刻的鍵值自增指令的落地思路,以幫助讀者更好的理解和學(xué)習(xí)mini-redis。

因為近期比較忙碌,所以對于mini-redis的復(fù)刻基本處于一些指令向的完善,而本文將針對字符串操作中介紹筆者近期所復(fù)刻的鍵值自增指令的落地思路,以幫助讀者更好的理解和學(xué)習(xí)mini-redis。

對象類型前置校驗

因為指令是基于字符串操作的,所以在執(zhí)行INCR或者DECR之前我們都必須針對入?yún)⒌逆I值對進(jìn)行校驗,所以對于以下情況,我們都必須采用fail-fast的方式提前將失敗暴露,將鍵值對已存在,對應(yīng)的值非字符串類型(例如:字典類型),直接響應(yīng)錯誤:

基于上述的基本概念,我們給出落地的代碼,即位于command.go的incrDecrCommand方法,可以看到我們會優(yōu)先到redis內(nèi)存中查看是否存在對應(yīng)的key,如果存在則進(jìn)行必要的類型判斷,如果非字符串類型即REDIS_STRING則直接響應(yīng)錯誤出去,并直接返回:

func incrDecrCommand(c *redisClient, incr int64) {
 var value int64
 var oldValue int64
 var newObj *robj
 //查看鍵值對是否存在
 o := lookupKeyWrite(c.db, c.argv[1])
 //如果鍵值對存在且類型非字符串類型,直接響應(yīng)錯誤并返回
 if o != nil && checkType(c, o, REDIS_STRING) {
  return
 }
 
 
 //......

}

對此我們也給出checkType的內(nèi)部邏輯,可以看到當(dāng)比對類型不一致時會直接輸出錯誤并返回true,讀者可以參考注釋了解:

func checkType(c *redisClient, o *robj, rType int) bool {
 //如果類型不一致,則輸出-WRONGTYPE Operation against a key holding the wrong kind of value
 if o.robjType != rType {
  addReply(c, shared.wrongtypeerr)
  return true
 }
 return false
}

其實筆者這里也想吐槽一句redis對于函數(shù)設(shè)計的語義的不恰當(dāng)性,理論性合理的函數(shù)進(jìn)行校驗時正確的做法應(yīng)該是:

  • 邏輯校驗失敗,輸出錯誤返回false。
  • 邏輯校驗正確,返回true。

也只能說因為某些歷史原因,或者設(shè)計者有著自己的主觀編碼習(xí)慣吧,本著一比一的復(fù)刻理念,筆者也沿襲了這樣的編碼思路。

基于數(shù)值池高效完成字符串轉(zhuǎn)換

針對字符串類型(可以轉(zhuǎn)數(shù)值的情況下,它也會轉(zhuǎn)數(shù)值類型),我們都是通過robj類型創(chuàng)建和維護(hù),因為我們本次所復(fù)刻的incr和decr所操作的類型是字符串中可轉(zhuǎn)為數(shù)值的對象,所以本著數(shù)值類型有跡可循的規(guī)律以及空間換時間的思想,我們提出池化思想,即將0-9999數(shù)值緩存一份數(shù)值池,后續(xù)的增減操作后處于該范圍的數(shù)值都可以直接使用數(shù)值池里對應(yīng)的robj對象,以節(jié)約robj對象創(chuàng)建的開銷和非必要的內(nèi)存資源占用:

所以筆者在main.go中聲明sharedObjectsStruct 這個結(jié)構(gòu)體中聲明了一個integers維護(hù)常量池的robj對象:

type sharedObjectsStruct struct {
 //......
 integers       [REDIS_SHARED_INTEGERS]*robj //通用0~9999常量數(shù)值池
 //......
}

然后在createSharedObjects方法中完成初始化,后續(xù)就可以直接使用了:

func createSharedObjects() {
 //......

 var i int64
 //初始化常量池對象
 for i = 0; i < REDIS_SHARED_INTEGERS; i++ {
  //基于接口封裝數(shù)值
  num := interface{}(i)
  //生成string對象
  shared.integers[i] = createObject(REDIS_STRING, &num)
  //聲明編碼類型為int
  shared.integers[i].encoding = REDIS_ENCODING_INT
 }

 //......
}

于是我們就得出了后續(xù)的編碼邏輯:

  • 將value強轉(zhuǎn)為數(shù)值判斷是否超出范圍,如果超了則拋出異常。反之進(jìn)入步驟2。
  • 查看取值范圍是否大于10000,如果是則自己生成robj對象,反之采用池化數(shù)值池的robj。
  • 基于1、2生成的數(shù)值對象將鍵值對更新或者覆蓋到內(nèi)存數(shù)據(jù)庫中。
/**
 針對字符串類型的值進(jìn)行如下判斷的和轉(zhuǎn)換:
 1. 如果為空,說明本次的key不存在,直接初始化一個空字符串,后續(xù)會直接初始化一個0值使用
 2. 如果是字符串類型,則轉(zhuǎn)為字符串類型
 3. 如果是數(shù)值類型,則先轉(zhuǎn)為字符串類型進(jìn)行后續(xù)的通用數(shù)值轉(zhuǎn)換操作保證一致性
 */
 var s string
 if o == nil {
  s = ""
 } else if isString(*o.ptr) {
  s = (*o.ptr).(string)
 } else {
  s = strconv.FormatInt((*o.ptr).(int64), 10)
 }
 //進(jìn)行類型強轉(zhuǎn)為數(shù)值,如果失敗,直接輸出錯誤并返回
 if getLongLongFromObjectOrReply(c, s, &value, nil) != REDIS_OK {
  return
 }

 oldValue = value
 //如果累加超范圍則報錯
 if (incr < 0 && oldValue < 0 && incr < (math.MinInt64-oldValue)) ||
  (incr > 0 && oldValue > 0 && incr > (math.MaxInt64-oldValue)) {
  errReply := "increment or decrement would overflow"
  addReplyError(c, &errReply)
  return
 }
 //基于incr累加的值生成value
 value += incr
 //如果超常量池范圍則封裝一個對象使用 
 if o != nil &&
  (value < 0 || value >= REDIS_SHARED_INTEGERS) &&
  (value > math.MinInt64 || value < math.MaxInt64) {
  newObj = o

  i := interface{}(value)
  o.ptr = &i
 } else if o != nil {//如果對象存在,且累加結(jié)果沒超范圍則調(diào)用createStringObjectFromLongLong獲取常量對象
  newObj = createStringObjectFromLongLong(value)
  //將寫入結(jié)果覆蓋
  dbOverwrite(c.db, c.argv[1], newObj)
 } else {//從常量池獲取數(shù)值,然后添加鍵值對到數(shù)據(jù)庫中
  newObj = createStringObjectFromLongLong(value)
  dbAdd(c.db, c.argv[1], newObj)
 }

通用結(jié)果響應(yīng)

完成上述操作后就是將結(jié)果按照RESP協(xié)議規(guī)范將結(jié)果響應(yīng)給客戶端,按照協(xié)議要求數(shù)值類型必須用:號開頭,所以假設(shè)我們累加結(jié)果為10,那么響應(yīng)給客戶端的結(jié)果就是10\r\n。

對應(yīng)我們的給出最后的代碼段:

//將累加后的結(jié)果返回給客戶端,按照RESP格式即 :數(shù)值\r\n,例如返回10 那么格式就是:10\r\n
 reply := *shared.colon + strconv.FormatInt(value, 10) + *shared.crlf
 addReply(c, &reply)

完整的代碼實現(xiàn)

我們來小結(jié)一下上述的實現(xiàn)思路:

  • 鍵值對查詢與校驗。
  • 數(shù)值類型轉(zhuǎn)換與越界判斷。
  • 字符串類型強轉(zhuǎn)并基于取值范圍查看是否通過數(shù)值池獲取。
  • 更新或覆蓋鍵值對。
  • 將操作結(jié)果返回客戶端。

完整代碼如下:

func incrDecrCommand(c *redisClient, incr int64) {
 var value int64
 var oldValue int64
 var newObj *robj
 //查看鍵值對是否存在
 o := lookupKeyWrite(c.db, c.argv[1])
 //如果鍵值對存在且類型非字符串類型,直接響應(yīng)錯誤并返回
 if o != nil && checkType(c, o, REDIS_STRING) {
  return
 }
 /**
 針對字符串類型的值進(jìn)行如下判斷的和轉(zhuǎn)換:
 1. 如果為空,說明本次的key不存在,直接初始化一個空字符串,后續(xù)會直接初始化一個0值使用
 2. 如果是字符串類型,則轉(zhuǎn)為字符串類型
 3. 如果是數(shù)值類型,則先轉(zhuǎn)為字符串類型進(jìn)行后續(xù)的通用數(shù)值轉(zhuǎn)換操作保證一致性
 */
 var s string
 if o == nil {
  s = ""
 } else if isString(*o.ptr) {
  s = (*o.ptr).(string)
 } else {
  s = strconv.FormatInt((*o.ptr).(int64), 10)
 }
 //進(jìn)行類型強轉(zhuǎn)為數(shù)值,如果失敗,直接輸出錯誤并返回
 if getLongLongFromObjectOrReply(c, s, &value, nil) != REDIS_OK {
  return
 }

 oldValue = value

 if (incr < 0 && oldValue < 0 && incr < (math.MinInt64-oldValue)) ||
  (incr > 0 && oldValue > 0 && incr > (math.MaxInt64-oldValue)) {
  errReply := "increment or decrement would overflow"
  addReplyError(c, &errReply)
  return
 }
 //基于incr累加的值生成value
 value += incr
 //如果超常量池范圍則封裝一個對象使用
 if o != nil &&
  (value < 0 || value >= REDIS_SHARED_INTEGERS) &&
  (value > math.MinInt64 || value < math.MaxInt64) {
  newObj = o

  i := interface{}(value)
  o.ptr = &i
 } else if o != nil { //如果對象存在,且累加結(jié)果沒超范圍則調(diào)用createStringObjectFromLongLong獲取常量對象
  newObj = createStringObjectFromLongLong(value)
  //將寫入結(jié)果覆蓋
  dbOverwrite(c.db, c.argv[1], newObj)
 } else { //從常量池獲取數(shù)值,然后添加鍵值對到數(shù)據(jù)庫中
  newObj = createStringObjectFromLongLong(value)
  dbAdd(c.db, c.argv[1], newObj)
 }
 //將累加后的結(jié)果返回給客戶端,按照RESP格式即 :數(shù)值\r\n,例如返回10 那么格式就是:10\r\n
 reply := *shared.colon + strconv.FormatInt(value, 10) + *shared.crlf
 addReply(c, &reply)

}

遞增遞減的復(fù)用

基于上述函數(shù)對應(yīng)的遞增指令I(lǐng)NCR就使用incrCommand,入?yún)?代表加1,而decrCommand則傳-1扣減即可:

func incrCommand(c *redisClient) {
 //累加1
 incrDecrCommand(c, 1)
}

func decrCommand(c *redisClient) {
 //遞減1
 incrDecrCommand(c, -1)
}

最終效果演示

最后,我們將服務(wù)啟動進(jìn)行測試,可以看到指令正常執(zhí)行:

127.0.0.1:6379> incr k1
(integer) 1
(4.50s)
127.0.0.1:6379> incr k1
(integer) 2
127.0.0.1:6379> incr k1
(integer) 3
127.0.0.1:6379> incr k1
(integer) 4
127.0.0.1:6379> incr k1
(integer) 5
127.0.0.1:6379> incr k1
(integer) 6
127.0.0.1:6379> decr k1
(integer) 5
127.0.0.1:6379> decr k1
(integer) 4
127.0.0.1:6379> decr k1
(integer) 3
127.0.0.1:6379> decr k1
(integer) 2
127.0.0.1:6379> decr k1
(integer) 1
127.0.0.1:6379> decr k1
(integer) 0
127.0.0.1:6379> decr k1
(integer) -1
127.0.0.1:6379>


責(zé)任編輯:趙寧寧 來源: 寫代碼的SharkChili
相關(guān)推薦

2024-11-22 15:00:00

開源Redis鏈表

2018-08-15 09:48:27

數(shù)據(jù)庫Redis應(yīng)用場景

2023-07-03 07:55:25

2024-04-18 08:00:00

2024-07-16 08:38:06

2024-04-18 00:20:56

Redis策略數(shù)據(jù)

2019-08-06 19:36:25

RedisMemcached緩存

2025-01-15 08:19:12

SpringBootRedis開源

2024-07-31 08:33:17

2024-11-11 17:12:22

2011-10-25 10:36:19

蘋果臺式機

2018-04-27 09:03:57

Redis數(shù)據(jù)存儲

2024-10-08 10:13:17

2024-12-09 00:00:09

2011-11-07 09:42:58

蘋果臺式機

2024-09-26 06:30:36

2018-11-06 10:51:07

Redis開發(fā)存儲系統(tǒng)

2011-10-27 15:04:19

蘋果臺式機

2020-01-15 14:51:04

Redis5.0數(shù)據(jù)策略

2020-06-29 07:44:36

Redis
點贊
收藏

51CTO技術(shù)棧公眾號