[Redis源碼閱讀]實現一個redis命令--nonzerodecr

上篇文章介紹了命令的執行流程,對redis如何執行命令也有了初步的瞭解,經過實現一個redis命令來再次加深印象。redis

筆者平時主要語言是PHP,有些功能PHP沒法知足就會用到PHP的擴展,好比swoole。所以,就想到redis可不能夠以作擴展?爲了知足一些特殊的需求,可不能夠爲redis開發一個命令?shell

前期準備

由於redis是用C開發的,爲了能開發redis命令,首先也是必須的是,你要懂一點C語言基礎,另外一個就是,須要瞭解一下redis命令是如何執行的,知道redis執行命令大概的流程,最簡單的一個流程描述就是:swoole

讀取命令->解析命令->調用命令函數->返回執行結果
複製代碼

或者再讀一次上篇文章函數

咱們要作的就是,確保redis能解析到新增的命令,能根據輸入的命令找到對應的方法並執行。測試

函數需求

要實現一個命令,說明當前redis的命令沒法知足開發的需求。考慮這樣的一個需求,在秒殺的情景下,達到了這樣的case:商品剩下最後一件,兩個用戶同時搶購,使用業務代碼:lua

if (redis->decr(key) >= 0) {
        return success
}
複製代碼

這樣作有個問題就是活動結束後,key的值可能爲-1,這樣對於最終查詢庫存時會出現負值,不利於數據對帳及統計。那麼,能不能新增一個命令,讓redis在計算時判斷key的值,若是是0就不進行扣減呢?spa

將函數命名爲nonzerodecr,開始實現。code

添加函數名到命令表

把函數名稱添加到命令表,參照decr命令的命令表:cdn

{"decr",decrCommand,2,"wmF",0,NULL,1,1,1,0,0}
複製代碼

增長nonzerodecrCommand:server

{"nonzerodecr",nonzerodecrCommand,2,"wmF",0,NULL,1,1,1,0,0},
複製代碼

聲明nonzerodecr,由於新增的命令nonzerodecr只是內部增長一個非0的判斷,其他操做沒有變化,所以只須要跟decrCommand同樣的聲明便可:

void nonzerodecrCommand(client *c);
複製代碼

實現函數

在實現新的函數以前,先看看decrComamnd命令的實現:

void decrCommand(client *c) {
    incrDecrCommand(c, -1);
}
複製代碼

函數調用了incrDecrCommand實現自增和自減,實現以下:

/* * incr、decr具體的實現 */

void incrDecrCommand(client *c, long long incr) {
  long long value, oldvalue;
  robj *o, *new;

  // 檢查key和value的類型
  o = lookupKeyWrite(c->db,c->argv[1]);
  if (o != NULL && checkType(c,o,OBJ_STRING)) return;
  if (getLongLongFromObjectOrReply(c,o,&value,NULL) != C_OK) return;

  // 處理執行後溢出的狀況
  oldvalue = value;
  if ((incr < 0 && oldvalue < 0 && incr < (LLONG_MIN-oldvalue)) ||
      (incr > 0 && oldvalue > 0 && incr > (LLONG_MAX-oldvalue))) {
      addReplyError(c,"increment or decrement would overflow");
      return;
  }

  value += incr;
  /* * 在long範圍內,直接賦值,不然使用longlong建立字符串後再賦值 */
  if (o && o->refcount == 1 && o->encoding == OBJ_ENCODING_INT &&
    (value < 0 || value >= OBJ_SHARED_INTEGERS) &&
    value >= LONG_MIN && value <= LONG_MAX) {
    new = o;
    o->ptr = (void*)((long)value);
  } else {
    new = createStringObjectFromLongLong(value);
    if (o) {
        dbOverwrite(c->db,c->argv[1],new);
    } else {
        dbAdd(c->db,c->argv[1],new);
    }
  }
  signalModifiedKey(c->db,c->argv[1]);
  notifyKeyspaceEvent(NOTIFY_STRING,"incrby",c->argv[1],c->db->id);
  server.dirty++;
  addReply(c,shared.colon);
  addReply(c,new);
  addReply(c,shared.crlf);
}

複製代碼

函數的流程圖以下:

incrDecrCommand

如圖所示,要實現函數nonzerodecrCommand,只須要在進行增/減操做前增長一個大於等於0 的判斷便可,其他的邏輯不變,實現以下:

void nonzerodecrCommand(client *c) {
    long long incr = -1;
    long long value, oldvalue;
    robj *o, *new;
    // 檢查key和value的類型
    o = lookupKeyWrite(c->db,c->argv[1]);
    if (o != NULL && checkType(c,o,OBJ_STRING)) return;
    if (getLongLongFromObjectOrReply(c,o,&value,NULL) != C_OK) return;
    // 處理執行後溢出的狀況
    oldvalue = value;
    if (incr < 0 && oldvalue < 0 && incr < (LLONG_MIN-oldvalue)) {
        addReplyError(c,"increment or decrement would overflow");
        return;
    }
    value += incr;
    // 判斷,若是操做後結果小於0,直接返回
    if (value < 0) {
        addReply(c,shared.czero);
        return;
    }
    
    if (o && o->refcount == 1 && o->encoding == OBJ_ENCODING_INT &&
      (value < 0 || value >= OBJ_SHARED_INTEGERS) &&
      value >= LONG_MIN && value <= LONG_MAX) {
      new = o;
      o->ptr = (void*)((long)value);
    } else {
      new = createStringObjectFromLongLong(value);
      if (o) {
          dbOverwrite(c->db,c->argv[1],new);
      } else {
          dbAdd(c->db,c->argv[1],new);
      }
    }
    signalModifiedKey(c->db,c->argv[1]);
    notifyKeyspaceEvent(NOTIFY_STRING,"incrby",c->argv[1],c->db->id);
    server.dirty++;
    addReply(c,shared.colon);
    addReply(c,new);
    addReply(c,shared.crlf);
}
複製代碼

編譯測試

編寫完代碼後,對代碼進行編譯測試:

127.0.0.1:6379> set totalCount 1
OK
127.0.0.1:6379> get totalCount
"1"
127.0.0.1:6379> nonzerodecr totalCount
(integer) 0
127.0.0.1:6379> get totalCount
"0"
127.0.0.1:6379> nonzerodecr totalCount
(integer) 0
127.0.0.1:6379> get totalCount
"0"
複製代碼

結果符合最初的需求,在值等於0以後,再進行扣減,值不會變爲負數。

總結與思考

經過介紹實現nonzerodecr命令的過程,對如何實現一個命令有了一個初步的認識,以後若是有新的需求也能夠根據這個步驟去實現一個新的命令。

上面介紹的命令實現方式比較粗暴,可能會有隱藏的bug,但對於入門實現一個命令這個目的來講,這個代碼時能夠的,另外,一開始提到的秒殺場景除了能夠使用新命令來解決,也能夠使用redis-lua腳本的形式來實現,實現方法是多樣的,具體的技術選型須要根據業務的場景來選擇,若是你有更好的方案,歡迎評論留下你的方案。

原創文章,文筆有限,才疏學淺,文中如有不正之處,萬望告知。

若是本文對你有幫助,請點個贊吧,謝謝^_^

更多精彩內容,請關注我的公衆號。

相關文章
相關標籤/搜索