[redis] 10 种数据结构详解
简介
5种常见数据结构
- string: 最常见的 string key value
- list: 双向链表
- set: 集合-
- zset: 有序集合
- hash: 类似 Java HashMap 和 golang 的 map 有 2 级索引
原语------ | 介绍 | 常见场景 |
---|---|---|
string | string key/value 最常见的数据类型 | 最常用缓存; 分布式锁; 储存日志; id生成器; 计数器; 限速限流; |
hash | hashmap(成员少时数组, 多时升级为hashmap) | 更加细粒度缓存对象 |
list | 实现是双向链表或者说一个队列 | 消息队列; 待办事项; |
set | 不重复的列表, 自动去重, 类似数据集合概念(内部实现是一个 value 为 null 的 hashmap) | 唯一计数器; 打标签; 点赞;投票; 社交关系; 推荐商品; |
zset | 有序的set, 提供额外的 score 参数来排序(内部实现是 hashmap 和 跳表) | 排行榜; 推荐商品; 自动补全(字典排序) |
5种其他数据结构
- hypperLoglog: 基数统计
- bitmap: 位图(布隆过滤器)
- geo: 地理位置坐标
- pub/sub: 发布订阅
- stream: 流(主要用于消息队列, 解决 pub/sub 发布订阅没有消息持久化的问题)
原语 | 介绍 | 场景 |
---|---|---|
hpperLogLog | 唯一计数器(12k就能计数2^64个成员, 计数估计算法误差0.81%) | 网站访客量ip; 检测垃圾信息;每周/月度/年度计数器 |
bitmap | bit数组, 用户可以通过偏移量(offset)对一个或者多个二进制进行位操作 | 布隆过滤器 |
geo | 地理坐标, 通过坐标距离计算范围查找 | 记录坐标; 附近的用户; |
pub/sub | 发布订阅, 消息队列(不能持久化) | 发布队列 |
stream | 消息队列(解决 list, zset 不能范围操作, pub/sub发布订阅不能持久化的问题) | 消息队列 |
string key value
string 是 redis 最常用最基本的数据结构, 不仅可以储存 string, 数字, 也可以是图片, 音频等更复杂的二进制数据
1. 储存结构
2.原语
SET key value[EX seconds|PX millisecods|EXAT timestamp|PXAT milliseconds-timestamp|KEEPTTL] [NX|EX] [GET]
原语 | 介绍 |
---|---|
SET key value |
设置key value 值 |
GET key |
获取 key value值 |
DEL key |
删除key value值(通用的方法能删除其他数据结构) |
SET key value NX |
设置值, key 不存在才能设置成功 |
SETNX key value |
设置值, key 不存在才能设置成功(同上) |
SET key value XX |
设置值, key 存在才能设置成功 |
SETXX key value |
设置值, key 存在才能设置成功(同上) |
SET key value EX seconds |
设置值和过期时间, 单位秒 |
SET key value PX milliseconds |
设置值和过期时间, 单位毫秒 |
EXPIRE key seconds |
设置过期时间, 单位秒 |
INCR key |
递增 1 , 如果 value 不是数字或者越界会报错 |
DNCR key |
递减少 1, 如果 value 不是数字或者越界会报错 |
INCRBY key integer |
递增 integer |
DNCRBY key integer |
递减 integer |
GETSET key value |
设置值并返回旧值 |
GETSET key value |
SET并GET旧的值 |
MGET key [key...] |
批量 GET 一般搭配 keys 批量获取键来使用 |
STRLEN key |
获取 value 长度 |
GETBIT key offset |
得到 value 的 offset 偏移 |
INCRBY key integer |
给 value 增加 number ; value = value + number |
DECRBY key interger |
value 减少 number ; value = value - number |
INCRBYFLOAT key increment |
由给定的数量递增键的浮点值 |
APPEND key value |
追加一个value到value的末尾 |
…
hash 散列
hash 理解为一个 string 嵌套了一个 string 能执行 string 的大部分操作
1. 解决的问题
我们通过多个 string kv 去储存一个 user 对象
"user:1:name" -> "张三"
"user:1:age" -> 13
"user:2:name" -> "李四"
...
问题:
- 多key: redis 会产生很多键 key, key 过多会拖慢整个 redis
- 可读性差
- 浪费内存: key 也要空间, 前缀是重复的
- 管理困难: 如果要删除一个缓存就需要将所有字段的 key 都删除, 比如有 2 个字段就需要删除 2 个, 比如有 10 个字段就需要删除 10 个
2. 散列的储存结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6OQ9kuE7-1644494146691)(images/image-20220209113746280.png)]
3. 原语
原语 | 介绍 |
---|---|
HSET key field value [field value...] |
为字段设置值(不存在 field 会新建并返回 1 ,存在 field 覆盖并返回0) |
HGET key filed |
获取值(获取不到结果会返回 nil) |
HDEL key field |
删除 key 的 filed |
HSETNX key field value |
只在字段不存在的情况下为它设置值(filed 存在将会失败并返回 0) |
HINCRBY key field integer |
字段递增递减 |
HINCRBYFLOAT key field float |
字段递增浮点数 |
HSTRLEN key field |
获取字段长度 |
HEXISTS key field |
检查字段是否存在 |
HLEN key |
获取 key 字段个数 |
DEL key |
删除 key |
HMSET key field value [field value...] |
批量设置 HSET 也能这样用 |
HMGET key filed [field..] |
批量 get field |
HKEYS key |
获取 key 所有字段 |
HVALS key |
获取 key 所有 value |
HGETALL key |
获取 key 所有的字段和value |
list 列表
实现是双向链表或者说一个队列
1.储存结构
2.原语
原语 | 介绍 |
---|---|
LPUSH key element [element...] |
将成员推入列表左端 |
RPUSH key element [element...] |
将成员推入列表右端 |
LPUSHX key element [element...] |
将成员推入列表左端(key不存在返回0) |
RPUSHX key element [element...] |
将成员推入列表右端(key不存在返回0) |
LPOP key** ** |
弹出列表最左端的成员 |
RPOP key** ** |
弹出列表最右端的成员 |
RPOPLPUSH key |
将右端弹出的成员推入左端 |
LLEN key |
获取列表的长度 |
LINSERT key BEFORE|AFTER pivot element |
插入成员, pivot 是成员中点成员的值 |
LRANGE key start stop |
得到范围的成员集合, stop 为 -1 代表所有成员 |
LTRIM key start stop |
修建列表, 移除范围之类的所有成员 |
LREM key count element |
移除成员, count = 0 移除所有,count>0移除 count 个数 |
BLPOP key timeout |
阻塞式左端弹出 |
BRPOP key timeout |
阻塞式右端弹出 |
BRPOPLPUSH key timeout |
阻塞式将右端弹出的成员推入左端 |
set 集合
不重复的列表, 自动去重, 类似数据集合概念(内部实现是一个 value 为 null 的 hashmap)
1.储存结构
(内部实现是一个 value 为 null 的 hashmap)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dHrOdotu-1644494146692)(images/image-20220209143243515.png)]
2.原语
原语 | 介绍 |
---|---|
SADD key element [element...] |
将成员添加到集合 |
SREM key element |
从集合中移除成员 |
SMOVE source target element |
将成员从一个集合移动到另一个集合 |
SMEMBERS key |
获取集合中所有成员 |
SCARD key |
获取集合成员数量 |
SISMEMBER key element |
成员是否属于该集合 |
SRANDMEMBER key [count] |
随机获得集合成员, count 默认 为 1 |
SPOP key [count] |
随机弹出集合成员, count 默认 为 1 |
SINTER key [key...] |
返回多个集合的交集 |
SINTERSTORE target [key...] |
多个集合的交集储存到一个新的集合,返回新集合个数 |
SUNION [key...] |
返回多个集合的并集 |
SUNIONSTORE target [key...] |
多个集合的并集储存到一个新的集合,返回新集合个数 |
SDIFF [key...] |
返回多个集合的差集 |
SDIFFSTORE target [key...] |
多个集合的差集储存到一个新的集合,返回新集合个数 |
zset 有序集合
通过新增 score 字段来排序, -inf 和 +inf 代表正无穷和负无穷的分数, 分数一致的成员会使用字典排序
1.储存结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-td9ar9CF-1644494146692)(images/image-20220209164359582.png)]
2.原语
原语 | 介绍 |
---|---|
ZADD key score element[score element...] |
添加或者更新成员 |
ZREM key element |
移除成员 |
ZSCORE key element |
获取成员的分数 |
ZINCRBY key score element |
增加成员分数 |
ZCARD key |
获取集合成员个数 |
ZRANK key element |
获取成员升序排名 |
ZREVRANK key element |
获取成员降序排名 |
ZRANGE key start end |
获取排名区间成员(升序) |
ZREVRANGE key start end |
获取排名区间成员(降序) |
ZRANGEBYSCORE key min max |
获取分数区间的成员(升序) |
ZREVRANGEBYSCORE key max min |
获取分数区间的成员(降序) |
ZCOUNT key min max |
获取分数区间的成员个数(升序) |
ZREMRANGEBYRANK key start end |
移除排名区间的成员(升序), 负数可以实现倒数 |
ZREMRANGEBYSCORE key min max |
移除分数区间的成员(升序) |
ZUNIONSTORE target key [key...] |
多个集合的并集储存到一个新的集合,相同的 element 会累加 score |
ZRANGEBYLEX key min max [limit offset count] |
获取字典排序指定范围成员 |
ZREVRANGEBYLEX key min max [limit offset count] |
获取字典排序指定范围成员(降序) |
ZLEXCOUNT key min max |
获取字典排序指定范围成员数量 |
ZREMRANGEBYLEX key min max |
移除字典排序指定范围成员 |
ZPOPMAX key [count] |
弹出分值高的成员 |
ZPOPMIN key [count] |
弹出分值低的成员 |
BZPOPMAX key [key...] timeout |
阻塞弹出分值高的成员, 从多个 zset 中 select 弹出 |
BZPOPMIN key [key...] timeout |
阻塞弹出分值低的成员, 从多个 zset 中 select 弹出 |
HyperLogLog 集合唯一计数器
唯一计数器(12k就能计数2^64个成员, 计数估计算法误差0.81%)
- 统计集合成员的个数
- 并集多个 HyperLogLog
2.原语
原语 | 介绍 |
---|---|
PFADD key element [element...] |
添加成员(成功返回1,成员存在返回0) |
PFCOUNT key |
计数成员 |
PFMERGE target key [key...] |
并集 key |
bitmap 位图
bitmap 是一个二进制的bit数组, 能对指定的 offset 进行位操作
1. 数据结构
下面展示一个 1 byte = 8 bit(位) 的 bitmpa
位图存储的值为10010100
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ez8XcVCy-1644494146692)(images/image-20220209231147095.png)]
2.原语
原语 | 介绍 |
---|---|
SETBIT key offset 0|1 |
设置offset的值位(0或者1) |
GETBIT key offset |
获取offset的值位(0或者1) |
BITCOUNT key |
获取1的数量 |
BITPOS key 0|1 start end |
获取位的offset(注意: start end 的单位式 byte) |
BITOP target key [key...] |
一个或者多个bitmap执行位运算并将结果储存到 target 中(AND|OR|XOR|NOT) |
BITFIELD key SET type offset value |
设置整数值(type: i8 有符号8位, u8无符号8位,类推) |
BITFIELD key SET type #index value |
设置整数值(offset 变为 #index, #1 表示第一个 type 类型的位置) |
BITFIELD key GET type offset |
获取整数值 |
BITFIELD key GET type #index |
获取整数值(根据 index ) |
GET key |
可以使用 string 命令对 bitmiap 操作, 因为 bitmap 是基于 string 实现的 |
geo 地理坐标
2.原语
原语 | 介绍 |
---|---|
GEOADD key [NX|XX] x y name[key x y name...] |
设置坐标, x 经度, y 纬度, name 是别名, 返回已有的数量 |
GEOPOS key name[name...] |
获取坐标 |
GEODIST key name name |
计算 2 个坐标的距离 |
GEORADIUS key x y radius unit |
查找坐标半径内的其他成员, radius 是半径, unit 单位(m km mi ft) |
GEORADIUSBYMEMBER key name radius unit |
查找成员半径内的其他成员 |
stream 流(重要)
1. XADD 添加新元素到末尾
(1) id
XADD key id key value [key value...]
流元素的 id 由 毫秒时间戳 + 序列号组成 需要他们整体呈递增, 不然将会报错
id 使用 * 将自动生成 id
127.0.0.1:6379> XADD x1 * k1 v1
"1644465874779-0"
(2) 执行 XADD 同时用 MAXLEN 修剪流
注意观察, 这个是 XADD 执行时候修剪, 注意使用时机
127.0.0.1:6379> XADD x1 MAXLEN = 2 * k1 v1
"1644466561573-0"
127.0.0.1:6379> XADD x1 * k2 v2
"1644466573705-0"
127.0.0.1:6379> XRANGE x1 - +
1) 1) "1644466561573-0"
2) 1) "k1"
2) "v1"
2) 1) "1644466573705-0"
2) 1) "k2"
2) "v2"
# 这里没有添加 MAXLEN 限制将不会生效 MAXLEN
127.0.0.1:6379> XADD x1 * k3 v3
"1644466587887-0"
127.0.0.1:6379> XRANGE x1 - +
1) 1) "1644466561573-0"
2) 1) "k1"
2) "v1"
2) 1) "1644466573705-0"
2) 1) "k2"
2) "v2"
3) 1) "1644466587887-0"
2) 1) "k3"
2) "v3"
# 这里添加 MAXLEN 限制将会生效 MAXLEN
127.0.0.1:6379> XADD x1 MAXLEN = 2 * k4 v4
"1644466613469-0"
127.0.0.1:6379> XRANGE x1 - +
1) 1) "1644466587887-0"
2) 1) "k3"
2) "v3"
2) 1) "1644466613469-0"
2) 1) "k4"
2) "v4"
127.0.0.1:6379>
2. XTRIM 修剪流
我们除了执行 XADD 的时候同时使用 MAXLEN 修剪流, 我们也能用 XTRIM 单独修剪流
XTRIM key MAXLENTH|MINID =|~ threshold [limit count]
示例
127.0.0.1:6379> XRANGE x1 - +
1) 1) "1644466587887-0"
2) 1) "k3"
2) "v3"
2) 1) "1644466613469-0"
2) 1) "k4"
2) "v4"
127.0.0.1:6379> XTRIM x1 MAXLEN 1
(integer) 1
127.0.0.1:6379> XRANGE x1 - +
1) 1) "1644466613469-0"
2) 1) "k4"
2) "v4"
127.0.0.1:6379>
3. XDEL 移除流中的 ID
使用
XDEL key [id...]
示例
127.0.0.1:6379> XRANGE x1 - +
1) 1) "1-0"
2) 1) "k1"
2) "v1"
2) 1) "2-0"
2) 1) "k2"
2) "v2"
127.0.0.1:6379> XDEL x1 1-0
(integer) 1
127.0.0.1:6379> XRANGE x1 - +
1) 1) "2-0"
2) 1) "k2"
2) "v2"
127.0.0.1:6379>
4. XLEN 获取元素数量
XLEN key
示例
127.0.0.1:6379> XRANGE x1 - +
1) 1) "2-0"
2) 1) "k2"
2) "v2"
127.0.0.1:6379> XLEN x1
(integer) 1
127.0.0.1:6379>
5. XRANGE、XREVRANGE:访问流中元素
XRANGE 可以获取流的单个元素, 多个元素, 以及迭代整个流
XRANGE key start-id end-id [COUNT n]
迭代整个流可以通过每次访问用 COUNT 限制 1 个然后下一次访问的时候用 start-id 赋值位 上一次的成员的 id + 1
就能范围缩小从而迭代下一个元素
这里 start-id 将会获取大于等于 start-id 的成员
6. XREAD 从一个和多个 stream 用阻塞或者非阻塞的方式获取流
XREAD [COUNT n] [BLOCK milliseconds] STREAMS key [key...] id [id...]
这里 id 将会获取大于 id 的成员
如果 id 为 $ 将会仅仅获取命令执行之后的最新的流
BLOCK 有流就会返回, 比如要求有 COUNT 为 3, 只能读取到 2 个就直接返回 2 个.
7. XGROUP 消费组
(1) 创建消费组
XGROUP [CREATE key groupname id-or-$] [SETID key groupname id-or-$] [DESTROY key groupname] [DELCONSUMER key groupname consumername]
- key 为流的名称,不存在就创建
- groupname 是消费者的名称
- id-or-$: id 代表从 id 位置开始, $ 代表从尾部开始, 就是之前的信息会忽略
从头开始消费
XGROUP CREATE mystream groupname 0-0
只是消费最新的信息
XGROUP CREATE mystream groupname $
(2) 在消费组里消费信息
- 同一个 stream 可以创建多个消费组
- 同一个消费组的信息只能被一个消费者读取
XREADGROUP GROUP group consumer [COUNT count] [BLOCK milliseconds] [NOACK] STREAMS key [key ...] ID [ID ...]
同理 XREAD 命令, 新增的字段如下
- group 是创建的消费组的名称
- consumer 是消费者的名称, 用户自定义
- id: 消息的 id (
>
就是读取一条未递送的消息), 读取之后的消息同一个消息组的其他消费者将不能再次读取
127.0.0.1:6379> XADD x1 1 k1 v1
"1-0"
# 创建 g1 消费组, 读取最新的内容
127.0.0.1:6379> XGROUP CREATE x1 g1 $
OK
# 使用 c1 读取 g1 消费组中的内容, 没有最新内容返回 nil
127.0.0.1:6379> XREADGROUP GROUP g1 c1 STREAMS x1 >
(nil)
# 给 stream x1 添加内容, g1 会共享到 x1 的天降
127.0.0.1:6379> XADD x1 2 k2 v2
"2-0"
# 使用 c1 读取 g1 消费组中的内容
127.0.0.1:6379> XREADGROUP GROUP g1 c1 STREAMS x1 >
1) 1) "x1"
2) 1) 1) "2-0"
2) 1) "k2"
2) "v2"
# 使用 c1 读取 g1 消费组中的内容, 没有最新内容返回 nil
127.0.0.1:6379> XREADGROUP GROUP g1 c1 STREAMS x1 >
(nil)
# 使用 新的消费者 c2 读取 g1 消费组中的内容, 没有最新内容返回 nil, 一个消息在一个消费组里面只能被消费者消费一次
127.0.0.1:6379> XREADGROUP GROUP g1 c2 STREAMS x1 >
(nil)
# 消费者处理完消息之后发送 ACK 确认, 消息就从待处理变成已确认
127.0.0.1:6379> XACK x1 g1 2-0
(integer) 1
# 只能确认一次
127.0.0.1:6379> XACK x1 g1 2-0
(integer) 0
127.0.0.1:6379>
(3) XACK 确认
XACK key groupname id
通过 id 确认消息
消息添加后在消息组中 4 个状态
- 不存在
- 未递送
- 待处理
- 已确认
转换关系如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g0O6zVS7-1644494146693)(images/image-20220210170444852.png)]
示例
127.0.0.1:6379> XREADGROUP GROUP g1 c1 STREAMS x1 >
1) 1) "x1"
2) 1) 1) "3-0"
2) 1) "k3"
2) "v3"
# 查看没有确认的消息
127.0.0.1:6379> XPENDING x1 g1
1) (integer) 1
2) "3-0"
3) "3-0"
4) 1) 1) "c1"
2) "1"
# 确认消息
127.0.0.1:6379> XACK x1 g1 3-0
(integer) 1
127.0.0.1:6379> XPENDING x1 g1
1) (integer) 0
2) (nil)
3) (nil)
4) (nil)
127.0.0.1:6379>
(4) 管理消费组
原语 | 介绍 |
---|---|
XGROUP SETID key groupname id |
修改消费组的id(消费组会读取大于id的消息,$代表最新的消息), 该命令会重置 group 的未读消息 |
XGROUP DELCOMSUMER key groupname consuemr |
删除消费者 |
XGROUP DISTORY key groupname |
删除消费组 |
8. 查看取待处理的消息
XPENDING key groupname [start stop count] [consumer]
(1) 查看所有消费者的待处理消息
127.0.0.1:6379> XPENDING x1 g1
1) (integer) 2 # 待处理消息数量
2) "3-0" # 首条未读消息的id
3) "4-0" # 最后一台未读消息的id
4) 1) 1) "c1" # 各个消费者目前正在处理消息的数量
2) "1"
2) 1) "c2"
2) "1"
127.0.0.1:6379>
(2) 查看具体一个消费者的待处理消息
127.0.0.1:6379> XPENDING x1 g1 - + 1 c1
1) 1) "4-0" # 消息 id
2) "c1" # 消费者
3) (integer) 3321593 # 消息处理的时间
4) (integer) 1 # 消息消费的次数
127.0.0.1:6379>
9. XCLAIM:转移消息的归属权
XCLAIM key groupname new_consuemr max_pending_time id [id...]
旧的消费者需要在 max_pending_time 的期限中 XACK 信息, 否则消息的归属权将会转移
10. XINFO:查看流和消费者组的相关信息
(1) HELP 帮助
127.0.0.1:6379> XINFO HELP
1) XINFO <subcommand> [<arg> [value] [opt] ...]. Subcommands are:
2) CONSUMERS <key> <groupname> # 1.展示 consuemr 信息
3) Show consumers of <groupname>.
4) GROUPS <key> # 2. 展示 group 信息
5) Show the stream consumer groups.
6) STREAM <key> [FULL [COUNT <count>] # 3. 展示 stream 消息
7) Show information about the stream.
8) HELP
9) Prints this help.
127.0.0.1:6379>
(2) CONSUMERS 查看消费组里的消费者
127.0.0.1:6379> XINFO CONSUMERS x1 g1
1) 1) "name" # 1. 消费者名称
2) "c1"
3) "pending" # 2. pending
4) (integer) 1
5) "idle" # 3. 处理的时间,还没有规范
6) (integer) 4116697
2) 1) "name"
2) "c2"
3) "pending"
4) (integer) 1
5) "idle"
6) (integer) 3980477
127.0.0.1:6379>
(3) STREAM 查看流的信息
127.0.0.1:6379> XINFO STREAM x1
1) "length" #成员数量
2) (integer) 4
3) "radix-tree-keys" #基树键的数量
4) (integer) 1
5) "radix-tree-nodes" # 基树节点的数量
6) (integer) 2
7) "last-generated-id" #最后一个节点的 id
8) "4-0"
9) "groups" #消费者数量
10) (integer) 1
11) "first-entry" #最开始的成员
12) 1) "1-0"
2) 1) "k1"
2) "v1"
13) "last-entry" #最后的成员
14) 1) "4-0"
2) 1) "k4"
2) "v4"
127.0.0.1:6379>
pub/sub 发布订阅
发布订阅就像广播, 是无状态的, 订阅者没有收到就丢失了, 不会储存旧信息也不会持久化
1. 发布订阅频道
发布者发布的消息能被多个订阅者接收
2. 发布订阅模式
模式匹配可以 一个 pattern 订阅多个模式
3.原语
原语 | 介绍 |
---|---|
PUBLISH key message |
发布者发布消息 |
SUBSCRIBE key [key...] |
订阅者订阅消息 |
UNSUBSCRIBE [key...] |
退订频道, 默认退订所有频道 |
PSUBSCRIBE pattern [pattern...] |
订阅模式, 一个 pattern 可以匹配多个频道 |
PUNSUBSCRIBE pattern [pattern...] |
退订模式 |
PUBSUB CHANNELS [pattern] |
列出目前被订阅的频道 |
PUBSUB NUMSUB [key ...] |
查询频道订阅的数量 |
PUBSUB NUMPAT |
查询频道订阅的总数 |
数据库
原语 | 介绍 |
---|---|
KEYS pattern |
返回匹配正则表达式的所有的 key |
EXISTS key |
时候存在 key |
DBSIZE |
key 数量 |
TTL key |
key 的过期时间(秒) |
PTTL key |
key 的过期时间(毫秒) |