Redis-5種基本類型結(jié)構(gòu)

筆記來自:《redis開發(fā)與維護》第二章 API的理解和使用
主要內(nèi)容:redis 5種數(shù)據(jù)結(jié)構(gòu):string、hash 、list 、set 、sorted set

字符串(String)

鍵都是字符串類型,所以其他數(shù)據(jù)結(jié)構(gòu)都是在字符串類型基礎(chǔ)上構(gòu)建的

字符串類型的值最大不超過512MB

1. 常用命令

(1)設(shè)置值
set key value [ex seconds] [px seconds] [nx|xx]

[ex seconds] :設(shè)置秒級過期時間

[px seconds]:設(shè)置毫秒級過期時間

[nx|xx]:

nx 鍵不存在,才可設(shè)置成功,用于添加;

xx 鍵必須存在,才可設(shè)置成功,用于更新, he

redis提供setexsetnx 兩個命令:

setex key seconds value
setnx key value

插入失敗

127.0.0.1:6379> get hello
"reids"
127.0.0.1:6379> setnx hello redis
(integer) 0
(2) 獲取值 -- get

不存在,返回nil(空):

127.0.0.1:6379> get no_exist_key
(nil)
(3)批量 設(shè)置值 -- mset
mset key value [key value ...]
127.0.0.1:6379> mset a 1 b 2 c 3 d 4
OK
(4)批量獲取值 -- mget
mset key value [key value ...]
127.0.0.1:6379> mget a b c d
1) "1"
2) "2"
3) "3"
4) "4"

不存在,返回nil(空):
127.0.0.1:6379> mget a c f
1) "1"
2) "3"
3) (nil)
(5) 計數(shù) -- incr key

incr命令用于做自增操作,返回結(jié)果分三種情況:

  • 值不是整數(shù),返回錯誤
  • 值是整數(shù),返回自增后的結(jié)果
  • 鍵不存在,按照為0自增,返回結(jié)果為1

例子:

  1. 鍵不存自增

    127.0.0.1:6379> exists testkey
    (integer) 0
    127.0.0.1:6379> incr testkey
    (integer) 1  
    

    返回1

  2. 值不是整數(shù)自增

    127.0.0.1:6379> set testkey abcd
    OK
    127.0.0.1:6379> type testkey
    string
    127.0.0.1:6379> get testkey
    "abcd"
    127.0.0.1:6379> incr testkey
    (error) ERR value is not an integer or out of range
    

返回錯誤

  1. 值為整數(shù)自增
127.0.0.1:6379> set testkey 10
OK
127.0.0.1:6379> incr testkey
(integer) 11

除了incr命令外,還有其他:

  • 自減 decr

  • 自增指定數(shù)字 incrby

    127.0.0.1:6379> set testkey 10
    OK
    127.0.0.1:6379> incr testkey
    (integer) 11
    127.0.0.1:6379> incrby testkey 5
    (integer) 16
    
  • 自減指定數(shù)字 decrby

  • 自增浮點數(shù) incrbyfloat

2. 不常用命令

(1)追加值 -- append

向字符串尾追加值

127.0.0.1:6379> get testkey
"16"
127.0.0.1:6379> append testkey abcd
(integer) 6
127.0.0.1:6379> get testkey
"16abcd"
(2)字符串長度 -- strlen
127.0.0.1:6379> get testkey
"16abcd"
127.0.0.1:6379> strlen testkey
(integer) 6
(3)設(shè)置并返回原值 -- getset
127.0.0.1:6379> get testkey
"\xca\xc0\xbd\xe7"
127.0.0.1:6379> getset testkey getsetvalue
"\xca\xc0\xbd\xe7"
(4)設(shè)置指定位置的字符 -- setrange
127.0.0.1:6379> setrange testkey 5 newvalue
(integer) 13
127.0.0.1:6379> get testkey
"getsenewvalue"
127.0.0.1:6379> setrange testkey 3 123
(integer) 13
127.0.0.1:6379> get testkey
"get123ewvalue"

從 指定位置修改字符,從 "getsenewvalue"第3個字符sen修改為123

(5)獲取部分字符串 -- getrange
127.0.0.1:6379> get testkey
"get123ewvalue"
127.0.0.1:6379> getrange testkey 3 5
"123"

3. 內(nèi)部編碼

Redis 會根據(jù)當前值的類型和長度決定使用哪種內(nèi)部編碼實現(xiàn)。

字符串類型編碼3種:
int: 8個字節(jié)長度
embstr: 小于等于39個字節(jié)長度
raw: 大于39個字節(jié)的字符串
127.0.0.1:6379> object encoding testkey
"embstr"
127.0.0.1:6379> get hello
"word"
127.0.0.1:6379> get counter
"2"
127.0.0.1:6379> object encoding counter
"int"

4. 典型使用場景

1. 緩沖功能

(1) 模擬訪問獲取數(shù)據(jù)過程

  • 首先從redis獲取用戶信息
  • 如果沒有從redis獲取用戶信息,需要從mysql中讀取,并將結(jié)構(gòu)返回寫到redis, 添加1小時(3600秒)過期時間

(2)整個功能的偽代碼

public function getUserInfo(integer $id)
{
    //定義鍵
    $userRedisKey = "user:info:" + $id;
    // 從redis獲取值
    $value = redis.get($userRedisKey);
    
    if ($value) {
        // 將值進行反序列化
        $userInfo = json_decode($value, true);
        
    } else {
        // 否則,從mysql獲取數(shù)據(jù)
        $userInfo = mysql.get($id);
        if ($userInfo) {
            redis.setex($userRedisKey, 3600, json_encode($userInfo));
        }
    }
    return $userInfo;
}
2. 計數(shù)

例如: 實現(xiàn)視頻播放數(shù)計數(shù),用戶每播放一次,播放次數(shù)自增1

public function incrVideoCounter(integer $id) {
    $key = 'video:playCount:' . $id;
    return redis.incr($key);
}
3.共享Session

分布式服務(wù)將用戶的訪問均衡到不同服務(wù)器上,用戶刷新一次訪問可能會發(fā)現(xiàn)需要請登錄,解決這個問題,使用redis將用戶的session進行集中管理,只要保證redis是高可用、擴展性,每次用戶更新或查詢登錄信息都直接從redis中集中獲取

4. 限速

場景:用戶登錄使用手機短信驗證碼,確定是否用戶本人,為了短信接口不被頻繁訪問,會限制用戶每分鐘獲取驗證碼的頻率,例如一分鐘不超過5次,實現(xiàn)思路:

public function shortMessageLimit($phoneNumber) {
    $key = 'shortMessage:limit:'. $phoneNumber;
    // set key value ex 60 nx
    $isExists = redis.set($key, 1, 'ex 60', 'nx');
    if ($isExists || redis.incr($key) <= 5) {
        //通過
    } else {
        // 不通過
    }
}

哈希(hash)

哈希類型本身是指鍵值對本身又是一種鍵值對結(jié)構(gòu),如value = { {field1, value1}, ... {field2, value2}};

1. 命令

(1) 設(shè)置值 -- hset key field value
127.0.0.1:6379> hset user:1 name tom
(integer) 1

如果設(shè)置成功返回1,否則返回0,此外redis提供hsetnx命令,作用域是field

127.0.0.1:6379> hsetnx user:1 age 20
(integer) 1
127.0.0.1:6379> hget user:1 age
"20"
(2) 獲取值 -- hget key field
127.0.0.1:6379> hget user:1 name
"tom"

如果鍵或field存在返回值,不存在返回nil

127.0.0.1:6379> hget user:1 *
(nil)
(3) 刪除值 -- hdel key field [field ...]

hdel 刪除一個或多個field

127.0.0.1:6379> hdel user:1 name age
(integer) 2
127.0.0.1:6379> hget user:1  age
(nil)
(4) 計算field 個數(shù) -- hlen key
127.0.0.1:6379> hlen user:1
(integer) 0
(5)批量設(shè)置或獲取field-value -- hmset key field value [field value ...] /hmget key field [field ...]
127.0.0.1:6379> hmset user:1 name john age 20 sex man
OK
127.0.0.1:6379> mget user:1 name age
1) (nil)
2) (nil)
3) (nil)
127.0.0.1:6379> hmget user:1 name age
1) "john"
2) "20"
(6) 判斷field是否存 -- hexists key field
127.0.0.1:6379> hexists user:1 name1
(integer) 0
127.0.0.1:6379> hexists user:1 name
(integer) 1
(7) 獲取所有field -- hkeys key

127.0.0.1:6379> hkeys user:1
1) "name"
2) "age"
3) "sex"
(8) 獲取所有value -- hvals key
127.0.0.1:6379> hvals user:1
1) "john"
2) "20"
3) "man"
(9)獲取所有field - value -- hgetall key
127.0.0.1:6379> hgetall user:1
1) "name"
2) "john"
3) "age"
4) "20"
5) "sex"
6) "man"
(10) hincrby key field / hincrbyfloat key value
127.0.0.1:6379> hincrby user:1 name 1
(error) ERR hash value is not an integer
127.0.0.1:6379> hincrby user:1 age 1
(integer) 21
(11) 計算value長度 -- hstrlen key field (3.2 版本以上)
127.0.0.1:6379> hstrlen user:1 name
(integer) 4

2. 內(nèi)部編碼

哈希類型的內(nèi)部編碼有2種:

ziplist (壓縮列表)

哈希類型元素個數(shù)小于hash-max-ziplist-entries配置(512個) 同時所有值都小于hash-max-ziplist-value配置(默認64字節(jié))

ziplist使用更加緊湊的結(jié)構(gòu)實現(xiàn)多個元素的連續(xù)存儲,節(jié)省內(nèi)存方面比hashtable更加優(yōu)秀

hashtable (哈希表)

當哈希類型無法滿足ziplist的條件時,redis會使用hashtable作為哈希內(nèi)部實現(xiàn)

演示:

  • 當field個數(shù)比較小且沒有大value時,內(nèi)部編碼為ziplist:
127.0.0.1:6379> hmset hashkey f1 v1 f2 v2
OK
127.0.0.1:6379> object encoding hashkey
"ziplist"
  • 當value大于64字節(jié),內(nèi)部編碼會有ziplist變?yōu)閔ashtable:
127.0.0.1:6379> hset hashkey f3 "this is a stirng bigger than 64this is a stirng bigger than 64this is a stirng bigger than 64this is a stirng bigger than 64this is a stirng bigger than 64this is a stirng bigger than 64this is a stirng bigger than 64this is a stirng bigger than 64this is a stirng bigger than 64this is a stirng bigger than 64this is a stirng bigger than 64this is a stirng bigger than 64this is a stirng bigger than 64 "
(integer) 1
127.0.0.1:6379> object encoding hashkey
"hashtable"
  • 當field個數(shù)超過512個,內(nèi)部編碼會有ziplist變?yōu)閔ashtable:
127.0.0.1:6379> hset hashkey f1 v1 f2 v2 f3 v3 ...省略... f513 v513
(integer) 1
127.0.0.1:6379> object encoding hashkey
"hashtable"

3. 使用場景

哈希類型儲存用戶信息
public function getUserInfo($id)
{
    //用戶id作為key后綴
    $userRedisKey = "user:info:" . $id;
    // 使用hgetall獲取所有用戶信息映射關(guān)系
    $userInfoMap = redis.hgetall($userRedisKey);
    if ($userInfoMap) {
    // 將映射關(guān)系轉(zhuǎn)換為$userInfo
    $userInfo = transferMapToUserInfo($userInfoMap);
        
    } else {
        // 從Mysql中獲取用戶信息
        $userInfo = mysql.get($id)
        // 將userIndo變?yōu)橛成潢P(guān)系使用 hmset保存到redis中
        redis.hmset($userRedisKey, transferMapToUserInfo($userInfo ));
        // 添加過期時間
        redis.expire($userRedisKey, 3600);
    }
    return $userInfo;
}
哈希類型和關(guān)系型數(shù)據(jù)庫有兩種不同之處:
  • 哈希類型是稀疏的,而關(guān)系數(shù)據(jù)庫是完成結(jié)構(gòu)化,
  • 關(guān)系型數(shù)據(jù)庫做復(fù)雜關(guān)系查詢,redis去模擬關(guān)系型復(fù)雜查詢開發(fā)困難,維護成本高
三種方案緩存用戶信息,實現(xiàn)方法和優(yōu)缺點:
(1) 原生字符串類型:每個屬性一個鍵 (一般不采用)

set user:1:name tom

set user:1:age 20

set user:1:city beijing

優(yōu)點: 簡單直觀,每個屬性都支持更新操作

缺點:占用過多的鍵,內(nèi)存占用量較大,同時用戶信息內(nèi)聚性比較差

(2) 序列化字符串類型:將用戶信息序列化后用一個鍵保存

set user:1 serialize($userInfo)

優(yōu)點:簡化編程,如果合理使用序列化可以提高內(nèi)存的利用率

缺點:序列化和反序列化有一定的開銷,同時每次更新屬性都需要把全部數(shù)據(jù)讀取出來進行反序列化,更新后序列化到redis中

(3) 哈希類型:每一個屬性使用一對field-value,但是只用一個鍵保存

hmset user:1 name tom age 23 city beijing

優(yōu)點:簡單直觀,如果合理可以減少內(nèi)存空間的使用

缺點:要控制哈希在ziplist和hahstable兩種內(nèi)部編碼的轉(zhuǎn)換,hashtable會消耗內(nèi)存


列表(list)

列表(list)類型是用來存儲多個有序的字符串,如a,b,c,d,..多一個元素從左到右組成一個有序的列表。

列表中的每個字符串稱為元素(element),一個列表最多可以儲存2^32-1

在reids中,可以對列表兩端插入push 和彈出pop,還可以獲取指定范圍的元素列表、獲取指定索引下標的元素等

數(shù)據(jù)結(jié)構(gòu)比較靈活,可以充當棧和隊列的角色

1. 特點:

1. 有序的

可以通過索引下標獲取某個元素或某個范圍內(nèi)的元素的列表

2. 列表中元素可以重復(fù)

2. 命令

操作類型 操作
添加 rpush lpush linsert
lrange lindex llen
刪除 lpop rpop lrem ltrim
修改 lset
阻塞操作 blpop brpop
添加操作
(1)從右邊插入元素 -- rpush key value [value ...]
127.0.0.1:6379> rpush listkey c b a
(integer) 3
(2) 從左邊插入元素 -- lpush key value [value ...]
127.0.0.1:6379> lpush listkey a b c
(integer) 6
127.0.0.1:6379> lrange listkey 0 -1
1) "c"
2) "b"
3) "a"
4) "c"
5) "b"
6) "a"
(3) 向某個元素前或后插入元素 -- linsert listkey before/after pivot value

**pivot **指的是列表中插入元素前后相對的那個元素

127.0.0.1:6379> lrange listkey 0 -1
1) "c"
2) "b"
3) "a"
4) "c"
5) "b"
6) "a"
127.0.0.1:6379> linsert listkey before c d
(integer) 7
127.0.0.1:6379> lrange listkey 0 -1
1) "d"
2) "c"
3) "b"
4) "a"
5) "c"
6) "b"
7) "a"
127.0.0.1:6379> linsert listkey after a e
(integer) 8
127.0.0.1:6379> lrange listkey 0 -1
1) "d"
2) "c"
3) "b"
4) "a"
5) "e"
6) "c"
7) "b"
8) "a"
查找
(1 ) 獲取指定范圍內(nèi)的元素列表 -- lrange key start stop

從左到右獲取列表的所有元素

127.0.0.1:6379> lrange listkey 0 -1
1) "c"
2) "b"
3) "a"

特點:

  • 所以下標從左到右0 到 N-1,但是從右到左分別是-1 到-N
  • lrange中的end選項包含了自身
(2) 獲取列表指定索引下標的元素 -- lindex key value
127.0.0.1:6379> lrange listkey 0 -1
1) "d"
2) "c"
3) "b"
4) "a"
5) "e"
6) "c"
7) "b"
8) "a"
127.0.0.1:6379> lindex listkey 5
"c"
127.0.0.1:6379> lindex listkey -1
"a"
(3) 獲取列表長度 -- llen key
127.0.0.1:6379> llen listkey
(integer) 8
刪除
(1) 從列表左側(cè)彈出元素 -- lpop key
127.0.0.1:6379> lpop listkey
"d"
127.0.0.1:6379> lrange listkey 0 -1
1) "c"
2) "b"
3) "a"
4) "e"
5) "c"
6) "b"
7) "a"
127.0.0.1:6379> llen listkey
(integer) 7
(2)從列表右側(cè)彈出 -- rpop key
127.0.0.1:6379> rpop listkey
"a"
127.0.0.1:6379> lrange listkey 0 -1
1) "c"
2) "b"
3) "a"
4) "e"
5) "c"
6) "b"
127.0.0.1:6379> llen listkey
(integer) 6
(3) 刪除指定 元素 -- lrem key count value

根據(jù)count的不同分3中情況:

  • count > 0 , 從左到右,刪除最多count個元素
  • count < 0, 從右到左,刪除最多count絕對值元素
  • count = 0 , 刪除所有
127.0.0.1:6379> lrem listkey 4 a
(integer) 1
127.0.0.1:6379> lrange listkey 0 -1
1) "c"
2) "b"
3) "e"
4) "c"
5) "b"
127.0.0.1:6379> lpush listkey a a a a a
(integer) 10
127.0.0.1:6379> lrange listkey 0 -1
 1) "a"
 2) "a"
 3) "a"
 4) "a"
 5) "a"
 6) "c"
 7) "b"
 8) "e"
 9) "c"
10) "b"
127.0.0.1:6379> lrem listkey 0 a
(integer) 5
127.0.0.1:6379> lrange listkey 0 -1
1) "c"
2) "b"
3) "e"
4) "c"
5) "b"
(4)按照索引范圍修剪列表 -- ltrim key start end
127.0.0.1:6379> lrange listkey 0 -1
1) "c"
2) "b"
3) "e"
4) "c"
5) "b"
127.0.0.1:6379> ltrim listkey 0 2
OK
127.0.0.1:6379> lrange listkey 0 -1
1) "c"
2) "b"
3) "e"
修改
(1) 修改指定索引下標的元素 -- lset key index newValue
127.0.0.1:6379> lrange listkey 0 -1
1) "c"
2) "b"
3) "e"
127.0.0.1:6379> lset listkey 2 a
OK
127.0.0.1:6379> lrange listkey 0 -1
1) "c"
2) "b"
3) "a"
阻塞操作

blpop key [key ...] timeout

brpop key [key ...] timeout

參數(shù)說明:

  • key [key ...] : 多個列表的鍵
  • timeout: 阻塞時間(單位:秒)

(1)列表為空:如果timeout=3,那么客戶端要等3秒后返回,如果timeout為0,那么客戶端一直等下去

127.0.0.1:6379> brpop list:test 3
(nil)
(3.08s)
127.0.0.1:6379> brpop list:test 0

(2)列表不為空:客戶端會立馬返回

另開客戶端添加元素,立馬返回元素

// 添加元素客戶端
127.0.0.1:6379> rpush list:test element1
(integer) 1
127.0.0.1:6379>
// 阻塞客戶端
127.0.0.1:6379> brpop list:test 0
1) "list:test"
2) "element1"
(28.96s)

注意:

1-1 如果是過鍵,brpop會從左到右遍歷鍵,一旦有一個鍵能彈出元素,客戶端立即返回

1-2 如果多個客戶端對同一個鍵執(zhí)行brpop,那么最先執(zhí)行rpop命令的客戶端最先獲取彈出的值

內(nèi)部編碼

