本文是有關 Redis 的學習筆記的一部分,相關目錄請參考 Redis 學習筆記(1)-簡介。
在上一篇的學習中,已啟動 Redis 容器,接下來要使用該 Redis 容器學習 Redis 指令。
下列為指令驗證的項目:
為最基本的型別,其簡易的操作(查/寫/讀/刪) 如下:
操作 | 指令 | 範例 |
---|---|---|
查找 Key | KEYS pattern | keys * |
寫入 Key | SET key value | set key k1 v1 |
讀出 Key | GET key | get k1 |
刪除 Key | DEL key | del k1 |
除了基本的操作外,模擬了三個想像的業務需求,使用 Redis 的功能來達成指定的需求。
下列則是這三個業務需求的實作。
需求: 產出訂單流水號,不可重複,由 1001 開始
相關指令:
操作 | 指令 | 範例 |
---|---|---|
遞增 Key | INCR key | incr orderId:generator |
指令操作 | 說明 |
---|---|
set orderId:generator 1000 | 設定初始值 |
incr orderId:generator | 每次呼叫遞增後回傳 |
incr orderId:generator | 每次呼叫遞增後回傳 |
需求: 統計系統當下的連接量,可作為是否熔斷的依據
相關指令:
操作 | 指令 | 範例 |
---|---|---|
遞減 Key | DECR key | decr session:current:count |
指令操作 | 說明 |
---|---|
incr session:current:count | 每次連入總數加1 |
decr session:current:count | 每次斷線總數減1 |
get session:current:count | 取得當下連入數 |
需求: 針對每一用戶在單位時間內僅能進行額度內操作,超過額度則禁止該操作
相關指令:
操作 | 指令 | 範例 |
---|---|---|
確認 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 | 檢視效期 |
若一個鍵內包含不是單一的值,而是多個欄位,則可以使用雜湊(Hash)。例如,訂單資料內有多個欄位(訂單編號、客戶編號、...)。
模擬了二個想像的業務需求,使用 Redis 的功能來達成指定的需求。
下列則是這二個業務需求的實作。
需求: 將一個客戶資料集成在一個 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 | 檢視一個欄位 |
需求: 針對每個客戶給定帳本,可儲值和扣款,額度足夠時才可執行指定的操作
相關指令:
操作 | 指令 | 範例 |
---|---|---|
對欄位作增減 | 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 | 檢視所有欄位 |
列表添加元素時,可由表頭添加、表尾添加或是插入指定位置。彈出列元素時,可由表頭彈出也可由表尾彈出。可以使用這些功能模擬 Queue(FIFO) 或是 Stack (LIFO)。
當有多路資料滙總,依序由特定程序處理時,可使用由 List 模擬的 Queue 來完成。
模擬了一個想像的業務需求,使用 Redis 的功能來達成指定的需求。
下列則是這一個業務需求的實作。
需求: 多點可請求發送簡訊,只需將簡訊請求推入 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 | 以阻塞方式取出元表 |
集合內的元素不可重覆,故可用來作去重功能。集合可以差集、交集、聯集,可用來達成共同朋友功能,或黑名單功能。
模擬了一個想像的業務需求,使用 Redis 的功能來達成指定的需求。
下列則是這一個業務需求的實作。
需求: 同一使用者每日訪問可能多次,要求統計每日不同使用者訪問總數
相關指令:
操作 | 指令 | 範例 |
---|---|---|
將元素加入 | 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 訪問的使用者數目 |
集合內的元素不可重覆,但每個元素可以有分數,元素會依分數由小到大在集合內排序。該特性可以用來實作排行榜、時效任務管理和權重任務管理。
模擬了一個想像的業務需求,使用 Redis 的功能來達成指定的需求。
下列則是這一個業務需求的實作。
操作 | 指令 | 範例 |
---|---|---|
對元素分數作遞增 | 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 | 返回由大到小前三名(含分數) |
MULTI 可以將數個指令先上傳到伺服器上的隊列中,此時尚未執行,待客戶端發送 EXEC 指令時,伺服器上隊列內的指令會一起執行。此時的執行也保證原子性操作。
Redis 7 以後支援 functions,在 Redis 7 以前是使用 LUA Script。相比 LUA Script 使用 function 更符合開發者習慣性。
使用 function 可將數個 redis 指令整合成為一個 redis function,執行時該 function 在 redis servier 端執行,不但整個 function 為一個原子性操作,所有的資料也由本地端讀取不經由網路。
模擬了一個想像的業務需求,使用 Redis 的功能來達成指定的需求。
下列則是這一個業務需求的實作。
需求: 有產品三件,讓大量客戶端搶購,僅有最早三個請求可以成功。
實作指令(建立 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 重覆搶購,失敗 |
若無法使用 Function 功能的情況下,也可以使用 LUA 腳本。LUA 腳本有二種使用方式如下:
模擬了一個想像的業務需求,使用 Redis 的功能來達成指定的需求。
下列則是這一個業務需求分別使用 EVAL 和 SCRIPT LOAD 的實作。
需求: 有產品三件,讓大量客戶端搶購,僅有最早三個請求可以成功。
實作指令檔
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
需求: 有產品三件,讓大量客戶端搶購,僅有最早三個請求可以成功。
實作指令檔
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
經過這些指令的學習,可以瞭解 Redis 特色功能,接下來就是整合到 Java 客戶端程式中了。
參考資料: