iT邦幫忙

1

Redis 學習筆記(4)-Redis 指令

  • 分享至 

  • xImage
  •  

Redis 學習筆記(4)-Redis 指令

本文是有關 Redis 的學習筆記的一部分,相關目錄請參考 Redis 學習筆記(1)-簡介

在上一篇的學習中,已啟動 Redis 容器,接下來要使用該 Redis 容器學習 Redis 指令。

下列為指令驗證的項目:

  • String
  • Hash
  • List
  • Set
  • Sorted Set
  • MULTI
  • Function
  • Lua 腳本

1. String 型別指令

為最基本的型別,其簡易的操作(查/寫/讀/刪) 如下:

操作 指令 範例
查找 Key KEYS pattern keys *
寫入 Key SET key value set key k1 v1
讀出 Key GET key get k1
刪除 Key DEL key del k1

除了基本的操作外,模擬了三個想像的業務需求,使用 Redis 的功能來達成指定的需求。

  • 流水號產生
  • 即時資源使用量
  • 流量控管

下列則是這三個業務需求的實作。

1.1 流水號產生

  • 需求: 產出訂單流水號,不可重複,由 1001 開始

  • 相關指令:

操作 指令 範例
遞增 Key INCR key incr orderId:generator
  • 實作指令:
指令操作 說明
set orderId:generator 1000 設定初始值
incr orderId:generator 每次呼叫遞增後回傳
incr orderId:generator 每次呼叫遞增後回傳

1.2 即時資源使用量

  • 需求: 統計系統當下的連接量,可作為是否熔斷的依據

  • 相關指令:

操作 指令 範例
遞減 Key DECR key decr session:current:count
  • 實作指令:
指令操作 說明
incr session:current:count 每次連入總數加1
decr session:current:count 每次斷線總數減1
get session:current:count 取得當下連入數

1.3 流量控管

  • 需求: 針對每一用戶在單位時間內僅能進行額度內操作,超過額度則禁止該操作

  • 相關指令:

操作 指令 範例
確認 Key 存在 EXISTS key exists sessCtrl:sessId:101:count
設定 Key 值和效期 SETEX key seconds value setex sessCtrl:sessId:101:count 60 1
讀取 Key 效期 TTL key ttl sessCtrl:sessId:101:count
當 Key 不存在時寫入 SETNX key value setnx sessCtrl:sessId:101:count 1
設定 Key 效期 EXPIRE key seconds expire sessCtrl:sessId:101:count 60
  • 實作一指令:
指令操作 說明
exists sessCtrl:sessId:101:count 檢查該用戶計數是否存在
incr sessCtrl:sessId:101:count 若存在則直接加1
setex sessCtrl:sessId:101:count 60 1 若不存在則設定值和效期
get sessCtrl:sessId:101:count 檢視計數是否超額
ttl sessCtrl:sessId:101:count 檢視效期
  • 實作二指令:
指令操作 說明
setnx sessCtrl:sessId:101:count 1 試寫入 Key (不存在Key才寫入)
expire sessCtrl:sessId:101:count 60 若成功則設定效期
incr sessCtrl:sessId:101:count 若失敗則直接加1
get sessCtrl:sessId:101:count 檢視計數是否超額
ttl sessCtrl:sessId:101:count 檢視效期

2. Hash 型別指令

若一個鍵內包含不是單一的值,而是多個欄位,則可以使用雜湊(Hash)。例如,訂單資料內有多個欄位(訂單編號、客戶編號、...)。

模擬了二個想像的業務需求,使用 Redis 的功能來達成指定的需求。

  • 物件資料
  • 額度控管

下列則是這二個業務需求的實作。

2.1 物件資料

  • 需求: 將一個客戶資料集成在一個 Key 內

  • 相關指令:

操作 指令 範例
設定 Key 欄位 HGET key field hset customer:custId:101 custId 101
設定 Key 多欄位 HMSET key field1 value1 [field2 value2 ] hset customer:custId:101 name Bob sex M
讀出 Key 欄位 HGET key field hget customer:custId:101 name
讀出 Key 全欄位 HGETALL key hgetall customer:custId:101
  • 實作指令:
指令操作 說明
hset customer:custId:101 custId 101 設定 Key 和一個欄位
hset customer:custId:101 name Bob sex M 設定 Key 和多個欄位
hgetall customer:custId:101 檢視所有欄位
hget customer:custId:101 name 檢視一個欄位

2.2 額度控管

  • 需求: 針對每個客戶給定帳本,可儲值和扣款,額度足夠時才可執行指定的操作

  • 相關指令:

操作 指令 範例
對欄位作增減 HINCRBY key field increment hincrby customer:custId:101 points -30
  • 實作指令:
指令操作 說明
hincrby customer:custId:101 points -30 對帳本扣款30,若回傳負值則表餘額不足
hincrby customer:custId:101 points 30 若餘額不足,作補償操作
hincrby customer:custId:101 points 100 執行儲值 100
hincrby customer:custId:101 points -30 對帳本扣款30,若回傳正值則表餘額足夠
hgetall customer:custId:101 檢視所有欄位

3. List 型別指令

列表添加元素時,可由表頭添加、表尾添加或是插入指定位置。彈出列元素時,可由表頭彈出也可由表尾彈出。可以使用這些功能模擬 Queue(FIFO) 或是 Stack (LIFO)。
當有多路資料滙總,依序由特定程序處理時,可使用由 List 模擬的 Queue 來完成。

模擬了一個想像的業務需求,使用 Redis 的功能來達成指定的需求。

  • 任務待處理佇列

下列則是這一個業務需求的實作。

3.1 任務待處理佇列

  • 需求: 多點可請求發送簡訊,只需將簡訊請求推入 Queue 即可,簡訊發送程式(一個或多個)可由 Queue 取出請求並執行發送。

  • 相關指令:

操作 指令 範例
將元素由右側(尾)推入 RPUSH key value1 [value2] rpush task:reqQue "mdn:123,to:456,msg:msg01"
將元素由左側(頭)取出 RPOP key lpop task:reqQue
以阻塞模式,將元素由左側(頭)取出 BLPOP key1 [key2 ] timeout blpop task:reqQue 30
  • 實作指令:
指令操作 說明
rpush task:reqQue "mdn:123,to:456,msg:msg01" 將請求推入 Queue 中
rpush task:reqQue "mdn:123,to:456,msg:msg02" 將請求推入 Queue 中
rpush task:reqQue "mdn:123,to:456,msg:msg03" 將請求推入 Queue 中
lpop task:reqQue 以非阻塞方式取出元表
blpop task:reqQue 30 以阻塞方式取出元表

4. Set 型別指令

集合內的元素不可重覆,故可用來作去重功能。集合可以差集、交集、聯集,可用來達成共同朋友功能,或黑名單功能。

模擬了一個想像的業務需求,使用 Redis 的功能來達成指定的需求。

  • 每日使用者訪問數

下列則是這一個業務需求的實作。

4.1 每日使用者訪問數

  • 需求: 同一使用者每日訪問可能多次,要求統計每日不同使用者訪問總數

  • 相關指令:

操作 指令 範例
將元素加入 SADD key member1 [member2] sadd visit:20220101 userId:101
查元素總數 SCARD key scard visit:20220101
查全部元素 SMEMBERS key smembers visit:20220101
檢查是否為集合元素 SISMEMBER key member sismember visit:20220101 userId:101
二集合交集 SINTER key1 [key2] sinter visit:20220101 visit:20220102
二集合聯集並儲存結果 SUNIONSTORE destination key1 [key2] sunionstore visit:tmp01 visit:20220101 visit:20220102
  • 實作指令:
指令操作 說明
sadd visit:20220101 userId:101 記錄 1/1 userId:101 訪問
sadd visit:20220101 userId:102 記錄 1/1 userId:102 訪問
sadd visit:20220101 userId:101 記錄 1/1 userId:101 訪問
sadd visit:20220101 userId:101 記錄 1/1 userId:101 訪問
scard visit:20220101 查詢 1/1 訪問的使用者數目
smembers visit:20220101 查詢 1/1 訪問的使用者
sismember visit:20220101 userId:103 查詢 userId:103 在 1/1 是否訪問
sadd visit:20220102 userId:101 記錄 1/2 userId:101 訪問
sadd visit:20220102 userId:103 記錄 1/2 userId:103 訪問
sinter visit:20220101 visit:20220102 1/1 & 1/2 皆有訪問的使用者
sunionstore visit:tmp01 visit:20220101 visit:20220102 1/1 & 1/2 皆有訪問的使用者
expire visit:tmp01 60 對暫時的 Key 設效期
scard visit:tmp01 查詢 1/1 ~ 1/2 訪問的使用者數目

5. Sorted Set 型別指令

集合內的元素不可重覆,但每個元素可以有分數,元素會依分數由小到大在集合內排序。該特性可以用來實作排行榜、時效任務管理和權重任務管理。

模擬了一個想像的業務需求,使用 Redis 的功能來達成指定的需求。

  • 活躍使用者排行榜

下列則是這一個業務需求的實作。

