Redis-5种基本类型结构

笔记来自:《redis开发与维护》第二章 API的理解和使用
主要内容:redis 5种数据结构:string、hash 、list 、set 、sorted set

字符串(String)

键都是字符串类型,所以其他数据结构都是在字符串类型基础上构建的

字符串类型的值最大不超过512MB

1. 常用命令

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

[ex seconds] :设置秒级过期时间

[px seconds]:设置毫秒级过期时间

[nx|xx]:

nx 键不存在,才可设置成功,用于添加;

xx 键必须存在,才可设置成功,用于更新, 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)批量 设置值 -- 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) 计数 -- incr key

incr命令用于做自增操作,返回结果分三种情况:

  • 值不是整数,返回错误
  • 值是整数,返回自增后的结果
  • 键不存在,按照为0自增,返回结果为1

例子:

  1. 键不存自增

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

    返回1

  2. 值不是整数自增

    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. 值为整数自增
127.0.0.1:6379> set testkey 10
OK
127.0.0.1:6379> incr testkey
(integer) 11

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

  • 自减 decr

  • 自增指定数字 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
    
  • 自减指定数字 decrby

  • 自增浮点数 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)设置并返回原值 -- 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)设置指定位置的字符 -- 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. 内部编码

Redis 会根据当前值的类型和长度决定使用哪种内部编码实现。

字符串类型编码3种:
int: 8个字节长度
embstr: 小于等于39个字节长度
raw: 大于39个字节的字符串
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) 模拟访问获取数据过程

  • 首先从redis获取用户信息
  • 如果没有从redis获取用户信息,需要从mysql中读取,并将结构返回写到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获取数据
        $userInfo = mysql.get($id);
        if ($userInfo) {
            redis.setex($userRedisKey, 3600, json_encode($userInfo));
        }
    }
    return $userInfo;
}
2. 计数

例如: 实现视频播放数计数,用户每播放一次,播放次数自增1

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

分布式服务将用户的访问均衡到不同服务器上,用户刷新一次访问可能会发现需要请登录,解决这个问题,使用redis将用户的session进行集中管理,只要保证redis是高可用、扩展性,每次用户更新或查询登录信息都直接从redis中集中获取

4. 限速

场景:用户登录使用手机短信验证码,确定是否用户本人,为了短信接口不被频繁访问,会限制用户每分钟获取验证码的频率,例如一分钟不超过5次,实现思路:

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)

哈希类型本身是指键值对本身又是一种键值对结构,如value = { {field1, value1}, ... {field2, value2}};

1. 命令

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

如果设置成功返回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 个数 -- hlen key
127.0.0.1:6379> hlen user:1
(integer) 0
(5)批量设置或获取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. 内部编码

哈希类型的内部编码有2种:

ziplist (压缩列表)

哈希类型元素个数小于hash-max-ziplist-entries配置(512个) 同时所有值都小于hash-max-ziplist-value配置(默认64字节)

ziplist使用更加紧凑的结构实现多个元素的连续存储,节省内存方面比hashtable更加优秀

hashtable (哈希表)

当哈希类型无法满足ziplist的条件时,redis会使用hashtable作为哈希内部实现

演示:

  • 当field个数比较小且没有大value时,内部编码为ziplist:
127.0.0.1:6379> hmset hashkey f1 v1 f2 v2
OK
127.0.0.1:6379> object encoding hashkey
"ziplist"
  • 当value大于64字节,内部编码会有ziplist变为hashtable:
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个数超过512个,内部编码会有ziplist变为hashtable:
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获取所有用户信息映射关系
    $userInfoMap = redis.hgetall($userRedisKey);
    if ($userInfoMap) {
    // 将映射关系转换为$userInfo
    $userInfo = transferMapToUserInfo($userInfoMap);
        
    } else {
        // 从Mysql中获取用户信息
        $userInfo = mysql.get($id)
        // 将userIndo变为映射关系使用 hmset保存到redis中
        redis.hmset($userRedisKey, transferMapToUserInfo($userInfo ));
        // 添加过期时间
        redis.expire($userRedisKey, 3600);
    }
    return $userInfo;
}
哈希类型和关系型数据库有两种不同之处:
  • 哈希类型是稀疏的,而关系数据库是完成结构化,
  • 关系型数据库做复杂关系查询,redis去模拟关系型复杂查询开发困难,维护成本高
三种方案缓存用户信息,实现方法和优缺点:
(1) 原生字符串类型:每个属性一个键 (一般不采用)

set user:1:name tom

