全域性命令
Redis有五種資料結構,它們是鍵值對中的值,對於鍵來說有一些通用的命令。
- 檢視所有的鍵(
keys *
)keys *
會遍歷所有的鍵,生產環境禁止使用。
172.17.236.250:6379> set hello world
OK
172.17.236.250:6379> set java jedis
OK
172.17.236.250:6379> set python redis-py
OK
172.17.236.250:6379> keys *
1) "java"
2) "python"
3) "hello"
172.17.236.250:6379>
複製程式碼
- 鍵總數(
dbsize
)dbsize
命令計算鍵總數時直接獲取Redis內建的鍵總數變數,不會去遍歷所有的鍵。
172.17.236.250:6379> dbsize
(integer) 3
複製程式碼
- 檢查鍵是否存在(
exists key
) 存在返回1,不存在返回0。
172.17.236.250:6379> exists java
(integer) 1
172.17.236.250:6379> exists pp
(integer) 0
複製程式碼
- 刪除鍵(
del key \[key ...\]
) del無論值是什麼型別,del命令都可以將其刪除。也可以支援刪除多個鍵。返回結果為刪除成功的鍵數,如果刪除一個不存在的鍵則返回0。
172.17.236.250:6379> del java
(integer) 1
172.17.236.250:6379> del python hello
(integer) 2
172.17.236.250:6379> exists java
(integer) 0
複製程式碼
- 鍵過期(
expire key seconds
) Redis支援對鍵新增過期時間,當超過過期時間後,會自動刪除鍵,例如為鍵hello設定10秒過期時間。
172.17.236.250:6379> set hello world
OK
172.17.236.250:6379> expire hello 10
(integer) 1
複製程式碼
- 返回鍵過期的剩餘時間(
ttl key
)ttl key
有三種返回值: 大於等於0的整數:鍵剩餘的過期時間; -1:鍵沒有設定過期時間; -2:鍵不存在。
172.17.236.250:6379> set hello world
OK
172.17.236.250:6379> ttl helo
(integer) -2
172.17.236.250:6379> ttl hello
(integer) -1
172.17.236.250:6379> expire hello 10
(integer) 1
172.17.236.250:6379> ttl hello
(integer) 5
複製程式碼
- 鍵的資料結構型別(
type key
)type key
會顯示鍵的資料結構型別,如果鍵不存在則返回結果為none
。
資料結構和內部編碼
type key
命令實際返回的就是鍵當前的資料結構型別,分別是string(字串)、hash(雜湊)、list(列表)、set(集合)、zset(有序集合)。但是這些都是Redis對外的資料結構。如下圖所示:
- 檢視內部編碼(
object encoding key
),例如檢視鍵hello
對應值的內部編碼
172.17.236.250:6379> set hello world
OK
172.17.236.250:6379> object encoding hello
"embstr"
複製程式碼
- 設計優點
- 可以改進內部編碼,但是對外部的資料結構無影響;
- 多種內部編碼實現可以在不同場景下發揮各自的優勢;
單執行緒架構
- 開啟兩個客戶端同時執行命令:
127.0.0.1:6379> incr counter
127.0.0.1:6379> incr counter
複製程式碼
看到如下Redis客戶端與服務端的簡化模型圖:
因為Redis是單執行緒處理命令,所以當一條命令到達服務端是不會被立即執行,所有的命令都會進入到一個佇列中,然後再逐步執行,所以上面兩個命令的執行順序是不確定的。如下圖所示: 但是可以確定的是不會有兩條命令同時執行,所以兩條incr
命令不管是怎麼執行都是2,不會產生併發問題。
為什麼Redis單執行緒還能那麼快?
- Redis將所有的資料存在記憶體中,是純記憶體操作;
- 非阻塞I/O,Redis使用epoll作為I/O多路複用技術實現,再加上Redis自身的事件處理模型將epoll中的連線、讀寫、關閉都轉行為事件,不會在網路I/O上浪費時間。如下圖:
- 單執行緒避免了執行緒切換和競態產生消耗;
單執行緒的好處:
- 簡化資料結構和演算法實現;
- 避免了執行緒切換和競態的消耗;
單執行緒的問題:
- 如果某個命令執行過長,會造成其它命令阻塞,這對Redis是致命的。
字串
字串是Redis最基礎的資料結構。首先鍵都是字串型別,並且其他資料結構都是在字串型別的基礎上進行構建的。 字串型別的值可以為:
- 字串(簡單的字串和複雜的字串(例如:JSON、XML))
- 數字(整數、浮點數)
- 二進位制(音視訊或圖片(不能超過512MB))
字串常用操作命令
設定值
- 設定成功則返回
OK
set key value \[ex seconds\] \[px milliseconds] \[nx|xx\]
複製程式碼
set
命令有以下幾個選項:
ex seconds
:為鍵設定秒級過期時間;px milliseconds
:為鍵設定毫秒級過期時間;nx
:鍵必須不存在才可以設定成功;xx
:與nx相反,鍵必須存在,才可以設定成功; 除了set
之外,Redis還提供了setex
和setnx
兩個命令,作用和set
命令的ex
和nx
選項一樣。如下:
setex key seconds value
setnx key value
複製程式碼
用例子說明set、setnx、set xx的區別:
##鍵hello不存在
172.17.236.250:6379> exists hello
(integer) 0
##set xx更新鍵hello的值返回nil
172.17.236.250:6379> set hello redis xx
(nil)
##為鍵hello設定值
172.17.236.250:6379> set hello world
OK
##setnx為鍵hello設定值,因為鍵hello已存在所以設定失敗返回0
172.17.236.250:6379> setnx hello redis
(integer) 0
##set xx為鍵hello更新值成功
172.17.236.250:6379> set hello jedis xx
OK
172.17.236.250:6379> get hello
"jedis"
複製程式碼
- 批量設定值
###mset key value \[key value ...\]
172.17.236.250:6379> mset a 1 b 2 c 3 d 4
OK
複製程式碼
在實際的應用場景中,由於Redis是單執行緒的,如果有多個客戶端同時執行setnx key value
,根據setnx
的特性只有一個客戶端能設定成功,setnx
可以作為分散式鎖的一個實現方案(Redis官方使用 setnx 實現分散式鎖方案傳送門)
獲取值
- 獲取單個值,如果要獲取的鍵不存在則返回
nil
get key
複製程式碼
- 批量獲取值(如果獲取的鍵不存在則返回
nil
)
mget key \[key ... \]
172.17.236.250:6379> mget a b c d
1) "1"
2) "2"
3) "3"
4) "4"
複製程式碼
批量操作命令可以提高開發效率,如果沒有mget
這樣的命令,要執行n次get
命令會按照下圖方式執行,具體耗時如下:
n次get時間 = n次網路時間 + n次命令執行時間
複製程式碼
使用mget
命令會按照下圖方式執行,具體耗時如下:
n次get時間 = 1次網路時間 + n次命令時間
複製程式碼
Redis一次命令的執行時機包括網路時間和命令執行時間,Redis服務端的執行速度已經夠快。網路可能會成為效能瓶頸。在實際開發場景中,每次批量操作傳送的命令數不是無節制的,數量過多可能會造成Redis阻塞或網路堵塞。
計數
incr key
複製程式碼
incr命令用於對值進行自增操作,返回情況有三種:
- 值不是整數,返回錯誤;
- 值是整數,返回自增後的結果;
- 鍵不存在,按照值為0自增,返回結果為1。 示例如下:
### 鍵 a 不存在
172.17.236.250:6379> exists a
(integer) 0
### 對不存在的鍵 a 進行自增,返回結果為 1
172.17.236.250:6379> incr a
(integer) 1
### 再次對 a 進行自增操作,返回結果為 2
172.17.236.250:6379> incr a
(integer) 2
### 值不是整數,報錯
172.17.236.250:6379> set hello world
OK
172.17.236.250:6379> incr hello
(error) ERR value is not an integer or out of range
複製程式碼
除了incr
命令,Redis提供了decr
自減、incrby
自增指定數字、decrby
自減指定數字、incrbyfloat自增浮點數。如下所示:
decr key
incrby key increment
decrby key increment
incrbyfloat key increment
172.17.236.250:6379> decr a
(integer) 1
172.17.236.250:6379> incrby a 5
(integer) 6
172.17.236.250:6379> decrby a 5
(integer) 1
172.17.236.250:6379> incrbyfloat a 4.9
"5.9"
複製程式碼
字串不常用操作命令
追加值
append key value
複製程式碼
append
可以向字串尾部追加值,如下所示:
172.17.236.250:6379> set hello world
OK
172.17.236.250:6379> get hello
"world"
172.17.236.250:6379> append hello redis
(integer) 10
172.17.236.250:6379> get hello
"worldredis"
複製程式碼
字串長度
strlen key
複製程式碼
當前值為worldredis,所以返回長度為10(中文佔用3個位元組),如下:
172.17.236.250:6379> strlen hello
(integer) 10
複製程式碼
設定並返回原值
getset key value
複製程式碼
getset
和set
會設定值,不同的是,它會返回這個鍵原來的值,如下:
172.17.236.250:6379> getset hello world
(nil)
172.17.236.250:6379> getset hello redis
"world"
複製程式碼
設定指定位置的字串
setrange key offeset value
複製程式碼
將"adcd"變成"ddcd":
172.17.236.250:6379> set redis adcd
OK
172.17.236.250:6379> setrange redis 0 d
(integer) 4
172.17.236.250:6379> get redis
"ddcd"
複製程式碼
獲取部分字串
getrange key start end
複製程式碼
start和end分別是開始和結束的偏移量,偏移量從0開始計算,如下:
172.17.236.250:6379> getrange redis 0 1
"dd"
複製程式碼
字串型別命令時間複雜度表
內部編碼
字串的內部編碼有3種:
- int:8個位元組的長整型;
- embstr:小於等於39個位元組的字串;
- raw:大於39個位元組的字串。 Redis會根據當前值的型別和長度決定使用哪種內部編碼實現。如下所示:
#### int 型別
172.17.236.250:6379> set key 81
OK
172.17.236.250:6379> object encoding key
"int"
#### embstr 型別
172.17.236.250:6379> set key hello,world
OK
172.17.236.250:6379> object encoding key
"embstr"
#### raw 型別
172.17.236.250:6379> set key qwertyuiopasdfghjklzxcvbnmqwertyuioplkjhgfdsaczvxbnm
OK
172.17.236.250:6379> object encoding key
"raw"
複製程式碼
典型使用場景
快取功能
Redis作為快取層,MySQL作為儲存層,絕大部分請求的資料都是從Redis中獲取。因為Redis有支撐高併發的特性,所以快取能起到加速讀寫和降低後端壓力的作用。如下圖:
思路如下:- 定義獲取使用者基礎資訊函式
- 首先從Redis獲取使用者資訊
- 若沒有從Redis中獲取到使用者資訊,需要從MySQL中進行獲取,並且將結果寫入Redis中,新增過期時間
- 虛擬碼如下:
UserInfo getUserInfo(long id) {
//定義鍵
userRedisKey = "user:info:"+id;
//從Redis獲取值
value = redis.get(userRedisKey);
if(value!=null){
// 將值進行反序列化為UserInfo並返回結果
userInfo = deserialize(value);
return userInfo;
} else {
// 從MySQL獲取使用者資訊
userInfo = mysql.get(id);
// 將userInfo序列化,設定3600秒過期時間,存入Redis
if (userInfo != null) {
redis.setex(userRediskey,3600,serialize(userInfo));
}
}
}
複製程式碼
注意:Redis沒有命令空間,也沒有對鍵名有強制要求(除了不能使用一些特殊字元)。鍵名要設計合理,有利於防止鍵衝突專案的可維護性。推薦使用"業務名: 物件名:id:[屬性]"作為鍵名。可以在能描述鍵的含義下適當減少鍵的長度,從而減少鍵過長而造成的記憶體浪費。
計數
使用Redis作為計數的基礎工具,他可以實現快速計數、查詢快取功能,同時資料可以非同步落地到其他資料來源。 虛擬碼如下:
long incrVideoCounter(long id) {
key = "video:playCount:"+id;
return redis.incr(key);
}
複製程式碼
在實際開發中,計數系統要考慮很多東西:防作弊、按照不同的維度計數,資料持久化到底層資料來源等等。
共享session
一個分散式Web服務將使用者的session資訊儲存在各自的伺服器上,但是出於負載均衡的考慮,分散式服務會將使用者的訪問均衡到不同的伺服器上,這樣就導致使用者重新整理一次訪問可能就會發現需要重新登入。如下圖:
為了解決這個問題,開發人員可以使用Redis將使用者的session進行集中的管理。在這種模式下只需要保證Redis是高可用和擴充套件性的,每次使用者更新或者查詢登入資訊都可以直接從Redis種獲取。如下圖:限速
很多應用在每次進行登入時,會讓使用者輸入手機驗證碼來確定是否是使用者本人。為了讓簡訊介面不被頻繁訪問,會限制使用者在每分鐘的獲取驗證碼的頻率,例如一分鐘不能超過5次。如下圖:
虛擬碼如下:phoneNum=""135xxxxxxxx;
key = "shortMsg:limit:"+phoneNum;
isExists = redis.set(key,1,"EX 60","NX");
if(isExists != null || redis.incr(key) <= 5) {
//通過
} else {
//限速
}
複製程式碼
例如某些網站不能在1秒之內訪問超過n次也可以採用類似的思路。 除了以上幾種應用場景,字串還有很多的應用場景,這需要我們結合字串提供的相應命令去決定怎樣使用。