5.1 活躍使用者排行榜

  • 需求: 依使用者的訪問次數,列出前三名,榜單10分鐘更新。
  • 相關指令:
操作 指令 範例
對元素分數作遞增 ZINCRBY key increment member zincrby visit:active 1 userId:101
以大到小排序,返回索引區間 ZREVRANGE key start stop [WITHSCORES] zrevrange visit:active 0 100 withscores
  • 實作指令:
指令操作 說明
zincrby visit:active 1 userId:101 記錄 userId:101 訪問
zincrby visit:active 1 userId:102 記錄 userId:102 訪問
zincrby visit:active 1 userId:103 記錄 userId:103 訪問
zincrby visit:active 1 userId:104 記錄 userId:104 訪問
zrevrange visit:active 0 2 返回由大到小前三名
zrevrange visit:active 0 2 withscores 返回由大到小前三名(含分數)

6. MULTI 型別指令

MULTI 可以將數個指令先上傳到伺服器上的隊列中,此時尚未執行,待客戶端發送 EXEC 指令時,伺服器上隊列內的指令會一起執行。此時的執行也保證原子性操作。

7 Function 指令

Redis 7 以後支援 functions,在 Redis 7 以前是使用 LUA Script。相比 LUA Script 使用 function 更符合開發者習慣性。
使用 function 可將數個 redis 指令整合成為一個 redis function,執行時該 function 在 redis servier 端執行,不但整個 function 為一個原子性操作,所有的資料也由本地端讀取不經由網路。

模擬了一個想像的業務需求,使用 Redis 的功能來達成指定的需求。

  • 秒殺功能

下列則是這一個業務需求的實作。

7.1 秒殺功能

  • 需求: 有產品三件,讓大量客戶端搶購,僅有最早三個請求可以成功。

  • 實作指令(建立 function): 進入 Redis 容器後,在 Shell 中執行下列指令

    cat << EOF | redis-cli -a 'mypwd' -x FUNCTION LOAD REPLACE
    #!lua name=mylib  
    local function flash_sale_top3(keys, args)
      local myZset = keys[1] -- 取得 KEY 名稱
      local myZval = args[1] -- 取得 VALUE
      local keyType = redis.call('type', myZset) -- 取得 KEY 的型別
      keyType = keyType.ok or keyType -- 由 TABLE 取出結果
      -- redis.log(redis.LOG_NOTICE, 'KeyType:'..keyType) -- 在日誌中印出
      -- 若 KEY 存在但不為 ZSET 則回應錯誤
      if keyType ~= 'none' and keyType ~= 'zset' then 
        return redis.error_reply('輸入的 KEY 應為 zset 或不存在')
      end
      -- 若 VALUE 不存在 則回應錯誤
      if myZval == nil then
        return redis.error_reply('未輸入 '..myZset..' 對應的值')
      end
      -- 若 VALUE 已加入 KEY 中,則回應重覆
      local myScore = redis.call('zscore',myZset,myZval)
      if myScore then
        return redis.status_reply('duplicated-'..myScore)
      end
      -- 若 KEY 內的數目已達 3 則回應無額度
      local count = redis.call('zcard', myZset)
      if count >= 3 then
        return redis.status_reply('no quota')
      end
      -- 將 VALUE 加入 KEY 中,並回應其排名
      redis.call('zadd',myZset,count+1, myZval)
      -- return redis.status_reply(tostring(count+1))
      return redis.status_reply('success-'..(count+1))
    end
    
    redis.register_function('flash_sale_top3', flash_sale_top3)
    EOF
    
    
  • 實作指令(執行 function):

指令操作 說明
fcall flash_sale_top3 1 myZset user01 模擬 user01 搶購,成功
fcall flash_sale_top3 1 myZset user02 模擬 user02 搶購,成功
fcall flash_sale_top3 1 myZset user03 模擬 user03 搶購,成功
fcall flash_sale_top3 1 myZset user04 模擬 user04 搶購,失敗
fcall flash_sale_top3 1 myZset user05 模擬 user05 搶購,失敗
fcall flash_sale_top3 1 myZset user02 模擬 user02 重覆搶購,失敗

8. Lua 腳本

若無法使用 Function 功能的情況下,也可以使用 LUA 腳本。LUA 腳本有二種使用方式如下:

  • 使用 EVAL 指令: 將腳本內容上傳,並直接執行
  • 使用 SCRIPT LOAD 指令將腳本內容上傳,使用 EVALSHA 執行腳本

模擬了一個想像的業務需求,使用 Redis 的功能來達成指定的需求。

  • 秒殺功能