set user:1:age 20

set user:1:city beijing

优点: 简单直观,每个属性都支持更新操作

缺点:占用过多的键,内存占用量较大,同时用户信息内聚性比较差

(2) 序列化字符串类型:将用户信息序列化后用一个键保存

set user:1 serialize($userInfo)

优点:简化编程,如果合理使用序列化可以提高内存的利用率

缺点:序列化和反序列化有一定的开销,同时每次更新属性都需要把全部数据读取出来进行反序列化,更新后序列化到redis中

(3) 哈希类型:每一个属性使用一对field-value,但是只用一个键保存

hmset user:1 name tom age 23 city beijing

优点:简单直观,如果合理可以减少内存空间的使用

缺点:要控制哈希在ziplist和hahstable两种内部编码的转换,hashtable会消耗内存


列表(list)

列表(list)类型是用来存储多个有序的字符串,如a,b,c,d,..多一个元素从左到右组成一个有序的列表。

列表中的每个字符串称为元素(element),一个列表最多可以储存2^32-1

在reids中,可以对列表两端插入push 和弹出pop,还可以获取指定范围的元素列表、获取指定索引下标的元素等

数据结构比较灵活,可以充当栈和队列的角色

1. 特点:

1. 有序的

可以通过索引下标获取某个元素或某个范围内的元素的列表

2. 列表中元素可以重复

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 ) 获取指定范围内的元素列表 -- 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) 从列表左侧弹出元素 -- 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)从列表右侧弹出 -- 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

根据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

参数说明:

  • 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 如果多个客户端对同一个键执行brpop,那么最先执行rpop命令的客户端最先获取弹出的值

内部编码

列表类型的内部编码有两种

ziplist(压缩列表)

当列表的元素个数小于 list-max-ziplist-entries配置(默认512个),同时列表中每个元素的值都小于list-max-ziplist-value配置(默认64字节)

redis选用ziplist来作为列表的内部实现减少内存的使用

linkedlist(链表)

当列表类型无法满足ziplist的条件时,redis会使用linkedlist作为列表的内部实现

quicklist

它是一个以ziplist为节点的linkedlist,它结合了ziplist和linkedlist两者优势,为列表类型提供更为优秀的内部编码实现

内部编码变化示例:

(1)当元素格式较少且没有大元素时,内部编码为ziplist

(2) 当元素个数超过512个,内部编码变为linkedlist

(3)或当某个元素超过64字节,内部编码变为linkedlist

使用场景

1. 消息队列

redis使用 lpush + brpop 命令组合即可显示阻塞队列,生产者客户端使用lpush从列表左侧插入元素,多个消费者客户端使用brpop命令阻塞式“抢”列表尾部的元素,多个客户端保证了消费的负载均衡和高可用性

2. 文章列表

每个用户属于自己的文章列表,现需要分页展示文章列表,此时可以考虑使用列表,因列表不但有序,而且支持按照索引范围获取元素

(1) 每篇文章使用哈希结构储存,例如每篇文章有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"

列表保存和获取文章列表会存在两个问题:

第一,如果每次分页获取的文章个数较多,需要执行多车hgetall操作,

可以考虑Pipline批量获取

或将文章数据序列化为字符串,使用mget批量获取,

第二,分页获取文章列表时,lrange命令在列表两端性能较好,如果列表较大,获取列表中间范围的元素性能会变差,此时考虑将列表做二级拆分;获者使用redis3.2quicklist内部编码实现,它结合ziplist和linkedlist特点,获取列表中间范围的元素时可以高效完成

实际开发场景使用口诀

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

集合(set)

定义:

用来保存多个的字符串元素

特点:

不允许元素重复
集合中元素无序
不能通过索引下标获取元素

命令

除了增删改查,还支持集合合取交集、并集、差集, 下面将集合内集合间两个维度对集合的常用命令介绍

集合内操作
(1)添加元素 -- sadd key element [element ...]

添加成功返回添加成功的元素个数

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)计算元素个数 -- 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)随机从集合返回指定个数元素 -- srandmember key [count]

[count] 是可选参数,如果不选默认为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参数

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 区别是: spop弹出元素后会从集合中删除,而srandmember不会

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

返回的结果是无序的

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 来完成

集合间操作

示例:现有两个集合,分别为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) 将交集的结果保存 -- 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) 将并集的结果保存 -- 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) 将差集的结果保存 -- 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"

内部编码

有两种内部编码:

intset(整数集合)

当集合中的元素都是整数元素个数 小于set-max-intset-entries配置(默认512个)时,redis会选用intset作为集合的内部实现,从而减少内存的使用

