1. 程式人生 > 其它 >Redis基礎操作和高階特性

Redis基礎操作和高階特性

基本資料結構

通用命令

  • keys [pattern] 遍歷出所有的key,可以根據正則表示式匹配

  • dbsize 統計key的數量

  • exists [key] 檢查key是否存在

  • del [key] 刪除key

  • expire [key] [seconds] 設定key在指定的時間後過期

  • ttl [key] 檢視key過期時間

  • persist [key] 設定永不過期

  • type key 檢視key的型別

字串型別(string)

資料結構

​ 字串型別的value不能大於512M,key和value其實儲存的都是二進位制資料。String型別是二進位制安全的。意思是redis的string可以包含任何資料。比如jpg圖片或者序列化的物件 。

主要命令

get [key] # 獲取
set [key] [value] # 設定值
del [key] # 刪除key
----------------------------------------
incr [key] # 自增1,如果key不存在則建立並自增
decr [key] # 自減1
incrby [key] [k] # 自增k
decrby [key] [k] # 自減k
----------------------------------------
set [key] [value] # 不管key是否存在都會執行成功
setnx [key] [value] # key不存在時才成功 
setxx [key] [value] # key存在時則成功
----------------------------------------
mset key1 [value1 key2 value2 ....keyn valuen] #批量設定 (若有多次的處理操作可以放入一次IO中,節省網路IO時間)
mget [key1 key2 key3...keyn] # 批量獲取
----------------------------------------
getset [key] [newvalue] # 給key設定一個新值並將舊值返回,操作是原子的
append [key] [value] # 追加值到後面
strlen [key] # 字串的長度
----------------------------------------
incrbyfloat [key] [value] # 對資料做浮點數處理
getrange [key] [startIndex] [endIndex] # 獲取字串索引從startIndex到endIndex的值
setrange [key] [index] [value]# 設定字串索引index位置的值

使用場景:

  1. 適合儲存熱點資料,如視訊資訊,列表,熱點資料的json串。
  2. 由於字串型別可以實現自增且配合單執行緒的執行緒安全很適合用於做計數器。如分散式自增id

雜湊型別(hash)

資料結構:

​ key看作是資料庫中的一行的索引,而filed和value就是資料庫中的屬性。可以看成從redis中獲取一個Map。

主要命令

hget [key] [field] #  從key對應的map中獲取鍵為field的值 
hset [key] [field] [value] # 從key對應的map中設定鍵為field的值
hdel [key] [field ] # 刪除key對應的map中的field的鍵 
----------------------------------------
hexists [key] [field] # 查詢key對應的map中是否有field對應的鍵
hlen [key] # 判斷map有多少條鍵值對  
----------------------------------------
hmget [key] [field1...fieldn] # 批量獲取map中的filed域
hmset [key] [field1 value1.....fieldn valuen] # 批量設定
----------------------------------------
hgetall [key] # 返回所有的key-value 謹慎使用
hvals [key] # 返回所有的value
hkeys [key] # 返回所有的field
----------------------------------------
hincrby [key] [field] # 自增
hincrbyfloat [key] [field] [value] # 自增浮點數
hsetnx [key] [field] [value] # key不存在時才設定 

使用場景

  1. 適合作為儲存物件的介質,比字串型別更好去處理每個欄位的值
  2. 由於hash資料型別的key-value的特性,用來儲存關係型資料庫中表記錄,是redis中雜湊型別最常用的場景。一條記錄作為一個key-value,把每列屬性值對應成field-value儲存在雜湊表當中,然後通過key值來區分表當中的主鍵

列表(list)

資料結構:

特性:

  • 有序
  • 可以重複
  • 可以從左右彈出或者壓入

主要命令

----------------------------------------增
rpush [key] [value1....valuen] # 從佇列右邊壓入資料
lpush [key] [value1....valuen] # 從佇列左邊壓入資料
linsert [key] [before|after] [value] [nevalue] # 在給定list指定值的前或後插入新值
----------------------------------------刪
lpop [key] # 左邊彈出一個元素
rpop [key] # 右邊彈出一個元素
lrem [key] [count] [value] # 刪除value為給定值的資料,刪除給定數量的值
ltrim [key] [star]t [end] # 按照給定索引範圍修剪列表
----------------------------------------改
lset [key] [index] [newValue] # 設定指定索引的值為newValue
----------------------------------------查
lrange [key] [start] [end] # 獲取指定範圍的所有資料
lindex [key] [index] #獲取指定索引的值
llen [key] #獲取列表長度
----------------------------------------不常用
blpop|brpop  [key] [timeout] # lpop的阻塞版本,timeout=0永遠不阻塞。用作訊息佇列時好用