下列則是這一個業務需求分別使用 EVAL 和 SCRIPT LOAD 的實作。

8.1 使用 EVAL 方式實作

  • 需求: 有產品三件,讓大量客戶端搶購,僅有最早三個請求可以成功。

  • 實作指令檔

    cat << EOF > myfunc.lua
      local myZset = KEYS[1] -- 取得 KEY 名稱
      local myZval = ARGV[1] -- 取得 VALUE
      local keyType = redis.call('type', myZset) -- 取得 KEY 的型別
      keyType = keyType.ok or keyType -- 由 TABLE 取出結果
      -- redis.log(redis.LOG_NOTICE, 'KeyType:'..keyType..',myZset:'..myZset) -- 在日誌中印出
      -- 若 KEY 存在但不為 ZSET 則回應錯誤
      if keyType ~= 'none' and keyType ~= 'zset' then 
        error( '輸入的 KEY 應為 zset 或不存在' )
      end
      -- 若 VALUE 不存在 則回應錯誤
      if myZval == nil then
        error('未輸入 '..myZset..' 對應的值')
      end
      -- 若 VALUE 已加入 KEY 中,則回應重覆
      local myScore = redis.call('zscore',myZset,myZval)
      if myScore then
        return 'duplicated-'..myScore
      end
      -- 若 KEY 內的數目已達 3 則回應無額度
      local count = redis.call('zcard', myZset)
      if count >= 3 then
        return 'no quota'
      end
      -- 將 VALUE 加入 KEY 中,並回應其排名
      redis.call('zadd',myZset,count+1, myZval)
      return 'success-'..(count+1)
    EOF
    
    
  • 執行指令:

    ## 第一次搶購
    redis-cli -a 'mypwd' --eval myfunc.lua k1 , v1
    
    ## 第二次搶購
    redis-cli -a 'mypwd' --eval myfunc.lua k1 , v2
    
    ## 第三次搶購
    redis-cli -a 'mypwd' --eval myfunc.lua k1 , v3
    
    ## 第四次搶購
    redis-cli -a 'mypwd' --eval myfunc.lua k1 , v4
    
    

8.2 使用 SCRIPT LOAD 方式實作

  • 需求: 有產品三件,讓大量客戶端搶購,僅有最早三個請求可以成功。

  • 實作指令檔

    cat << EOF | redis-cli -a 'mypwd' -x script load
      local myZset = KEYS[1] -- 取得 KEY 名稱
      local myZval = ARGV[1] -- 取得 VALUE
      local keyType = redis.call('type', myZset) -- 取得 KEY 的型別
      keyType = keyType.ok or keyType -- 由 TABLE 取出結果
      -- redis.log(redis.LOG_NOTICE, 'KeyType:'..keyType..',myZset:'..myZset) -- 在日誌中印出
      -- 若 KEY 存在但不為 ZSET 則回應錯誤
      if keyType ~= 'none' and keyType ~= 'zset' then 
        error( '輸入的 KEY 應為 zset 或不存在' )
      end
      -- 若 VALUE 不存在 則回應錯誤
      if myZval == nil then
        error('未輸入 '..myZset..' 對應的值')
      end
      -- 若 VALUE 已加入 KEY 中,則回應重覆
      local myScore = redis.call('zscore',myZset,myZval)
      if myScore then
        return 'duplicated-'..myScore
      end
      -- 若 KEY 內的數目已達 3 則回應無額度
      local count = redis.call('zcard', myZset)
      if count >= 3 then
        return 'no quota'
      end
      -- 將 VALUE 加入 KEY 中,並回應其排名
      redis.call('zadd',myZset,count+1, myZval)
      return 'success-'..(count+1)
    EOF
    
    

    執行成功後會得到一個識別字串(如:28a7bad2bba6b34a220eaaf3cd6da48d4be22bd9),可用該字串來指稱上傳的 SCRIPT

  • 執行指令:

    ## 第一次搶購
    evalsha 28a7bad2bba6b34a220eaaf3cd6da48d4be22bd9 1 k2 v1
    
    ## 第二次搶購
    evalsha 28a7bad2bba6b34a220eaaf3cd6da48d4be22bd9 1 k2 v2
    
    ## 第三次搶購
    evalsha 28a7bad2bba6b34a220eaaf3cd6da48d4be22bd9 1 k2 v3
    
    ## 第四次搶購
    evalsha 28a7bad2bba6b34a220eaaf3cd6da48d4be22bd9 1 k2 v4
    
    

9. 結論

經過這些指令的學習,可以瞭解 Redis 特色功能,接下來就是整合到 Java 客戶端程式中了。


參考資料:


圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言