列表類型的內(nèi)部編碼有兩種

ziplist(壓縮列表)

當列表的元素個數(shù)小于 list-max-ziplist-entries配置(默認512個),同時列表中每個元素的值都小于list-max-ziplist-value配置(默認64字節(jié))

redis選用ziplist來作為列表的內(nèi)部實現(xiàn)減少內(nèi)存的使用

linkedlist(鏈表)

當列表類型無法滿足ziplist的條件時,redis會使用linkedlist作為列表的內(nèi)部實現(xiàn)

quicklist

它是一個以ziplist為節(jié)點的linkedlist,它結(jié)合了ziplist和linkedlist兩者優(yōu)勢,為列表類型提供更為優(yōu)秀的內(nèi)部編碼實現(xiàn)

內(nèi)部編碼變化示例:

(1)當元素格式較少且沒有大元素時,內(nèi)部編碼為ziplist

(2) 當元素個數(shù)超過512個,內(nèi)部編碼變?yōu)閘inkedlist

(3)或當某個元素超過64字節(jié),內(nèi)部編碼變?yōu)閘inkedlist

使用場景

1. 消息隊列

redis使用 lpush + brpop 命令組合即可顯示阻塞隊列,生產(chǎn)者客戶端使用lpush從列表左側(cè)插入元素,多個消費者客戶端使用brpop命令阻塞式“搶”列表尾部的元素,多個客戶端保證了消費的負載均衡和高可用性

2. 文章列表

每個用戶屬于自己的文章列表,現(xiàn)需要分頁展示文章列表,此時可以考慮使用列表,因列表不但有序,而且支持按照索引范圍獲取元素

(1) 每篇文章使用哈希結(jié)構(gòu)儲存,例如每篇文章有3個屬性tittle,timestamp, content

127.0.0.1:6379> hmset article:1 title xx timestamp 1476536196 content xxx
OK
127.0.0.1:6379> hmset article:2 title xx1 timestamp 1476536196 content xxx
OK
127.0.0.1:6379> hmset article:3 title xx2 timestamp 1476536196 content xxx
OK

(2 ) 向用戶文章列表添加文章,user : {id}: articles作為用戶文章列表的鍵

127.0.0.1:6379> lpush user:1:aticles article:1 article:2 article:3
(integer) 3
..

(3) 分頁獲取文章列表,例如 先偽代碼獲取用戶id=1的前10篇文章:

articles = lrange user:1:articles 0 -1
for article in {article}
    hgetall {article}


127.0.0.1:6379> lrange user:1:aticles 0 -1
1) "article:3"
2) "article:2"
3) "article:1"
127.0.0.1:6379> hgetall article:1
1) "title"
2) "xx"
3) "timestamp"
4) "1476536196"
5) "content"
6) "xxx"

列表保存和獲取文章列表會存在兩個問題:

第一,如果每次分頁獲取的文章個數(shù)較多,需要執(zhí)行多車hgetall操作,

可以考慮Pipline批量獲取

或?qū)⑽恼聰?shù)據(jù)序列化為字符串,使用mget批量獲取,

第二,分頁獲取文章列表時,lrange命令在列表兩端性能較好,如果列表較大,獲取列表中間范圍的元素性能會變差,此時考慮將列表做二級拆分;獲者使用redis3.2quicklist內(nèi)部編碼實現(xiàn),它結(jié)合ziplist和linkedlist特點,獲取列表中間范圍的元素時可以高效完成

實際開發(fā)場景使用口訣

1. lpush + lpop = Stack (棧)
2. lpush + rpop = Queue(隊列)
3. lpush + ltrim = Capped Collection(有限集合)
4. lpush + brpop = Message Queue (消息隊列)

集合(set)

定義:

用來保存多個的字符串元素

特點:

不允許元素重復(fù)
集合中元素?zé)o序
不能通過索引下標獲取元素

命令

除了增刪改查,還支持集合合取交集、并集、差集, 下面將集合內(nèi)集合間兩個維度對集合的常用命令介紹

集合內(nèi)操作
(1)添加元素 -- sadd key element [element ...]

添加成功返回添加成功的元素個數(shù)

127.0.0.1:6379> sadd key element1 element2 element3
(integer) 3
127.0.0.1:6379> exists key
(integer) 1
127.0.0.1:6379> exists myset
(integer) 0
127.0.0.1:6379> sadd myset a b c
(integer) 3
127.0.0.1:6379> sadd myset b
(integer) 0
(2)刪除元素 -- srem key element [element ...]
127.0.0.1:6379> srem key element1
(integer) 1
(3)計算元素個數(shù) -- scard key
127.0.0.1:6379> scard key
(integer) 2
127.0.0.1:6379> scard myset
(integer) 3
(4)判斷元素是否存在集合中 -- sismember key element

存在返回1, 不存在返回0

127.0.0.1:6379> sismember key element1
(integer) 0
127.0.0.1:6379> sismember key element2
(integer) 1
(5)隨機從集合返回指定個數(shù)元素 -- srandmember key [count]