使用場景

  1. 微博關注人釋出訊息列表的時間軸就可以通過redis的列表來實現,通過lpush將新發布的訊息插入到列表中可以實現訊息的時間排列
  2. 可以用list模擬出訊息佇列的功能,還能模擬棧的功能
    1. LPUSH + LPOP = Stack
    2. LPUSH + RPOP = Queue
    3. LPUSH + BRPOP = Message Queue

集合(set)

資料結構

特點:

  1. 無序
  2. 無重複
  3. 支援集合間操作

集合內API

 sadd [key] [element] # 向集合中新增element,可以插入多個
 srem [key] [element] # 刪除
 scard [key] # 計算集合大小
 sismember [key] [element] # 判斷是否在集合中 
 srandmember [key] [count] #  隨機取出count個元素 
 spop [key] # 隨機彈出一個元素 
 smembers [key]  #獲取所有元素

集合間API

sdiff [key1] [key2] # 差集
sinter[key1] [key2] # 交集
sunion [key1] [key2] # 並集

使用場景

  1. 標籤-使用者系統中可以使用set來實現,使用者對應的set可以sadd插入不同的標籤,標籤set中可以sadd中不同的使用者
  2. 抽獎系統可以通過set的spop或者srandmember實現
  3. 可以實現共同關注的功能,通過集合間的操作如sinter

有序集合(zset)

資料結構

集合 VS 有序集合
列表 VS 有序集合

重要API

---------------------------------------- 基本操作
zadd [key] [score] [element] # 新增元素,可以插入多個 
zrem [key] [element] # 刪除元素,可以多個
zscore [key] [element] # 獲取元素的分數
 ---------------------------------------- 範圍操作
 zincrby [key] [incrScore] [element] # 自增元素的score
 zcard [key] # 元素個數
 zrange [key] [start] [end] # 返回指定索引範圍內的順序元素
 zrangebyscore [key] [minScore] [maxScore] # 返回指定分數期間的升序元素
 zcountbyscore [key] [minScore] [maxScore] # 獲取指定分數區間的元素的個數
 zremrangebyrank [key] [start] [end] # 刪除給定排序索引範圍的元素
 zrevrank # zrev字首的代表從大到小排序
 zrevrange 
 zrevrangebyscore
 ---------------------------------------- 集合操作
 zunionstore # 並集
 zinterstore # 交集

使用場景:

  1. 排行榜,如音樂排行榜,新書榜可以使用有序佇列來實現。

高階特性

pipline 管道

未使用pipeline:

使用pipeline:

​ pipeline時間一系列操作通過一次網路IO傳送過去並執行,減少網路IO帶來的開銷。redis處理一行命令的時間大概是微秒級別的,但是網路延遲可能是十幾毫秒這樣就大大的浪費了redis高速執行的優勢,所以pipeline可以優化網路延遲帶來的開銷。

不具備原子性

傳統的命令都是具有原子性的,而pipeline命令保護了多個命令時,可能在命令處理的間隙會插入其他的命令進來。

傳統的命令執行:

pipeline命令:

注意:

  1. 注意每次pipeline攜帶的資料量
  2. pipeline每次只能作用在一個redis節點上。

在Jedis中使用pipeline

/**
	 * 刪除多個字串key 並釋放連線
	 * 
	 * @param keys*
	 * @return 成功返回value 失敗返回null
	 */
public boolean mdel(List<String> keys) {
    Jedis jedis = null;
    boolean flag = false;
    try {
        jedis = pool.getResource();//從連線借用Jedis物件
        Pipeline pipe = jedis.pipelined();//獲取jedis物件的pipeline物件
        for(String key:keys){
            pipe.del(key); //將多個key放入pipe刪除指令中
        }
        pipe.sync(); //執行命令,完全此時pipeline物件的遠端呼叫 
        flag = true;
    } catch (Exception e) {
        pool.returnBrokenResource(jedis);
        e.printStackTrace();
    } finally {
        returnResource(pool, jedis);
    }
    return flag;
}

