《Redis開發與運維》第二章 API的理解和使用(上)讀書筆記

搬磚大叔發表於2018-12-25

全域性命令

  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> 
複製程式碼
  • 鍵總數(dbsizedbsize命令計算鍵總數時直接獲取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 keyttl 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 keytype key會顯示鍵的資料結構型別,如果鍵不存在則返回結果為none

資料結構和內部編碼

  type key命令實際返回的就是鍵當前的資料結構型別,分別是string(字串)、hash(雜湊)、list(列表)、set(集合)、zset(有序集合)。但是這些都是Redis對外的資料結構。如下圖所示:

在這裡插入圖片描述
  每一種資料結構都有自己的底層內部編碼實現,並且是多種實現,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還提供了setexsetnx兩個命令,作用和set命令的exnx選項一樣。如下:
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
複製程式碼

  getsetset會設定值,不同的是,它會返回這個鍵原來的值,如下:

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次也可以採用類似的思路。   除了以上幾種應用場景,字串還有很多的應用場景,這需要我們結合字串提供的相應命令去決定怎樣使用。

相關文章