[count] 是可選參數(shù),如果不選默認為1

127.0.0.1:6379> srandmember key 3
1) "element2"
2) "element3"
127.0.0.1:6379> srandmember myset 3
1) "b"
2) "c"
3) "a"
(6)從集合隨機彈出元素 -- spop key count

從集合中隨機彈出一個元素,redis3.2版本支持count參數(shù)

127.0.0.1:6379> spop key 2
1) "element2"
2) "element3"
127.0.0.1:6379> spop key 2
(empty list or set)
127.0.0.1:6379> spop myset 1
1) "c"

spop 和srandmember 區(qū)別是: spop彈出元素后會從集合中刪除,而srandmember不會

(7)獲取所有元素 -- smembers key

返回的結(jié)果是無序的

127.0.0.1:6379> smembers key
(empty list or set)
127.0.0.1:6379> smembers myset
1) "b"
2) "a"

smembers 和lrange 、hgetall都屬于比較重的命令,如果元素過多存阻塞的可能性,可以使用sscan 來完成

集合間操作

示例:現(xiàn)有兩個集合,分別為user:1:follow 和 user:2:follow

127.0.0.1:6379> sadd user:1:follow it music his sports
(integer) 4
127.0.0.1:6379> sadd user:2:follow it new ent sports
(integer) 4
(1) 求多個集合的交集 -- sinter key [key ...]
127.0.0.1:6379> sinter user:1:follow user:2:follow
1) "it"
2) "sports"
127.0.0.1:6379>
(2)求多個集合的并集 -- sunion key [key...]
127.0.0.1:6379> sunion user:1:follow user:2:follow
1) "sports"
2) "ent"
3) "music"
4) "it"
5) "new"
6) "his"
(3)求多個集合的差集 -- sdiff key [key ...]
127.0.0.1:6379> sdiff user:1:follow user:2:follow
1) "his"
2) "music"
(4) 將交集的結(jié)果保存 -- sinterstore destination key [key ...]
127.0.0.1:6379> sinterstore user:1_2:follow user:1:follow user:2:follow
(integer) 2
127.0.0.1:6379> smembers user:1_2:follow
1) "it"
2) "sports"
(5) 將并集的結(jié)果保存 -- sunionstore destionation key [key ...]
127.0.0.1:6379> sunionstore user:1_2:union user:1:follow user:2:follow
(integer) 6
127.0.0.1:6379> smembers user:1_2:union
1) "sports"
2) "ent"
3) "music"
4) "it"
5) "new"
6) "his"
(6) 將差集的結(jié)果保存 -- sdiffstore destionation key [key ...]
127.0.0.1:6379> sdiffstore user:1_2:diff user:1:follow user:2:follow
(integer) 2
127.0.0.1:6379> smembers user:1_2:diff
1) "his"
2) "music"

內(nèi)部編碼

有兩種內(nèi)部編碼:

intset(整數(shù)集合)

當集合中的元素都是整數(shù)元素個數(shù) 小于set-max-intset-entries配置(默認512個)時,redis會選用intset作為集合的內(nèi)部實現(xiàn),從而減少內(nèi)存的使用

hashtable(哈希表)

當集合類型無法滿足intset的條件時,redis使用hashtable作為集合的內(nèi)部實現(xiàn)

示例:

(1)當元素個數(shù)較少且都為整數(shù)時,內(nèi)部編碼為intset

127.0.0.1:6379> sadd setkey 1 2 3 4
(integer) 4
127.0.0.1:6379> object encoding setkey
"intset"

(2)當元素個數(shù)超過512個,內(nèi)部編碼為hastable

(3) 當某個元素不為整數(shù)時,內(nèi)部編碼也會變?yōu)閔ashtable

127.0.0.1:6379> sadd setkey 'abc'
(integer) 1
127.0.0.1:6379> smembers setkey
1) "3"
2) "1"
3) "2"
4) "4"
5) "abc"
127.0.0.1:6379> object encoding setkey
"hashtable"

使用場景

集合類型比較經(jīng)典的使用場景是標簽(tag)
使用集合類型實現(xiàn)標簽功能:
(1)給用戶添加標簽
127.0.0.1:6379> sadd user:1:tags tag1 tag2 tag3
(integer) 3
127.0.0.1:6379> sadd user:2:tags tag1 tag2 tag3
(integer) 3
127.0.0.1:6379> sadd user:3:tags tag1 tag2 tag3
(2)給標簽添加用戶
127.0.0.1:6379> sadd tag1:users user:1 user:3
(integer) 2
127.0.0.1:6379> sadd tag2:users user:2 user:1 user:3
(integer) 3
(3) 刪除用戶下的標簽
127.0.0.1:6379> srem user:1:tags tag1
(integer) 1
(4) 刪除標簽下的用戶
127.0.0.1:6379> srem tag2:users user:1
(integer) 1

(3)和(4)也是盡量放在一個事物執(zhí)行

(5)計算用戶共感興趣的標簽
127.0.0.1:6379> sinterstore user:1_2:tags user:1:tags user:2:tags
(integer) 2
127.0.0.1:6379> smembers user:1_2:tags
1) "tag3"