事務

​ Redis 事務的本質是一組命令的集合。事務支援一次執行多個命令,一個事務中所有命令都會被序列化。在事務執行過程,會按照順序序列化執行佇列中的命令,其他客戶端提交的命令請求不會插入到事務執行命令序列中。

  總結說:redis事務就是一次性、順序性、排他性的執行一個佇列中的一系列命令。 

特性

  1. 沒有隔離級別:批量操作在沒有傳送EXEC之前只是放入佇列中並沒有執行。
  2. 不保證原子性,任意指令失敗也不會回滾
  3. 只是會在執行前檢查一下語法錯誤,執行中的錯誤不會回滾

相關命令

  • watch [key1] ... [key2],監視給定的key,如果在事務啟動前被其他命令修改則事務被打斷
  • multi 標記一個事務塊的開始
  • exec 執行所有的事務塊的命令(watch被取消)
  • discard 取消事務
  • unwatch 取消watch

正常事務:

放棄事務:

錯誤時的場景:

  1. 當有指令編譯性錯誤時會將所有的執行佇列中的命令都discard掉,全體失敗
  2. 當有執行時錯誤時,只失敗那一條指令

pub/sub

角色

  1. 釋出者

  2. 訂閱者

  3. 頻道

    釋出者釋出到某個頻道上去,訂閱者通過訂閱功能訂閱頻道就能收到釋出者的釋出的資訊。

模型:

命令

publish [channel] [message] # 釋出訊息到指定的頻道,返回的是訂閱者數量
subscribe [channel] # 訂閱頻道
unsubscribe [channel] # 取消訂閱

和訊息佇列對比

​ 在訊息佇列中,傳送了一條訊息只能被一個訂閱者給搶到,而釋出訂閱模式中一條訊息傳送到頻道中能被所有的訂閱者給獲取到。如果要做通知功能則pub/sub比訊息佇列合適,如果要做搶紅包等功能時用訊息佇列更合適。

bitmap

​ 在平時的開發中,對於一些大量的布林變數如果用字串型別那麼太浪費記憶體空間了,可以嘗試使用點陣圖的功能,點陣圖實際上也是字串但是他每一位都可以用來表示一個bool變數,這樣一個位元組就能當成8個位元組來使用。

命令

 Redis 的位陣列是自動擴充套件,如果設定了某個偏移位置超出了現有的內容範圍,就會自動將位陣列進行零擴充。

setbit [key] [offset] [0|1] # 設定點陣圖
getbit [key] [offset] # 獲取偏移量對應的值
bitcount [key] # 統計為1的位數的個數
bitop [and|or|not|xor] destkey [key] [key1...keyn] # 多個位圖做操作後的結果存入destkey中
bitpos [key] [0|1] [start] [end] # 計算點陣圖範圍內第一個等於0|1的數所在的位置

使用場景

​ 場景就是用來儲存狀態值,如儲存使用者的每天登陸情況。可以將使用者的userid作為key,日期作為偏移量,登陸時就置1。可以通過bitcount

geoHash

​ 將二維的地址座標轉換成一維的變數,通過ZSET的有序集合可以實現從遠到近的排序功能。

原理

​ 經緯度範圍:東經180度到西經180度,緯度是南緯90度到北緯90度。設定:西經為負,南緯為負。那麼用數學表示就是經度:[-180,180],緯度:[-90,90],以子午線和赤道平分地球就是如下圖:

​ 那麼按照象限劃分分別是:11,01,00,10。再精確一步在每個象限中再去劃分:

這樣劃分的約多次,每一個子象限就越小,越小的子象限就越能精確的描述位置,所以位置描述的經度是和劃分的次數相關。

示例

給定一個點(39.923201, 116.390705):採用二分法得到以下的列表

最後得出維度的二進位制為

10111000110001111001

同理得出維度的二進位制

11010010110001000100

經緯度合併 (經讀在偶數位 緯度在奇數位)

11100 11101 00100 01111 00000 01101 01011 00001

再將二進位制資料用Base32轉換為最終值:

wx4g0ec1

在redis中,會分別對經度和維度進行26次切分,最後融合經緯度得到一個52位的位元位,通過base32進行處理,得到一個11為長度的字串,

將52位位元的數值當做score插入到zset中,之後的操作就跟操作zset類似了

命令

 geo add key  經度 維度 value  將key對應的value的經緯度存入geo中
 geodis key value1 value2 km  獲得key中的value1和value2的距離  (距離單位可以是 m、km、ml、ft,分別代表米、千米、英里和尺)
 geops key value  獲取key-value的經緯度座標
 geohash key value base32的編碼字串
 georadiusbymember key value 距離  count N asc  查詢距離key-value在某個範圍內的元素正排序
 georadiusbymember key value 20 km **withcoord withdist withhash** count 3 asc  **withcoord withdist withhash附加的引數  withdist 為距離**
 EXPIRE key seconds  //將key的生存時間設定為ttl秒
 PEXPIRE key milliseconds  //將key的生成時間設定為ttl毫秒
 EXPIREAT key timestamp  //將key的過期時間設定為timestamp所代表的的秒數的時間戳
 PEXPIREAT key milliseconds-timestamp  //將key的過期時間設定為timestamp所代表的的毫秒數的時間戳

特點

  1. 用一個字元來代替精度和緯度,在資料庫上可以實現一列上應用索引
  2. GeoHash不是一個點,而是一個區域
  3. 在redis中使用的話,最好只用單機redis來儲存geohash資料
  4. 編碼越長表示的範圍約小,就越精確

適用場景

​ QQ附件的人功能,APP上關於地理位置的列表都可以通過geohash來實現。

hyperloglog

​ HyperLogLog 是用來做基數統計的演算法,HyperLogLog 的優點是,在輸入元素的數量或者體積非常非常大時,計算基數所需的空間總是固定的、並且是很小的。在 Redis 裡面,每個 HyperLogLog 鍵只需要花費 12 KB 記憶體,就可以計算接近 2^64 個不同元素的基數。和計算基數時,元素越多耗費記憶體就越多的集合形成鮮明對比。

  • HyperLoglog 是一種基於概率的演算法並不是redis獨佔的
  • 用於做基數統計
  • 空間損耗小
  • 值具有少量誤差(0.81%)
  • 無法取出單條資料

原理

基數

​ 基數就是指一個集合中不同值的數目,比如[a,b,c,d]的基數就是4,[a,b,c,d,a]的基數還是4,因為a重複了一個,不算。基數也可以稱之為Distinct Value,簡稱DV。

伯努利實驗

​ 起源的本質是來自於生活中的拋硬幣的發現,稱作伯努利實驗:硬幣擁有正反兩面,一次的上拋至落下,最終出現正反面的概率都是50%。假設一直拋硬幣,直到它出現正面為止,我們記錄為一次完整的試驗,間中可能拋了一次就出現了正面,也可能拋了4次才出現正面。無論拋了多少次,只要出現了正面,就記錄為一次試驗。這個試驗就是伯努利試驗

那麼對於多次的伯努利試驗,假設這個多次為n次。就意味著出現了n次的正面。假設每次伯努利試驗所經歷了的拋擲次數為k。第一次伯努利試驗,次數設為k1,以此類推,第n次對應的是kn

其中,對於這n伯努利試驗中,必然會有一個最大的拋擲次數k,例如拋了12次才出現正面,那麼稱這個為k_max,代表拋了最多的次數。

伯努利試驗容易得出有以下結論:

  1. n 次伯努利過程的投擲次數都不大於 k_max。
  2. n 次伯努利過程,至少有一次投擲次數等於 k_max

最終結合極大似然估算的方法,發現在nk_max中存在估算關聯:$n = 2^(k_max) $。這種通過區域性資訊預估整體資料流特性的方法似乎有些超出我們的基本認知,需要用概率和統計的方法才能推導和驗證這種關聯關係。

簡單的想法

​ 對於集合{a,b}分別對兩個元素進行hash得到二進位制資料:00110111,10010000111,從左到右最近的一個1的位置分別為3和0,我們只保留最大值,根據伯努利公式:n=2^3=8,顯然這個演算法是不準確的需要進一步優化。