hashtable(哈希表)

当集合类型无法满足intset的条件时,redis使用hashtable作为集合的内部实现

示例:

(1)当元素个数较少且都为整数时,内部编码为intset

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

(2)当元素个数超过512个,内部编码为hastable

(3) 当某个元素不为整数时,内部编码也会变为hashtable

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"

使用场景

集合类型比较经典的使用场景是标签(tag)
使用集合类型实现标签功能:
(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)也是尽量放在一个事物执行

(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"

实际应用场景

1 sadd = Tagging(标签)
2 spop/srandmember = Random item(生成水数,比如抽奖)
3 sadd + sinter = social graph(社交需求)

有序集合(sorted set)

特点

保留了集合不能有重复成员,但score可重复
集合元素可排序
为每个元素设置一个分数(score)作为排序的依据,

列表、集合、和有序集合的区别

数据结果 是否允许元素重复 是否有序 有序实现方式 应用场景
列表 索引下标 时间轴、消息队列
集合 标签、社交
有序集合 分值 排行榜系统、社交

命令

按照集合内和集合外两个维度进行命令介绍

集合内操作
(1)添加成员 -- zadd key [NX|XX] [CH] [INCR] score member [score member ...]

redis 3.2 命令添加nx、xx、ch、incr个选项:

  • nx: member必须不存在,才可以设置成功,用于添加
  • xx: member必须存在,才可设置成功,用于更新
  • ch: 返回此次操作后,有序集合元素和分数发生变化的个数
  • incr: 对score做增加,相当于后面介绍zincrby
127.0.0.1:6379> zadd user:ranking nx ch 1 kris 91 mike 200 frank 250 martin
(integer)
(2)计算成员个数 -- zcard key
127.0.0.1:6379> zcard key
(integer) 0
127.0.0.1:6379> zcard user:ranking
(integer) 4
(3)计算某个成员的分数 -- 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...]

返回结果为成功的删除的个数

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

WITHSCORES : 返回每个成员的分数

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)返回指定分数范围的成员 -- zrangebyscore key min max [WITHSCORES] [LIMIT offset count]

WITHSCORES : 返回每个成员的分数

LIMIT offset count : 选项可以限制输出的起始位置和个数

同时min和max还支持开区间(小括号)闭区间(中括号) ,-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) 返回指定分数范围成员个数 -- 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)删除指定分数范围的成员 -- 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"
集合间操作

示例:现有两个有序集合

键名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]

参数说明:

destination:交集计算结果保存到这个键

numbekeys:需要做交集计算键的个数

key [key...]:需做交集计算 的键

WEIGHTS weight:每个键的权重,在交集计算中,每个键中的每个member会将自己分数乘以这个权重,每个权重默认为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 的权重变为0.5, 并且聚合效果使用max, 可以如下执行:

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"

结果分析:

tom score 888 * 0.5 = 444 ,但聚合是max,取那个最大的,user:ranking:2 tom score 444 大于 user:ranking:1 的tom score 251 ,所以结果才是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"

内部编码

内部编码有2种:

ziplist(压缩列表)

当有序集合的元素个数小于zset-max-ziplist-entries配置(默认128个),同时每个元素的值都小于zset-max-ziplist-value配置(默认64字节),可以有效减少内存的使用

skiplist(跳跃表)

当ziplist条件不满足时,有序集合会使用skiplist作为内部实现,因此读写效率会下降

示例说明:

(1)当元素个数较少且每个元素较小时,内部编码为ziplist

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

(2) 当元素个数超过128个,内部编码为skiplist

(3)某个元素大于64字节时,内部编码也会变为skiplist

使用场景

典型的场景为是排行榜系统
以视频榜单赞数实现4个功能:
(1) 添加用户赞数

用户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)取消用户赞数

由于各种元素,用户注销、作弊等需要取消用户赞数

127.0.0.1:6379> zrem user:ranking:20190210 mike
(integer) 0
(3) 展示获取赞数最多的10个用户
127.0.0.1:6379> zrevrange user:ranking 0 9 withscores
1) "martin"
2) "250"
3) "frank"
4) "100"
(4)展示用户信息以及用户分数
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

创作不易,觉得不错的话,欢迎关注、点赞👍或掌赏!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,519评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,842评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,544评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,742评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,646评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,027评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,513评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,169评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,324评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,268评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,299评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,996评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,591评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,667评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,911评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,288评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,871评论 2 341

推荐阅读更多精彩内容