實際應(yīng)用場景

1 sadd = Tagging(標簽)
2 spop/srandmember = Random item(生成水數(shù),比如抽獎)
3 sadd + sinter = social graph(社交需求)

有序集合(sorted set)

特點

保留了集合不能有重復(fù)成員,但score可重復(fù)
集合元素可排序
為每個元素設(shè)置一個分數(shù)(score)作為排序的依據(jù),

列表、集合、和有序集合的區(qū)別

數(shù)據(jù)結(jié)果 是否允許元素重復(fù) 是否有序 有序?qū)崿F(xiàn)方式 應(yīng)用場景
列表 索引下標 時間軸、消息隊列
集合 標簽、社交
有序集合 分值 排行榜系統(tǒng)、社交

命令

按照集合內(nèi)和集合外兩個維度進行命令介紹

集合內(nèi)操作
(1)添加成員 -- zadd key [NX|XX] [CH] [INCR] score member [score member ...]

redis 3.2 命令添加nx、xx、ch、incr個選項:

  • nx: member必須不存在,才可以設(shè)置成功,用于添加
  • xx: member必須存在,才可設(shè)置成功,用于更新
  • ch: 返回此次操作后,有序集合元素和分數(shù)發(fā)生變化的個數(shù)
  • incr: 對score做增加,相當于后面介紹zincrby
127.0.0.1:6379> zadd user:ranking nx ch 1 kris 91 mike 200 frank 250 martin
(integer)
(2)計算成員個數(shù) -- zcard key
127.0.0.1:6379> zcard key
(integer) 0
127.0.0.1:6379> zcard user:ranking
(integer) 4
(3)計算某個成員的分數(shù) -- zscore key member
127.0.0.1:6379> zscore user:ranking  kris
"1"
127.0.0.1:6379> zscore user:ranking frank
"200"
(4)計算成員的排名 -- zrank/zrevrank key member
127.0.0.1:6379> zrank user:ranking mike
(integer) 1
127.0.0.1:6379> zrank user:ranking kris
(integer) 0
127.0.0.1:6379> zrevrank user:ranking frank
(integer) 1
(5)刪除成員 -- zrem key member [member...]

返回結(jié)果為成功的刪除的個數(shù)

127.0.0.1:6379> zrem user:ranking kris
(integer) 1
(6)增加成員的分數(shù) -- zincrby key increment member
127.0.0.1:6379> zincrby user:ranking 10 mike
"101"
(7)返回指定排名范圍的成員 -- zrange/zrevrange key start stop [WITHSCORES]

WITHSCORES : 返回每個成員的分數(shù)

127.0.0.1:6379> zrange user:ranking 0 -1
1) "mike"
2) "frank"
3) "martin"

127.0.0.1:6379> zrevrange user:ranking 0 -1
1) "martin"
2) "frank"
3) "mike"

127.0.0.1:6379> zrevrange user:ranking 0 -1 withscores
1) "martin"
2) "250"
3) "frank"
4) "200"
5) "mike"
6) "101"
(8)返回指定分數(shù)范圍的成員 -- zrangebyscore key min max [WITHSCORES] [LIMIT offset count]

WITHSCORES : 返回每個成員的分數(shù)

LIMIT offset count : 選項可以限制輸出的起始位置和個數(shù)

同時min和max還支持開區(qū)間(小括號)閉區(qū)間(中括號) ,-inf 和 + inf分別代表無限小和無限大

127.0.0.1:6379> zrangebyscore user:ranking 50 120
1) "mike"
127.0.0.1:6379> zrangebyscore user:ranking 50 120 withscores
1) "mike"
2) "101"

127.0.0.1:6379> zrangebyscore user:ranking (200 +inf withscores
1) "martin"
2) "250"
(9) 返回指定分數(shù)范圍成員個數(shù) -- zcount key min max
127.0.0.1:6379> zcount user:ranking 150 250
(integer) 2
127.0.0.1:6379> zrange user:ranking 150 250 withscores
(empty list or set)
127.0.0.1:6379> zrange user:ranking 0 -1 withscores
1) "mike"
2) "101"
3) "frank"
4) "200"
5) "martin"
6) "250"
(10)刪除指定排名的升序元素--zremrangebyrank key start stop
127.0.0.1:6379> zremrangebyrank user:ranking 0 -1
(integer) 3
(0.62s)
127.0.0.1:6379> zrange user:ranking 0 -1 withscores
(empty list or set)
(11)刪除指定分數(shù)范圍的成員 -- zremrangebyscore key min max
127.0.0.1:6379> zadd user:ranking nx ch 1 kris 1 milk 200 frank 250 martin
(integer) 2

127.0.0.1:6379> zrevrange user:ranking 0 -1 withscores
1) "martin"
2) "250"
3) "frank"
4) "100"
5) "milk"
6) "1"
7) "kris"
8) "1"
127.0.0.1:6379> zremrangebyscore user:ranking 0 10
(integer) 2
127.0.0.1:6379> zrevrange user:ranking 0 -1 withscores
1) "martin"
2) "250"
3) "frank"
4) "100"
集合間操作