資料分桶

​ 將資料分成m等份後分別估算每個桶的值後取得平均值後再乘以m,具體分桶的步驟:

​ 分為兩桶,那麼就會拿到二進位制數字第一位來做判斷,0進桶1 ,1進桶2。這樣00110111會進入桶1中計算出估值為kmax=2,10010000111估算出kmax=3,通過公式:\(DV_{LL} = constant * m * 2^R\),所以上面演示的這個集合的值就等同於\(DV_{LL} = 2 * 2^{(2+3)/2}\)= 11.313,平均數任意收到最大數的影響,所以一般要採用調和平均數或算數平均數來解決這一問題。,而常數量constant是根據《HyperLogLog: the analysis of a near-optimal cardinality estimation algorithm》論文中得出的引數進行設定的。

命令

pfadd [key] [value....]# 新增元素
pfcount [key] # 獲得基數值(去重)
pfmerage [key .. keyn]# 合併key

適用場景

​ 一般用在資料量極大的統計上,並且這個統計能夠容忍細微的誤差,比如線上人數,註冊人數,訪問人數。

布隆過濾器

​ 布隆過濾器實際上就是一個Bit陣列,本質上是一個數據,可以根據下標快速找到資料。

通過hash函式對要記錄的資料進行hash操作,得到BIT陣列並將redis中的陣列對應的標誌位也置為1,布隆過濾器的長度會影響誤報率,長度越長錯誤率越低。布隆過濾器也可以通過bitmap和程式設計來實現布隆過濾器的功能。

將一對KEY-VALUE插入到布隆過濾器中:

  1. 由多種HASH演算法將KEY計算為一個整數的索引
  2. 將索引跟布隆中的容量陣列取模,得到該key落在陣列的位置
  3. 多個HASH演算法算出多個位置,將陣列這些位置都標為1

使用exists時的過程:

  1. 用多種hash演算法將此key的所有可能落點都計算出來
  2. 判斷是否所有落點都為1,如果是則存在 如果不全是1則為不存在

錯誤率計算

F 為錯誤率 L為陣列長度(bit位)N為預計的元素數量 K為hash函式的數量

\(K = 0.7 * (L/N)\)

\(F = 0.6187^{L/N}\)

由此可見 錯誤率和容量成反比關係,容量設定的越大則錯誤率越低需要的hash函式數目越少

如果設定的過大則會浪費記憶體空間,如果設定的容量越小則錯誤率越大,而且需要hash函式的過程變多,使得計算效率下降

大致的看

  1. 錯誤率為10% 時,一個元素需要4.79個bit 近似看成5位元
  2. 錯誤率為1%時,一個元素需要9.58個bit 近似看成10位元
  3. 錯誤率為0.1%時,一個元素需要14.38個bit 近似看成15位元

相當於SET來說,布隆只儲存key的“指紋資訊”,而SET會將字串或者物件序列化儲存進redis,元素的內容通常幾十到上百個位元組,而布隆只有15位左右也就是2個位元組,優勢相當大

例項

向redis的布隆過濾器中插入“baidu”,8為bit,三次hash後得到為1的位數為1,4,7。

再向redis中插入“tencent”,可以看到4這個位置發生了hash碰撞,所以長度過短造成碰撞的次數越多誤差率越高。

特性

  1. 可以設定精確度,當bit陣列位數越長時精度越高。但是達不到百分百精度。
  2. 只能存入,無法刪除。
  3. 布隆過濾器說不存在一定不存在,存在可能是誤判。

命令

1. bf.add [key] [value]	# 加入
2. bf.exists [key] [value] # 判斷是否存在
3. bf..madd [key] [value1] [value2] [value3] # 批量新增
4. bf.mexists [key] [value1] [value2] [value3] # 批量判斷是否存在

適用場景

  1. 社交軟體上推送新聞或者視訊的時候都可以通過布隆過濾器去判斷視訊時候已經被推送過,這樣就能推送沒有看過的新聞
  2. 在高併發的情況下,可以讓redis和資料庫做同步,查詢先通過redis的布隆過濾器,如果通過布隆過濾器則再去查詢對應的值。