示例:現(xiàn)有兩個有序集合

鍵名user:ranking:1 鍵名user:ranking:2
score member score member
1 kris 8 james
91 mike 77 mike
200 frank 625 martin
251 tom 888 tom
220 trim
250 martin
127.0.0.1:6379> zadd user:ranking:1 1 kris 91 mike 200 frank 220 trim 250 martin 251 tom
(integer) 6
127.0.0.1:6379> zadd user:ranking:2 8 james 77 mike 625 martin 888 tom
(integer) 4
(1)交集 -- zinterstore destination numkeys key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX]

參數(shù)說明:

destination:交集計算結(jié)果保存到這個鍵

numbekeys:需要做交集計算鍵的個數(shù)

key [key...]:需做交集計算 的鍵

WEIGHTS weight:每個鍵的權(quán)重,在交集計算中,每個鍵中的每個member會將自己分數(shù)乘以這個權(quán)重,每個權(quán)重默認為1

[AGGREGATE SUM|MIN|MAX]:計算 成員交集后,分值可以按照sum、min和max做匯總

127.0.0.1:6379> zinterstore user:ranking:1_inter_2 2 user:ranking:1 user:ranking:2
(integer) 3
127.0.0.1:6379> zrange user:ranking:1_inter_2 0 -1 withscores
1) "mike"
2) "168"
3) "martin"
4) "875"
5) "tom"
6) "1139"

將 user:ranking:2 的權(quán)重變?yōu)?.5, 并且聚合效果使用max, 可以如下執(zhí)行:

127.0.0.1:6379> zinterstore user:ranking:1_inter_2 2 user:ranking:1 user:ranking:2 weights 1 0.5 aggregate max
(integer) 3
127.0.0.1:6379> zrange user:ranking:1_inter_2 0 -1 withscores
1) "mike"
2) "91"
3) "martin"
4) "312.5"
5) "tom"
6) "444"

結(jié)果分析:

tom score 888 * 0.5 = 444 ,但聚合是max,取那個最大的,user:ranking:2 tom score 444 大于 user:ranking:1 的tom score 251 ,所以結(jié)果才是444; 同理,mike和martin也是這樣得出來的

(2)并集 -- zunionstore destination numkeys key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX]

user:ranking:1 和 user:ranking:2 做并集,weights和aggregate使用了默認配置,進行了sum操作

127.0.0.1:6379> zunionstore user:ranking:1_union_2 2 user:ranking:1 user:ranking:2
(integer) 7
127.0.0.1:6379> zrange user:ranking:1_union_2 0 -1 withscores
 1) "kris"
 2) "1"
 3) "james"
 4) "8"
 5) "mike"
 6) "168"
 7) "frank"
 8) "200"
 9) "trim"
10) "220"
11) "martin"
12) "875"
13) "tom"
14) "1139"

內(nèi)部編碼

內(nèi)部編碼有2種:

ziplist(壓縮列表)

當有序集合的元素個數(shù)小于zset-max-ziplist-entries配置(默認128個),同時每個元素的值都小于zset-max-ziplist-value配置(默認64字節(jié)),可以有效減少內(nèi)存的使用

skiplist(跳躍表)

當ziplist條件不滿足時,有序集合會使用skiplist作為內(nèi)部實現(xiàn),因此讀寫效率會下降

示例說明:

(1)當元素個數(shù)較少且每個元素較小時,內(nèi)部編碼為ziplist

127.0.0.1:6379> object encoding user:ranking
"ziplist"

(2) 當元素個數(shù)超過128個,內(nèi)部編碼為skiplist

(3)某個元素大于64字節(jié)時,內(nèi)部編碼也會變?yōu)閟kiplist

使用場景

典型的場景為是排行榜系統(tǒng)
以視頻榜單贊數(shù)實現(xiàn)4個功能:
(1) 添加用戶贊數(shù)

用戶user_mike獲取3個贊,后面又多個一個贊

127.0.0.1:6379> zadd user:ranking:20190210 3 user_mike
(integer) 1
127.0.0.1:6379> zincrby user:ranking:20190210 1 user_mike
"4"
(2)取消用戶贊數(shù)

由于各種元素,用戶注銷、作弊等需要取消用戶贊數(shù)

127.0.0.1:6379> zrem user:ranking:20190210 mike
(integer) 0
(3) 展示獲取贊數(shù)最多的10個用戶
127.0.0.1:6379> zrevrange user:ranking 0 9 withscores
1) "martin"
2) "250"
3) "frank"
4) "100"
(4)展示用戶信息以及用戶分數(shù)
127.0.0.1:6379> hgetall user:info:tom
(empty list or set)
127.0.0.1:6379> zscore user:ranking martin
"250"
127.0.0.1:6379> zrank user:ranking martin
(integer) 1

創(chuàng)作不易,覺得不錯的話,歡迎關(guān)注、點贊??或掌賞!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容