redis开发与运维-redis02-redis数据类型与命令总结
文章目录
- 【README】
- 【1】redis通用命令与数据结构
- 【1.1】通用命令
- 【1.2】数据结构与内部编码
- 【1.3】redis单线程架构
- 【1.3.1】redis单线程优缺点
- 【2】字符串(值的类型为字符串)
- 【2.1】常用命令
- 【2.1.1】设置值
- 【2.1.2】获取值
- 【2.1.3】批量设置值
- 【2.1.4】批量获取值
- 【2.1.5】计数
- 【2.2】不常用命令
- 【2.2.1】追加值
- 【2.2.2】字符串长度
- 【2.2.3】设置并返回原值
- 【2.2.4】设置指定位置的字符
- 【2.2.5】获取部分字符串
- 【2.2.6】值为字符串类型的命令总结
- 【2.3】内部编码
- 【2.4】值为字符串类型的典型使用场景
- 【3】哈希(值的类型为哈希)
- 【3.1】命令
- 【3.1.1】设置值
- 【3.1.2】获取值
- 【3.1.3】删除field
- 【3.1.4】计算field个数
- 【3.1.5】 批量设置或获取域值对
- 【3.1.6】判断field域是否存在
- 【3.1.7】获取所有域和值
- 【3.1.8】自增操作
- 【3.1.9】计算value的字符串长度
- 【3.1.10】值为哈希类型的命令总结
- 【3.2】内部编码
- 【3.3】使用场景
- 【3.3.1】 哈希类型与关系型数据库的不同
- 【3.3.2】缓存用户信息的方法
- 【4】列表(值的类型为列表)
- 【4.1】命令
- 【4.1.1】添加元素
- 【4.1.2】查找
- 【4.1.3】删除
- 【4.1.4】修改
- 【4.1.5】阻塞
- 【4.1.6】值为列表类型的命令总结
- 【4.2】内部编码
- 【4.3】使用场景
- 【4.3.1】消息队列
- 【4.3.2】文章列表
- 【4.3.3】列表类型的使用场景总结
- 【5】集合(值的类型为集合)
- 【5.1】命令
- 【5.1.1】集合内操作
- 【5.1.2】集合间操作
- 【5.1.3】值为集合类型的命令总结
- 【5.2】内部编码
- 【5.3】使用场景
- 【5.3.1】集合使用场景总结
- 【6】有序集合
- 【6.1】命令
- 【6.1.1】有序集合内命令
- 【6.1.2】有序集合间的操作命令
- 【6.1.3】有序集合类型的命令总结
- 【6.2】内部编码
- 【6.3】使用场景
- 【7】键管理
- 【7.1】单个键管理
- 【7.1.1】键重命名
- 【7.1.2】随机返回1个键
- 【7.1.3】键过期
- 【7.2】遍历键
- 【7.2.1】全量遍历键-keys(不建议)
- 【7.2.2】渐进式遍历-scan(推荐-常用)
- 【8】数据库管理
- 【8.1】redis数据库
- 【8.1.1】redis切换数据库(仅了解)
- 【8.1.1】redis3.0后弱化多数据库
- 【8.2】清除数据库(谨慎使用)
【README】
本文总结自《redis开发与运维》,作者付磊,张益军,墙裂推荐;
本文使用的redis版本是 7.0.15 ; 又《redis开发与运维》中使用的redis版本是3.0.7,两个版本的redis部分数据结构的内部编码有差异(但不影响整体知识结构);
【1】redis通用命令与数据结构
【1.1】通用命令
1)keys * 查看所有键
keys事件复杂度为O(n),因为它会遍历所有键 ;
192.168.163.211:6379> keys *
1) "name3"
2) "name"
3) "name2"
4) "tomhome:order:2"
5) "set2"
6) "yuwen_score_set"
7) "tomhome:user:1"
8) "orders"
9) "tomhome:order:1"
10) "tomhome:product:2"
11) "set1"
12) "tomhome:user:2"
13) "tomhome:product:1"
2)dbsize 键总数 : 返回当前数据库中键的总数;
dbsize时间复杂度为O(1),它是直接获取redis内置的键总数变量;
192.168.163.211:6379> dbsize
(integer) 13
3)exists key 检查键是否存在
192.168.163.211:6379> exists name3
(integer) 1
192.168.163.211:6379>
192.168.163.211:6379> exists hello
(integer) 0
4)del key 删除键
192.168.163.211:6379> del name3
(integer) 1
5)expire key seconds 键过期 (过期时间单位是秒)
redis支持对键添加过期时间,当超过过期时间后,会自动删除键;
192.168.163.211:6379> expire name 10
(integer) 1
# ttl 查看剩余的存活时间(单位秒)
192.168.163.211:6379> ttl name
(integer) 8
192.168.163.211:6379>
192.168.163.211:6379> ttl name
(integer) 7
192.168.163.211:6379> ttl name
(integer) 6
192.168.163.211:6379> ttl name
(integer) 5
192.168.163.211:6379> ttl name
(integer) -2 # -2表示该键name被删除了
【ttl命令】:返回键的剩余过期时间,有3种值;
- 大于等于0的整数: 键剩余的过期时间;
- -1 : 键没有设置过期时间,即永久存在;
- -2: 键不存在;
6)type key 键的数据结构类型
若键不存在,返回none
192.168.163.211:6379> keys *
1) "name2"
192.168.163.211:6379> type name2
string
# 若键不存在,返回none
192.168.163.211:6379> type hello
none
【1.2】数据结构与内部编码
1)type key命令实际返回键的数据结构类型,包括string字符串, hash哈希, list列表, set集合, zset有序集合;如下图所示。
2)可以通过object encoding key命令查询内部编码;
192.168.163.211:6379> get name2
"zhangsan2"
192.168.163.211:6379> object encoding name2
"embstr"
【1.3】redis单线程架构
1)为什么单线程还那么快?
- 纯内存访问:redis将所有数据放在内存中,内存的响应时长大约为100纳秒,这是redis达到每秒万级别访问的基础;
- 非阻塞IO:redis使用epoll作为io多路复用技术的实现,再加上redis自身的事件处理模型将epoll中的连接,读写,关闭都转换为事件,不在网络io上浪费过多的时间;
- 单线程避免了线程切换和竞态产生的消耗;
【1.3.1】redis单线程优缺点
1)redis单线程的好处:
- 单线程可以简化数据结构和算法实现;
- 单线程避免线程切换和竞态产生的消耗,对于服务端来说,锁和线程切换通常是性能杀手 ;
2)redis单线程缺点:
- 对于每个命令的执行时间有要求;如果某个命令执行时间过长,会造成其他命令的阻塞,对于redis这种高性能的服务来说是致命的,所以redis是面向快速执行场景的数据库;
【2】字符串(值的类型为字符串)
1)字符串定义: 字符串类型是redis最基础的数据结构。键都是字符串类型,而其他几种数据结构都是在字符串类型基础上构建的;
2)字符串类型的值:实际可以是字符串(简单字符串,复杂字符串如json),数字(整数,浮点数),二进制(图片,音视频),值最大不超过512M;
【2.1】常用命令
【2.1.1】设置值
1)set key value 设置键key的值为value
192.168.163.211:6379> get name
"zhangsan"
192.168.163.211:6379> set name zhangsan2
OK
192.168.163.211:6379> get name
"zhangsan2"
set命令有几个选项:如 set key value [NX|XX] [GET] [EX seconds|PX milliseconds|EXAT unix-time-seconds|PXAT unix-time-milliseconds|KEEPTTL]
- ex seconds: 为键设置秒级过期时间;
- px milliseconds:为键设置毫秒级过期时间;
- nx: 键必须不存在,才可以设置成功,用于添加;
- xx:与nx相反,键必须存在才可以设置成功,用于更新;
2)setex key seconds value 设置带有过期时间的key-value
等价于set key value ex seconds
192.168.163.211:6379> set name zhangsan3 ex 10
OK
192.168.163.211:6379> ttl name
(integer) 6
192.168.163.211:6379> ttl name
(integer) -2
192.168.163.211:6379> get name
(nil)
192.168.163.211:6379> exists name
(integer) 0 #0表示不存在
3)setnx key vlaue 当键不存在才新增
等价于 set key value nx
192.168.163.211:6379> exists name
(integer) 0
# setnx 不存在才新增
192.168.163.211:6379> setnx name zhangsan4
(integer) 1
# exists name是否存在; 1-存在,0-不存在
192.168.163.211:6379> exists name
(integer) 1
192.168.163.211:6379> get name
"zhangsan4"
# set key value nx 不存在才新增
192.168.163.211:6379> set name zhangsan5 nx
(nil)
192.168.163.211:6379> get name
"zhangsan4" # 还是zhangsan4
4)set key value xx 键key存在才新增,否则不新增
192.168.163.211:6379> keys *
1) "name2"
2) "set1"
3) "set2"
4) "name"
192.168.163.211:6379> set name3 zhangsan30 xx
(nil)
192.168.163.211:6379> get name
"zhangsan4"
192.168.163.211:6379> set name zhangsan30
OK
192.168.163.211:6379> get name
"zhangsan30"
【2.1.2】获取值
1)get key 获取值,若键不存在,则返回空nil
192.168.163.211:6379> keys *
1) "name2"
2) "set1"
3) "set2"
4) "name"
192.168.163.211:6379> get name
"zhangsan30"
192.168.163.211:6379> get name3
(nil)
【2.1.3】批量设置值
1)mset key value [key value …] 批量设置值
192.168.163.211:6379> mset k1 1 k2 2 k3 3
OK
【2.1.4】批量获取值
1)mget key [key…] 批量获取值
192.168.163.211:6379> mset k1 1 k2 2 k3 3
OK
# mget 批量获取值 ( 若key不存在,则返回空nil )
192.168.163.211:6379> mget k1 k2 k3 k4
1) "1"
2) "2"
3) "3"
4) (nil)
补充:批量操作mset,mget可以有效提高系统性能,因为减少了网络io;
注意: 每次批量操作发送的命令数不是无节制的,如果数量过多可能导致redis阻塞或网络阻塞;
【2.1.5】计数
1) incr key 自增
incr命令可以对值自增1,返回结果分三种情况:
- 值不是整数, 返回错误;
- 值是整数, 返回自增后的结果;
- 键不存在, 按照值为0自增, 返回结果为1;
192.168.163.211:6379> exists name3
(integer) 0
192.168.163.211:6379> incr name3
(integer) 1
192.168.163.211:6379> incr name3
(integer) 2
# 查找键name的值
192.168.163.211:6379> get name
"zhangsan30"
# 值不是整数,则返回错误
192.168.163.211:6379> incr name
(error) ERR value is not an integer or out of range
补充:除开 incr命令,redis还提供了 decr自减,incrby自增指定数字,decrby自减指定数字,incrbyfloat自增浮点数;
- 很多存储系统和编程语言内部使用CAS实现计数功能,会有一定的cpu开销; redis完全不存在这个问题, 因为redis是单线程架构,命令是顺序执行;
【2.2】不常用命令
【2.2.1】追加值
1)append key value 向字符串尾部追加值
192.168.163.211:6379> get name
"zhangsan30"
192.168.163.211:6379> append name hello
(integer) 15
192.168.163.211:6379> get name
"zhangsan30hello"
【2.2.2】字符串长度
1)strlen key 返回键=key的值的长度
192.168.163.211:6379> get name
"zhangsan30hello"
192.168.163.211:6379> strlen name
(integer) 15
【2.2.3】设置并返回原值
1)getset key new_value 设置key的值为new_value,并返回旧值
192.168.163.211:6379> get name
"zhangsan30hello"
192.168.163.211:6379> getset name hello
"zhangsan30hello"
192.168.163.211:6379> get name
"hello"
【2.2.4】设置指定位置的字符
1)setrange key offset new_value 把key对应值的offset位置(从0计数)上的值设置为new_value
192.168.163.211:6379> get tom
"pest"
192.168.163.211:6379> setrange tom 0 b
(integer) 4
192.168.163.211:6379> get tom
"best"
【2.2.5】获取部分字符串
1)getrange key start end 截取key对应值的[start,end]位置上的字符串, start与end从0开始计数
192.168.163.211:6379> get tom
"best"
192.168.163.211:6379> getrange tom 1 2
"es"
【2.2.6】值为字符串类型的命令总结
命令 | 命令描述 | 时间复杂度 |
---|---|---|
set key value | 设置键与值 | O(1) |
get key | 获取键的值 | O(1) |
del key [key …] | 删除键 | O(k),k为键的个数 |
mset key value [key value …] | 批量设置多个键值对 | O(k),k为键的个数 |
mget key [key …] | 批量获取多个键的值 | O(k),k为键的个数 |
incr key | Key的值自增 | O(1) |
decr key | Key的值自减 | O(1) |
incrby key increment | Key的值增加increment | O(1) |
decrby key increment | Key的值减少increment | O(1) |
incrbyfloat key increment | Key的值增加increment | O(1) |
append key value | 向key的值的尾部追加value | O(1) |
strlen key | 计算key的值的长度 | O(1) |
setrange key offset value | 设置指定位置的字符串,从0计数 | O(1) |
getrange key start end | 获取[start,end]位置范围上的字符串,从0计数 | O(n),n为字符串长度 |
【2.3】内部编码
1)字符串类型的内部有3种编码:
- int: 8个字节的长整型;
- embstr:小于等于39个字节的字符串;
- raw:大于39个字节的字符串;
redis 会根据当前值的类型与长度决定使用哪种内部编码;
192.168.163.211:6379> get num1
"1234"
192.168.163.211:6379> object encoding num1
"int"
【2.4】值为字符串类型的典型使用场景
1)缓存;
- redis作为缓存层,mysql作为存储层,绝大部分请求的数据都从redis获取;
- 键名格式: 业务名: 对象名 : id : [属性]
2)计数;
- 如视频观看数,点赞数;
3)共享Session;
- 每次用户更新或者查询登录信息都直接从redis中获取;
4)限速;
- 很多应用出于安全考虑,会在每次登录时,让用户输入手机验证码;
【3】哈希(值的类型为哈希)
1)哈希类型定义:指键的值本身又是一个键值对结构。 如value={{field1, value1}, {field2, value2}}
2)哈希类型的键值对举例,如下。
【3.1】命令
【3.1.1】设置值
1)hset key field value 为key添加一对 field-value 域-值对
设置成功返回1,否则返回0;
192.168.163.211:6379> hset user:1 name tom
(integer) 1
192.168.163.211:6379> hset user:1 age 18
(integer) 1
192.168.163.211:6379> hget user:1 name
"tom"
2)hsetnx key field value 当field不存在时,为key添加一对 field-value 域-值对
192.168.163.211:6379> hsetnx user:1 name tom2
(integer) 0
192.168.163.211:6379> hget user:1 name
"tom"
192.168.163.211:6379> hsetnx user:1 addr cd
(integer) 1
192.168.163.211:6379> hget user:1 addr
"cd"
【3.1.2】获取值
1)hget key field 获取key的field域的值
如果键或field不存在,则返回nil
192.168.163.211:6379> hget user:1 addr
"cd"
192.168.163.211:6379> hget user:2 name
(nil)
192.168.163.211:6379> hget user:1 name
"tom"
192.168.163.211:6379> hget user:1 account
(nil)
【3.1.3】删除field
1)hdel key field [field …]
hdel 会删除一个或多个field,返回结果为成功删除field的个数;
192.168.163.211:6379> hget user:1 name
"tom"
192.168.163.211:6379> hget user:1 age
"18"
192.168.163.211:6379> hdel user:1 name age
(integer) 2
192.168.163.211:6379> hget user:1 age
(nil)
192.168.163.211:6379> hget user:1 name
(nil)
【3.1.4】计算field个数
1) hlen key 计算field个数
# 获取所有域
192.168.163.211:6379> hkeys user:1
1) "addr"
2) "name"
3) "age"
# 计算域个数
192.168.163.211:6379> hlen user:1
(integer) 3
【3.1.5】 批量设置或获取域值对
1)hmset key field [field …] 批量设置域值对
2)hmget key field [field …] 批量获取域值对
192.168.163.211:6379> hmset user:2 name tom2 age 22 addr cd2
OK
192.168.163.211:6379> hmget user:2 name age addr
1) "tom2"
2) "22"
3) "cd2"
【3.1.6】判断field域是否存在
1)hexists key field 判断key对应值的域field是否存在
若key对应值包含域field,则返回1,否则返回0
192.168.163.211:6379> hexists user:2 name
(integer) 1
192.168.163.211:6379> hexists user:2 phonenum
(integer) 0
【3.1.7】获取所有域和值
1)hkeys key 获取key的所有域field
2)hvals key 获取key的所有域的值
192.168.163.211:6379> hkeys user:2
1) "name"
2) "age"
3) "addr"
192.168.163.211:6379> hvals user:2
1) "tom2"
2) "22"
3) "cd2"
3)hgetall key 获取key的所有域值对
192.168.163.211:6379> hgetall user:2
1) "name"
2) "tom2"
3) "age"
4) "22"
5) "addr"
6) "cd2"
【3.1.8】自增操作
1)hincrby key field given_value 把key的field域自增给定值given_value
- hincrby 与 incrby 类似;只不过hincrby 操作key的域,而incrby操作key;
2)hincrbyfloat key field given_value 把key的field域(浮点型)自增给定值given_value
- hincrby 与 incrby 类似;只不过hincrby 操作key的域,而incrby操作key;
192.168.163.211:6379> hget user:2 age
"22"
#
192.168.163.211:6379> hincrby user:2 age 3
(integer) 25
192.168.163.211:6379> hget user:2 age
"25"
#
192.168.163.211:6379> hincrbyfloat user:2 age 4
"29"
192.168.163.211:6379> hget user:2 age
"29"
【3.1.9】计算value的字符串长度
1)hstrlen key field 获取key的域field对应值的字符串长度
192.168.163.211:6379> hget user:2 addr
"cd2"
192.168.163.211:6379> hstrlen user:2 addr
(integer) 3
【3.1.10】值为哈希类型的命令总结
命令 | 命令描述 | 时间复杂度 |
---|---|---|
hset key field value | 设置key的field域的值为value | O(n),n为field的个数 |
hget key field | 获取key的field域的值 | O(n),n为field的个数 |
hdel key field [field …] | 删除key的field域 | O(1) |
hlen key | 获取key的field域的个数 | O(n),n为field的个数 |
hgetall key | 获取key的所有域值对(域+值) | O(n),n为field的个数 |
hmget field [field…] | 批量获取key的field域的值 | O(1) |
hmset field value [field value…] | 批量设置key的field域的值 | O(1) |
hexists key field | 判断key的field域是否存在 | O(1) |
hkeys key | 获取key的所有域 | O(1) |
hvals key | 获取key的所有域的值 | O(1) |
hsetnx key field value | 当field域不存在才为key设置域值对 | O(1) |
hincrby key field increment | key的field域自增给定值increment(整型) | O(1) |
hincrbyfloat key field increment | key的field域自增给定值increment(浮点型) | O(1) |
hstrlen key field | 获取key的field域的值的字符串长度 | O(1) |
【3.2】内部编码
1)哈希类型的内部编码有2种:
- ziplist-压缩列表:当域个数小于hash-max-ziplist-entries(默认512),且所有域的值的字节数小于hash-max-ziplist-value(默认64字节),redis使用ziplist编码格式存储该哈希类型的值; (补充: ziplist使用更加紧凑的结构实现多个元素的连续存储,比hashtable节省内存)
- hashtable-哈希表:当不满足ziplist条件时,redis使用hashtable编码格式存储该哈希类型的值; (因为ziplist的读写效率会下降,而hashtable的读写时间复杂度为O(1) )
2)object encoding key 查看key的值的编码
# object encoding key 查看key的值的编码
192.168.163.211:6379> object encoding user:1
"listpack"
192.168.163.211:6379> hgetall user:1
1) "addr"
2) "cd"
3) "name"
4) "tom"
5) "age"
6) "18"
【3.3】使用场景
【3.3.1】 哈希类型与关系型数据库的不同
1)哈希类型与关系型数据库的不同
- 哈希类型是稀疏的,关系型数据库是完全结构化的; 如哈希类型每个键可以有不同field,而关系型数据库一旦添加新的列,所有行都要为其设置值;
- 关系型数据库可以做复杂关系查询, redis做复杂查询比较困难;
【3.3.2】缓存用户信息的方法
1)缓存用户信息的3种方法:
- 方法1:原生字符串类型,每个属性1个键;【不推荐】
- 优点:简单直观,每个属性都支持更新操作;
- 缺点:占用过多键,内存占用量较大;
- 方法2:序列化字符串类型,把用户信息序列化后用一个键保存;
- 优点:提供内存使用率;
- 缺点:序列化与反序列化有一定开销;
- 方法3:哈希类型,每个用户属性使用一个域值对存储,但只用一个键保存;
- 优点:简单直观,如果使用合理可以减少内存使用;
- 缺点:控制哈希类型在ziplist和hashtable两种内部编码的转换, hashtable会消耗更多内存;
【4】列表(值的类型为列表)
1)列表类型定义: 用来存储多个有序的字符串, 如 aaa, bbb, ccc, ddd, eeee, 这5个元素从左到右组成了一个有序列表;
- 元素定义: 列表中的每个字符串称为元素;
- 一个列表最多可以存储 2^32-1个元素;
- 常用操作:对列表两端插入(push)或弹出(pop)元素;
2)列表类型有2个特点:
- 列表中的元素是有序的;
- 列表中的元素可以是重复的;
【4.1】命令
【4.1.1】添加元素
1)rpush key value [value…] 从右边插入元素
192.168.163.211:6379> rpush users zhangsan lisi wangwu
(integer) 3
# lrange key 0 -1 可以从左到右获取列表的所有元素
192.168.163.211:6379> lrange users 0 -1
1) "zhangsan"
2) "lisi"
3) "wangwu"
2)lpush key value [value…] 从左边插入元素
192.168.163.211:6379> lpush users zhaoliu tianqi
(integer) 5
192.168.163.211:6379> lrange users 0 -1
1) "tianqi"
2) "zhaoliu"
3) "zhangsan"
4) "lisi"
5) "wangwu"
3)linsert key before | after pivot value 在某个元素前或者后插入元素
192.168.163.211:6379> lrange flags 0 -1
1) "a1"
2) "a2"
3) "a3"
4) "a1"
5) "a4"
# linsert key before | after pivot value 在某个元素前或者后插入元素
192.168.163.211:6379> linsert flags before a1 java
(integer) 6
192.168.163.211:6379>
192.168.163.211:6379> lrange flags 0 -1
1) "java"
2) "a1"
3) "a2"
4) "a3"
5) "a1"
6) "a4"
【4.1.2】查找
1)lrange key start end 获取指定范围内的列表
范围=[start,end],即包含end下标,从0开始计数;
列表下标有2个特点:
- 下标从左到右分别是0到N-1 ;从右到左是 -1 到 -N ;
- lrange中的end选项包含自身;
192.168.163.211:6379> lrange flags 0 -1
1) "java"
2) "a1"
3) "a2"
4) "a3"
5) "a1"
6) "a4"
# lrange key start end 获取指定范围内的列表
192.168.163.211:6379> lrange flags 1 3
1) "a1"
2) "a2"
3) "a3"
2)lindex key index 获取列表指定索引下标的元素
192.168.163.211:6379> lrange flags 0 -1
1) "java"
2) "a1"
3) "a2"
4) "a3"
5) "a1"
6) "a4"
# lindex key index 获取列表指定索引下标的元素
192.168.163.211:6379> lindex flags -1
"a4"
192.168.163.211:6379> lindex flags 0
"java"
192.168.163.211:6379> lindex flags 1
"a1"
3)llen key 获取列表长度
192.168.163.211:6379> lrange flags 0 -1
1) "java"
2) "a1"
3) "a2"
4) "a3"
5) "a1"
6) "a4"
# llen key 获取列表长度
192.168.163.211:6379> llen flags
(integer) 6
【4.1.3】删除
1)lpop key 从列表左侧弹出元素
192.168.163.211:6379> lrange flags 0 -1
1) "java"
2) "a1"
3) "a2"
4) "a3"
5) "a1"
6) "a4"
# lpop key 从列表左侧弹出元素
192.168.163.211:6379> lpop flags
"java"
192.168.163.211:6379> lrange flags 0 -1
1) "a1"
2) "a2"
3) "a3"
4) "a1"
5) "a4"
2)rpop key 从列表右侧弹出元素
192.168.163.211:6379> lrange flags 0 -1
1) "a1"
2) "a2"
3) "a3"
4) "a1"
5) "a4"
# rpop key 从列表右侧弹出元素
192.168.163.211:6379> rpop flags
"a4"
192.168.163.211:6379> lrange flags 0 -1
1) "a1"
2) "a2"
3) "a3"
4) "a1"
3)lrem key count value 删除指定元素
- count=0 删除所有值等于value的元素
- count>0 从左到右删除最多count个值等于value的元素
- count<0 从右到左删除最多(count绝对值)个值等于value的元素
192.168.163.211:6379> lrange toms 0 -1
1) "tom1"
2) "tom1"
3) "tom1"
4) "tom1"
5) "tom2"
6) "tom2"
7) "tom2"
8) "tom3"
9) "tom3"
# lrem key count value 删除指定元素
# lrem toms 3 tom1 从左到右删除最多3个值等于tom1的元素
192.168.163.211:6379> lrem toms 3 tom1
(integer) 3
192.168.163.211:6379> lrange toms 0 -1
1) "tom1"
2) "tom2"
3) "tom2"
4) "tom2"
5) "tom3"
6) "tom3"
count<0 从右到左删除最多(count绝对值)个值等于value的元素
192.168.163.211:6379> lrange toms 0 -1
1) "tom1"
2) "tom2"
3) "tom2"
4) "tom2"
5) "tom3"
6) "tom3"
7) "tom2"
8) "tom2"
9) "tom3"
# count<0 从右到左删除最多(count绝对值)个值等于value的元素
192.168.163.211:6379> lrem toms -3 tom2
(integer) 3
192.168.163.211:6379> lrange toms 0 -1
1) "tom1"
2) "tom2"
3) "tom2"
4) "tom3"
5) "tom3"
6) "tom3"
count=0 删除所有值等于value的元素
192.168.163.211:6379> lrange toms 0 -1
1) "tom1"
2) "tom2"
3) "tom2"
4) "tom3"
5) "tom3"
6) "tom3"
# count=0 删除所有值等于value(=tom3)的元素
192.168.163.211:6379> lrem toms 0 tom3
(integer) 3
192.168.163.211:6379> lrange toms 0 -1
1) "tom1"
2) "tom2"
3) "tom2"
4)ltrim key start end 按照索引范围修剪key
保留[start, end]范围内的元素,其他元素删除,下标从0开始计数;
192.168.163.211:6379> lrange flags 0 -1
1) "a1"
2) "a2"
3) "a3"
4) "a4"
5) "a5"
6) "a6"
# ltrim key start end 按照索引范围修剪key
# ltrim flags 1 3 保留1号下标到3号下标范围的元素,即第2个到第4个这个范围的元素
192.168.163.211:6379> ltrim flags 1 3
OK
192.168.163.211:6379> lrange flags 0 -1
1) "a2"
2) "a3"
3) "a4"
【4.1.4】修改
1)lset key index newValue 修改指定下标的元素
192.168.163.211:6379> lrange flags 0 -1
1) "a2"
2) "a3"
3) "a4"
# lset key index newValue 修改指定下标的元素
192.168.163.211:6379> lset flags 0 a22
OK
192.168.163.211:6379> lrange flags 0 -1
1) "a22"
2) "a3"
3) "a4"
【4.1.5】阻塞
1)blpop key [key …] timeout 从左边阻塞式弹出
同理, brpop key [key …] timeout 从右边阻塞式弹出
192.168.163.211:6379> lrange flags 0 -1
1) "a22"
2) "a3"
3) "a4"
# blpop key [key ...] timeout 从左边阻塞式弹出
192.168.163.211:6379> blpop flags 1
1) "flags"
2) "a22"
192.168.163.211:6379> blpop flags 1
1) "flags"
2) "a3"
192.168.163.211:6379> lrange flags 0 -1
1) "a4"
补充: 有2个参数
- key [key…] 多个类型为列表的键;
- timeout 阻塞时间(单位秒)
2)如果列表为空,则客户端阻塞直到超时;
192.168.163.211:6379> lrange flags 0 -1
(empty array)
# 列表为空,则客户端阻塞直到超时
192.168.163.211:6379> blpop flags 10
(nil)
(10.08s)
3)若阻塞期间,有元素,则立即返回;如果阻塞时间为0,则客户端一直阻塞下去;
192.168.163.211:6379> lrange flags 0 -1
(empty array)
# 若阻塞期间,有元素,则立即返回
192.168.163.211:6379> blpop flags 10
1) "flags"
2) "a1"
(3.31s) # 当前列表为空,所以阻塞;阻塞期间有其他客户端插入元素,则立即弹出元素,整个过程耗时3.31秒
【4.1.6】值为列表类型的命令总结
操作 | 命令 | 描述 | 时间复杂度 |
---|---|---|---|
添加 | rpush/lpush key value [value…] | 从列表右侧/左侧插入 | O(k) k是元素个数 |
添加 | linsert key before|after pivot value | 在元素pivot前或后插入元素value | O(n) n是pivot到列表头或尾的距离 |
查找 | lrange key start end | 获取范围内的列表 | O(s+n) s是start,n是范围长度 |
查找 | lindex key index | 获取指定下标的元素 | O(n) n是索引偏移量 |
查找 | llen key | 获取列表长度 | O(1) |
删除 | lpop/rpop key | 从列表右侧/左侧弹出 | O(1) |
删除 | lrem key count value | 删除值等于value的元素,count有3种取值 | O(n) n是列表长度 |
删除 | ltrim key start end | 按照范围修剪key,保留范围内的元素,其他元素删除 | O(n) n是要裁剪的元素总数 |
修改 | lset key index value | 修改指定下标的元素 | O(1) n是索引偏移量 |
阻塞操作 | blpop/brpop key timeout | 从左边/右边阻塞式弹出,超时时间timeout秒 | O(1) |
备注:命令的下标从0开始计数,下标范围包含start,end下标上的元素
【4.2】内部编码
1)列表类型的内部编码有2种(ziplist + linkedlist):
- ziplist 压缩列表: 当列表的元素个数小于 list-max-ziplist-entries 配置(默认512),同时列表中每个元素的值的字节数小于list-max-ziplist-value 配置(默认64字节),redis会选择使用ziplist数据结构来保存列表,以减少内存使用;
- linkedlist链表:当列表类型无法满足ziplist的条件时,redis会使用linkedlist数据结构保存列表;
192.168.163.211:6379> object encoding flags
"quicklist"
补充: quicklist结合了ziplist和linkedlist两者的优势,为列表类型提供了更为优秀的内部编码实现;
【4.3】使用场景
【4.3.1】消息队列
1)使用lpush + brpop命令即可实现阻塞队列;
- 生产者使用 lpush 从列表左侧插入元素;
- 多个消费者使用 brpop 命令阻塞式抢列表尾部元素;
2)多个客户端保证了消费的负载均衡和高可用性;
【4.3.2】文章列表
1)每个用户有自己的文章列表,现在需要分页展示列表。
- 可以考虑使用列表保存文章;
2)使用列表保存文章的问题:
- 问题1:如果每次分页查询到的文章太多,需要执行多次hgetall操作;
- 解决方法:可以考虑使用 pipeline批量获取,或者把文章数据序列化为字符串类型,使用mget批量获取;
- 问题2:分页获取文章列表时,lrange在列表两端性能好,在列表中间范围性能差;(底层是双向链表)
- 解决方法:考虑把列表做二级拆分,或者使用redis的quicklist内部编码实现,在中间范围高效分页;
【4.3.3】列表类型的使用场景总结
1)列表类型的使用场景:
- lpush + lpop = 栈,先进后出;
- lpush + rpop = 队列,先进先出;
- lpush + ltrim = 有限集合;
- 左侧插入,左侧裁剪,仅保留范围内元素,其他元素裁剪掉;
- lpush + brpop = 消息队列;
- 左侧插入,右侧阻塞式弹出元素;
【5】集合(值的类型为集合)
1)集合类型定义:集合类型用于保存多个字符串元素,但不允许重复元素,且元素无序,不能通过通过下标获取元素;
- 1个集合最多可以存储 2^32-1个元素;
- 集合除了增删改查,还支持多个集合取交集,并集,差集;
【5.1】命令
【5.1.1】集合内操作
1)sadd key element [element…] 添加元素
返回结果为添加成功的元素个数;
192.168.163.211:6379> sadd set1 a1 a2 a3 a4
(integer) 4
# smembers key 获取所有元素,显然无序
192.168.163.211:6379> smembers set1
1) "a2"
2) "a3"
3) "a4"
4) "a1"
2)srem key element [element…] 删除元素
返回结果为成功删除的元素个数;
192.168.163.211:6379> smembers set1
1) "a2"
2) "a3"
3) "a4"
4) "a1"
# 删除元素
192.168.163.211:6379> srem set1 a1 a2
(integer) 2
192.168.163.211:6379> smembers set1
1) "a3"
2) "a4"
# 删除不存在的元素,返回0
192.168.163.211:6379> srem set1 a1 a2
(integer) 0
3) scard key 计算元素个数
192.168.163.211:6379> smembers set1
1) "a3"
2) "a4"
# scard key 计算元素个数
192.168.163.211:6379> scard set1
(integer) 2
4)sismember key member 判断元素member是否在key为键的集合中
存在返回1,不存在返回0
192.168.163.211:6379> smembers set1
1) "a3"
2) "a4"
# sismenber key member 判断元素member是否在key为键的集合中
192.168.163.211:6379> sismember set1 a3
(integer) 1
192.168.163.211:6379> sismember set1 a4
(integer) 1
192.168.163.211:6379> sismember set1 a5
(integer) 0
5)srandmember key [count] 随机从集合返回指定个数元素
192.168.163.211:6379> smembers set1
1) "a5"
2) "a6"
3) "a3"
4) "a4"
# srandmember key [count] 随机从集合返回指定个数元素
192.168.163.211:6379> srandmember set1 3
1) "a3"
2) "a6"
3) "a4"
6)spop key 从集合随机弹出元素
192.168.163.211:6379> smembers set1
1) "a5"
2) "a6"
3) "a3"
# spop key 从集合随机弹出元素
192.168.163.211:6379> spop set1
"a5"
192.168.163.211:6379> smembers set1
1) "a6"
2) "a3"
【注意1】 srandmember 和 spop 都是随机从集合选出元素, 两者不同的是spop命令会删除元素,而srandmember不会;
【注意2】smembers 和 lrange, hgetall 都属于比较重的命令,如果元素过多可能阻塞redis,可以使用sscan来完成;
【5.1.2】集合间操作
1)sinter key [key…] 求多个集合的交集
192.168.163.211:6379> smembers class1
1) "a2"
2) "a3"
3) "a4"
4) "a1"
192.168.163.211:6379> smembers class2
1) "a5"
2) "a2"
3) "a6"
4) "a4"
# sinter key [key...] 求多个集合的交集
192.168.163.211:6379> sinter class1 class2
1) "a2"
2) "a4"
2)sunion key [key…] 求多个集合的并集
# sunion key [key...] 求多个集合的并集
192.168.163.211:6379> sunion class1 class2
1) "a5"
2) "a3"
3) "a4"
4) "a2"
5) "a6"
6) "a1"
3)sdiff key [key…] 求多个集合的差集
sdiff key1 key2 获取key1集合中有的而key2集合中没有的元素;
# sdiff key [key...] 求多个集合的差集
192.168.163.211:6379> sdiff class1 class2
1) "a3"
2) "a1"
4)把交集,并集,差集的结果保存
- sinterstore result_key key1 key2 把key1集合与key2集合的交集结果保存到result_key集合;
- sunionstore result_key key1 key2 把key1集合与key2集合的并集结果保存到result_key集合;
- sdiffstore result_key key1 key2 把key1集合与key2集合的差集结果保存到result_key集合;
# sinterstore result_key key1 key2 把key1集合与key2集合的交集结果保存到result_key集合
192.168.163.211:6379> sinterstore key3_1 class1 class2
(integer) 2
# sunionstore result_key key1 key2 把key1集合与key2集合的并集结果保存到result_key集合
192.168.163.211:6379> sunionstore key3_2 class1 class2
(integer) 6
# sdiffstore result_key key1 key2 把key1集合与key2集合的差集结果保存到result_key集合
192.168.163.211:6379> sdiffstore key3_3 class1 class2
(integer) 2
192.168.163.211:6379> smembers key3_1
1) "a2"
2) "a4"
192.168.163.211:6379> smembers key3_2
1) "a5"
2) "a3"
3) "a4"
4) "a2"
5) "a6"
6) "a1"
192.168.163.211:6379> smembers key3_3
1) "a3"
2) "a1"
【5.1.3】值为集合类型的命令总结
操作 | 命令 | 描述 | 时间复杂度 |
---|---|---|---|
新增 | sadd key element [element…] | 新增元素 | O(k) k是元素个数 |
删除 | srem key element [element…] | 删除元素 | O(k) k是元素个数 |
删除 | spop key | 弹出元素 | O(1) |
查找 | scard key | 计算元素个数 | O(1) |
查找 | sismember key element | 判断元素element在key集合中是否存在 | O(1) |
查找 | srandmember key [count] | 随机从集合返回指定个数元素 | O(count) |
查找 | smembers key | 获取所有元素 | O(n) n为元素总数 |
集合运算 | sinter key [key…] | 计算集合交集 | O(m*k) m是键个数,k是多个集合中元素最少的个数 |
集合运算 | sunion key [key…] | 计算集合并集 | O(k) k是多个集合元素个数和 |
集合运算 | sdiff key [key…] | 计算集合差集 | O(k) k是多个集合元素个数和 |
集合运算 | sinterstore key3 key1 key2 | 计算集合交集并存储到key3 | O(m*k) m是键个数,k是多个集合中元素最少的个数 |
集合运算 | sunionstore key3 key1 key2 | 计算集合并集并存储到key3 | O(k) k是多个集合元素个数和 |
集合运算 | sdiffstore key3 key1 key2 | 计算集合差集并存储到key3 | O(k) k是多个集合元素个数和 |
【5.2】内部编码
1)集合类型的内部编码有2种:
- intset 整数集合:当集合中的元素都是整数且元素个数小于 set-max-intset-entries 配置(默认512个)时,redis会使用intset来存储集合数据,从而减少内存使用;
- hashtable 哈希表:当无法满足intset条件时,redis使用哈希表存储集合数据;
2)object encoding 查看键的内部编码:
# object encoding 查看键的内部编码
192.168.163.211:6379> object encoding key3_2
"hashtable"
192.168.163.211:6379> smembers key3_2
1) "a5"
2) "a3"
3) "a4"
4) "a2"
5) "a6"
6) "a1"
【5.3】使用场景
1)集合类型比较典型的应用场景是标签;
- 如用户贴上娱乐,体育的标签;
【5.3.1】集合使用场景总结
1)标签: sadd
2)生成随机数,比如抽奖; spop/srandmember
3)社交需求: sadd + sinter
【6】有序集合
1)有序集合类型定义: 有序集合是一种集合,不允许有重复元素,但可以排序;
- 通过给元素设置一个分数,然后根据元素分数来排序;
2)列表、集合、有序集合的不同点:
数据结构 | 是否允许重复 | 是否有序 | 有序实现方式 | 应用场景 |
---|---|---|---|---|
列表 | 是 | 是 | 索引下标 | 时间轴,消息队列 |
集合 | 否 | 否 | 无 | 标签,社交 |
有序集合 | 否 | 是 | 分数 | 排行榜系统,社交等 |
【6.1】命令
【6.1.1】有序集合内命令
1)zadd key score member [score member…] 添加成员
# zadd key score member [score member...] 添加成员
192.168.163.211:6379> zadd class3 1.1 tom1 1.2 tom2 1.3 tom3
(integer) 3
# zrange key start end [withscores] 返回指定排名范围的成员
192.168.163.211:6379> zrange class3 0 -1 withscores
1) "tom1"
2) "1.1000000000000001"
3) "tom2"
4) "1.2"
5) "tom3"
6) "1.3"
192.168.163.211:6379> zrange class3 0 -1
1) "tom1"
2) "tom2"
3) "tom3"
# zadd key score member [score member...] 添加成员
192.168.163.211:6379> zadd class3 0.4 tom4
(integer) 1
192.168.163.211:6379> zrange class3 0 -1
1) "tom4"
2) "tom1"
3) "tom2"
4) "tom3"
补充1:有序集合相比集合提供了排序字段,但也有代价;
- zadd的时间复杂度为 O(log(n))
- sadd的时间复杂度为 O(1)
补充2: zadd命令添加了 nx, xx, ch, incr 4个选项; 【 格式为 zadd key [NX|XX] [GT|LT] [CH] [INCR] score member [score member …] 】
- nx:member不存在才可以设置成功; 用于新增;
- xx:member存在才可以设置成功;用于更新;
- ch:返回此次操作后,有序集合元素和分数发生变化的个数;
- incr: 对score做增加, 相当于 zincrby ;
192.168.163.211:6379> zrange class3 0 -1 withscores
1) "tom4"
2) "44"
# xx:member存在才可以设置成功;用于更新;
192.168.163.211:6379> zadd class3 xx 45 tom4
(integer) 0
192.168.163.211:6379> zrange class3 0 -1 withscores
1) "tom4"
2) "45"
# nx:member不存在才可以设置成功; 用于新增; 显然tom4存在,所以新增失败
192.168.163.211:6379> zadd class3 nx 46 tom4
(integer) 0
192.168.163.211:6379> zrange class3 0 -1 withscores
1) "tom4"
2) "45"
# nx:member不存在才可以设置成功; 用于新增; 显然tom5不存在,所以新增【成功】
192.168.163.211:6379> zadd class3 nx 51 tom5
(integer) 1
192.168.163.211:6379> zrange class3 0 -1 withscores
1) "tom4"
2) "45"
3) "tom5"
4) "51"
# incr: 对score做增加
192.168.163.211:6379> zadd class3 incr 1 tom5
"52"
192.168.163.211:6379> zrange class3 0 -1 withscores
1) "tom4"
2) "45"
3) "tom5"
4) "52"
2)zcard key 计算成员个数
192.168.163.211:6379> zrange class3 0 -1 withscores
1) "tom4"
2) "45"
3) "tom5"
4) "52"
# zcard key 计算成员个数
192.168.163.211:6379> zcard class3
(integer) 2
3)zsocre key member 计算某个成员的分数
192.168.163.211:6379> zscore class3 tom4
"45"
4)zrank key member 计算成员排名(升序, 排名从0开始算起)
zrevrank key member (降序, 排名从0开始算起)
192.168.163.211:6379> zrange class3 0 -1 withscores
1) "tom2"
2) "21"
3) "tom3"
4) "31"
5) "tom4"
6) "45"
7) "tom5"
8) "52"
# zrank key member 计算成员排名(升序,排名从0开始算起)
192.168.163.211:6379> zrank class3 tom3
(integer) 1 # 顺数第1
# zrevrank key member (降序,排名从0开始算起)
192.168.163.211:6379> zrevrank class3 tom3
(integer) 2 # 倒数第2
5)zrem key member [member…] 删除成员
192.168.163.211:6379> zrange class3 0 -1 withscores
1) "tom2"
2) "21"
3) "tom3"
4) "31"
5) "tom4"
6) "45"
7) "tom5"
8) "52"
# zrem key member [member...] 删除成员
192.168.163.211:6379> zrem class3 tom2 tom3
(integer) 2
192.168.163.211:6379> zrange class3 0 -1 withscores
1) "tom4"
2) "45"
3) "tom5"
4) "52"
6)zincrby key increment member 增加成员分数
192.168.163.211:6379> zrange class3 0 -1 withscores
1) "tom4"
2) "45"
3) "tom5"
4) "52"
# zincrby key increment member 增加成员分数
192.168.163.211:6379> zincrby class3 1 tom4 # tom4的分数加1
"46"
192.168.163.211:6379> zrange class3 0 -1 withscores
1) "tom4"
2) "46"
3) "tom5"
4) "52"
7)zrange key start end [withscores] 返回指定排名范围的成员 (升序)
zrevrange key start end [withscores] (降序)
# zrange key start end [withscores] 返回指定排名范围的成员 (升序)
192.168.163.211:6379> zrange class3 0 -1 withscores
1) "tom2"
2) "21"
3) "tom3"
4) "31"
5) "tom4"
6) "46"
7) "tom5"
8) "52"
9) "tom6"
10) "61"
# zrevrange key start end [withscores] (降序)
192.168.163.211:6379> zrevrange class3 0 -1
1) "tom6"
2) "tom5"
3) "tom4"
4) "tom3"
5) "tom2"
8)zrangebyscore key min max [withscores] [limit offset count] 返回指定分数范围的成员(升序)
zrevrangebyscore key min max [withscores] [limit offset count] 返回指定分数范围的成员(降序)
192.168.163.211:6379> zrange class3 0 -1 withscores
1) "tom2"
2) "21"
3) "tom3"
4) "31"
5) "tom4"
6) "46"
7) "tom5"
8) "52"
9) "tom6"
10) "61"
# zrangebyscore key min max [withscores] [limit offset count] 返回指定分数范围的成员(升序)
192.168.163.211:6379> zrangebyscore class3 45 65 withscores
1) "tom4"
2) "46"
3) "tom5"
4) "52"
5) "tom6"
6) "61"
9)zcount key min max 返回指定分数范围成员个数
192.168.163.211:6379> zrangebyscore class3 45 65 withscores
1) "tom4"
2) "46"
3) "tom5"
4) "52"
5) "tom6"
6) "61"
# zcount key min max 返回指定分数范围成员个数
192.168.163.211:6379> zcount class3 45 65
(integer) 3
10)zremrangebyrank key start end 删除指定排名范围内的升序元素
192.168.163.211:6379> zrange class3 0 -1 withscores
1) "tom2"
2) "21"
3) "tom3"
4) "31"
5) "tom4"
6) "46"
7) "tom5"
8) "52"
9) "tom6"
10) "61"
# zremrangebyrank key start end 删除指定排名内的升序元素
192.168.163.211:6379> zremrangebyrank class3 1 3
(integer) 3
192.168.163.211:6379> zrange class3 0 -1 withscores
1) "tom2"
2) "21"
3) "tom6"
4) "61"
11)zremrangebysocre key min max 删除指定分数范围的成员
192.168.163.211:6379> zrange class3 0 -1 withscores
1) "tom2"
2) "21"
3) "tom3"
4) "32"
5) "tom4"
6) "42"
7) "tom5"
8) "52"
9) "tom6"
10) "61"
# zremrangebysocre key min max 删除指定分数范围的成员
192.168.163.211:6379> zremrangebyscore class3 35 55
(integer) 2
192.168.163.211:6379> zrange class3 0 -1 withscores
1) "tom2"
2) "21"
3) "tom3"
4) "32"
5) "tom6"
6) "61"
【6.1.2】有序集合间的操作命令
1) 计算交集
zinterstore result_key numkeys key [key …] [WEIGHTS weight [weight …]] [AGGREGATE SUM|MIN|MAX]
- result_key:结果键;
- numkeys: 参与交集运算的键的个数;
- key [key…] : 需要做交集计算的键;
- WEIGHTS weight [weight …] :每个键的权重;即每个member用自己的分数乘以对应权重;默认权重为1;
- AGGREGATE SUM|MIN|MAX :计算交集后,分数可以按照sum求和, min最小值,max最大值进行聚合,默认为sum;
192.168.163.211:6379> zrange class1 0 -1 withscores
1) "tom2"
2) "21"
3) "tom3"
4) "31"
5) "tom4"
6) "41"
7) "tom5"
8) "51"
192.168.163.211:6379> zrange class2 0 -1 withscores
1) "tom2"
2) "22"
3) "tom3"
4) "32"
5) "tom4"
6) "42"
7) "tom5"
8) "52"
# zinterstore result_key numkeys key [key ...] 计算交集
192.168.163.211:6379> zinterstore result_key 2 class1 class2
(integer) 4
192.168.163.211:6379> zrange result_key 0 -1 withscores
1) "tom2"
2) "43"
3) "tom3"
4) "63"
5) "tom4"
6) "83"
7) "tom5"
8) "103"
2)计算并集
zunionstore result_key numkeys key [key …] [WEIGHTS weight [weight …]] [AGGREGATE SUM|MIN|MAX]
参数解释如计算交集;
# zunionstore result_key numkeys key [key ...] 计算并集
192.168.163.211:6379> zunionstore result_key2 2 class1 class2
(integer) 4
192.168.163.211:6379> zrange result_key2 0 -1 withscores
1) "tom2"
2) "43"
3) "tom3"
4) "63"
5) "tom4"
6) "83"
7) "tom5"
8) "103"
【6.1.3】有序集合类型的命令总结
操作 | 命令 | 描述 | 时间复杂度 |
---|---|---|---|
新增 | zadd key score member [score member…] | 添加成员 | O(k*logn) k是添加成员个数,n是有序集合成员个数 |
查询 | zcard key | 计算成员个数 | O(1) |
查询 | zscore key member | 计算成员分数 | O(1) |
查询 | zrank key memberzrevrank key member | 计算成员排名 | O(logn) n是有序集合成员个数 |
查询 | zrange key start end [withscores]zrevrange key start end [withscores] | 返回指定排名范围内的成员-升序, zrevrange-降序 | O(k+logn) k是要获取的成员个数,n是有序集合成员个数 |
查询 | zrangebyscore key min max [withscores]zrevrangebysocre key min max [withscores] | 返回指定分数范围的成员-升序, zrevrangebyscore-降序 | O(k+logn) k是要获取的成员个数,n是有序集合成员个数 |
查询 | zcount key min max | 返回指定分数范围成员个数 | O(logn) n是有序集合成员个数 |
删除 | zrem key member [member…] | 删除成员 | O(k*logn) k是删除成员个数 n是有序集合成员个数 |
删除 | zremrangebyrank key start end | 删除指定排名范围内的升序元素 | O(k+logn) k是要删除的成员个数,n是有序集合成员个数 |
删除 | zremrangebyscore key start end | 删除指定分数范围内的升序元素 | O(k+logn) k是要删除的成员个数,n是有序集合成员个数 |
更新 | zincrby key increment member | 增加成员分数 | O(logn) n是有序集合成员个数 |
集合运算 | zinterstore result_key numkeys key [key…] | 计算交集并存储结果为key | O(nk)+O(mlogm) n是最小有序集合成员个数,k是有序集合的个数,m是结果集成员个数 |
集合运算 | zunionstore result_key numkeys key [key…] | 计算并集并存储结果为key | O(n)+O(m*logm) n是有序集合成员个数和,m是结果集成员个数 |
【6.2】内部编码
1)有序集合类型的内部编码有2种:
- ziplist 压缩列表:当有序集合的元素个数小于 zset-max-ziplist-entries配置(默认128),同时每个元素的值所占字节都小于 zset-max-ziplist-value配置(默认64字节)是,redis使用ziplist来存储有序集合数据,以减少内存使用 ;
- skiplist 跳跃表: 当ziplist条件不满足时,redis使用skiplist存储有序集合数据,因此此时ziplist读写效率会下降;
2)查看有序集合编码
192.168.163.211:6379> zrange class3 0 -1 withscores
1) "tom2"
2) "21"
3) "tom3"
4) "32"
5) "tom6"
6) "61"
192.168.163.211:6379> object encoding class3
"listpack"
192.168.163.211:6379> zadd class3 99 tom999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
(integer) 1
192.168.163.211:6379> object encoding class3
"skiplist" # 类型转为skiplist
【6.3】使用场景
1)有序集合典型的使用场景是: 排行榜系统;
2)排行榜系统主要实现4个功能:
- 用户赞数; zadd + zincrby
- 新增用户赞数: zadd
- 更新用户赞数: zincrby
- 取消用户赞数(把用户从排行榜取消掉);
- zrem key member
- 展示获取赞数最多的用户;
- zrevrangebyrank
- 展示用户信息以及用户分数;
- 把用户名作为键后缀, 将用户信息保存在哈希类型中;用户分数和排名可以使用zscore和zrank两个功能;
- hgetall key
- zscore key member
- zrank key member
- 把用户名作为键后缀, 将用户信息保存在哈希类型中;用户分数和排名可以使用zscore和zrank两个功能;
【7】键管理
【7.1】单个键管理
【7.1.1】键重命名
1)rename key newKey 键重命名
192.168.163.211:6379> set name tom
OK
# rename key newKey 键重命名
192.168.163.211:6379> rename name name2
OK
192.168.163.211:6379> get name
(nil)
192.168.163.211:6379> get name2
"tom"
补充:rename前,若newKey已存在,则newKey的原值会被覆盖;
192.168.163.211:6379> set a b
OK
192.168.163.211:6379> set c d
OK
# rename前,若newKey已存在,则newKey的原值会被覆盖
192.168.163.211:6379> rename a c # 把键a重命名为键c
OK
192.168.163.211:6379> get a
(nil)
192.168.163.211:6379> get c
"b" # 键c原值为d,而rename后,被覆盖了,为b
2)renamenx key newKey 当newKey不存在时才执行键重命名
192.168.163.211:6379> set name1 tom1
OK
192.168.163.211:6379> set name2 tom2
OK
# renamenx key newKey 当newKey不存在时才执行键重命名
192.168.163.211:6379> renamenx name1 name2
(integer) 0
# renamenx key newKey 当newKey不存在时才执行键重命名
192.168.163.211:6379> renamenx name1 name1_1
(integer) 1
192.168.163.211:6379> get name1
(nil)
192.168.163.211:6379> get name2
"tom2"
192.168.163.211:6379> get name1_1
"tom1"
【补充】rename键重命名的注意点:
- 注意点1:重命名时会执行del命令删除旧键,如果键对应的值所占内存比较大,可能会阻塞redis;
- 注意点2:如果rename与renamenx中key与newKey相同,则重命名也可以成功;
192.168.163.211:6379> get name2
"tom2"
# 如果rename中key与newKey相同,则重命名也可以成功
192.168.163.211:6379> rename name2 name2
OK
192.168.163.211:6379> get name2
"tom2"
192.168.163.211:6379> get name2
"tom2"
# 如果renamenx中key与newKey相同,则重命名也可以成功
192.168.163.211:6379> renamenx name2 name2
(integer) 0
192.168.163.211:6379> get name2
"tom2"
【7.1.2】随机返回1个键
1)randomkey 随机返回1个键
192.168.163.211:6379> keys *
1) "c"
2) "name1_1"
3) "name2"
# randomkey 随机返回1个键
192.168.163.211:6379> randomkey
"c"
【7.1.3】键过期
键过期的命令除了expire,ttl外,redis还提供了expireat, pexpire, pexpireat, pttl, persist 等命令
1)expire key seconds 键在seconds秒后过期
# expire key seconds 键在seconds秒后过期
192.168.163.211:6379> expire name2 10
(integer) 1
# ttl 查看剩余存活时间
192.168.163.211:6379> ttl name2
(integer) 8
192.168.163.211:6379> ttl name2
(integer) 1
192.168.163.211:6379> ttl name2
(integer) 0
192.168.163.211:6379> ttl name2
(integer) -2 # -2 键不存在
ttl与pttl都可以查看键的剩余存活时间,但ttl单位是秒,而pttl单位是毫秒,有3种返回值;
- 大于等于0的整数: 键剩余存活时间(ttl是秒,pttl是毫秒)
- -1: 键没有设置过期时间;
- -2:键不存在;
2)expireat key timestamp 键在秒级时间戳timestamp时刻后过期
192.168.163.211:6379> set name2 tom2
OK
192.168.163.211:6379>
# expireat key timestamp 键在秒级时间戳timestamp时刻后过期
192.168.163.211:6379> expireat name2 1734752199
(integer) 1
192.168.163.211:6379>
192.168.163.211:6379> get name2
"tom2"
192.168.163.211:6379> ttl name2
(integer) 75
192.168.163.211:6379> ttl name2
(integer) 74
192.168.163.211:6379> ttl name2
(integer) 73
补充:设置过期时间的其他命令
- pexpire key milliseconds 键在milliseconds毫秒后过期
- pexpire key milliseconds-timestamp 键在毫秒级时间戳timestamp时刻后过期
3)键过期命令的注意点:
注意点1:如果 expire key 时key不存在,则返回结果为0
192.168.163.211:6379> keys *
1) "c"
2) "name1_1"
# 如果 expire key 时key不存在,则返回结果为0
192.168.163.211:6379> expire name2 10
(integer) 0
注意点2:如果过期时间为负值,键会被立即删除,犹如使用del命令一样;
192.168.163.211:6379> get name2
"tom2"
192.168.163.211:6379>
# 过期时间为负值,键会被立即删除,犹如使用del命令一样 (过期时间设置为-2,则该键立即被删除)
192.168.163.211:6379> expire name2 -2
(integer) 1
192.168.163.211:6379> get name2
(nil)
192.168.163.211:6379> ttl name2
(integer) -2
注意点3:persist命令可以把键的过期时间清除:
192.168.163.211:6379> expire name2 20
(integer) 1
192.168.163.211:6379> ttl name2
(integer) 18
192.168.163.211:6379> ttl name2
(integer) 16
# persist命令可以把键的过期时间清除
192.168.163.211:6379> persist name2
(integer) 1
192.168.163.211:6379> ttl name2
(integer) -1 # -1表示键永不过期
注意点4:对于字符串类型键,执行set命令会去掉过期时间,默认为-1(永不过期)
192.168.163.211:6379> get name3
"tom3"
192.168.163.211:6379> expire name3 100
(integer) 1
192.168.163.211:6379> ttl name3
(integer) 97
# 对于字符串类型键,执行set命令会去掉过期时间,默认为-1
192.168.163.211:6379> set name3 tom31
OK
192.168.163.211:6379> ttl name3
(integer) -1
注意点5:redis不支持二级数据结构(如哈希,列表)内部元素的过期功能;
注意点6:setex命令=set+expire命令, 不仅是原子执行,还减少1次网络io;
【7.2】遍历键
1)redis提供了2个命令遍历键,分别是keys和scan;
【7.2.1】全量遍历键-keys(不建议)
1)keys pattern 遍历满足pattern通配符的所有键
192.168.163.211:6379> keys *
1) "c"
2) "name2"
3) "name1_1"
4) "name3"
192.168.163.211:6379> keys name*
1) "name2"
2) "name1_1"
3) "name3"
pattern通配符语法解释:
* 匹配任意字符
? 匹配单个字符
[] 匹配部分字符; 如 [1,3]匹配1,3 ;而[1-10] 匹配1到10的任意数字
\x 用于转义,如匹配 * ? 等;
2)keys的问题:如果redis包含了大量键,执行keys命令很可能会造成阻塞;所以不建议在生产环境使用keys ;
【解决keys遍历键阻塞的问题】
- 使用scan遍历键,能够有效防止阻塞;
【7.2.2】渐进式遍历-scan(推荐-常用)
1) scan cursor [match pattern] [count number]
- cursor:必须参数,是一个游标,第1次遍历从0开始,每次scan遍历完都会返回当前游标的值,直到游标值为0,表示遍历结束;
- match pattern:可选参数,作用是做模式匹配,与keys的通配符模式类似;
- count nuber: 可选参数, 表示每次要遍历的键个数,默认是10;
2)scan遍历示例
192.168.163.211:6379> keys *
1) "r"
2) "i"
3) "x"
4) "e"
5) "o"
6) "n"
7) "f"
8) "b"
9) "k"
10) "m"
11) "s"
12) "w"
13) "v"
14) "a"
15) "g"
16) "z"
17) "h"
18) "p"
19) "u"
20) "q"
21) "t"
22) "j"
23) "y"
24) "c"
25) "d"
26) "l"
# scan 第1次遍历从0开始
192.168.163.211:6379> scan 0
1) "26" # 26-每次scan遍历完都会返回当前游标的值
2) 1) "r"
2) "i"
3) "k"
4) "z"
5) "h"
6) "y"
7) "c"
8) "o"
9) "a"
10) "m"
# 第n次遍历传入第n-1次遍历返回的游标值
192.168.163.211:6379> scan 26
1) "9"
2) 1) "j"
2) "f"
3) "b"
4) "u"
5) "q"
6) "l"
7) "x"
8) "e"
9) "w"
10) "v"
192.168.163.211:6379> scan 9
1) "0" # 游标值为0,表示遍历结束
2) 1) "n"
2) "p"
3) "d"
4) "g"
5) "s"
6) "t"
# 如果再从0开始遍历, 则又从头开始
192.168.163.211:6379> scan 0
1) "26"
2) 1) "r"
2) "i"
3) "k"
4) "z"
5) "h"
6) "y"
7) "c"
8) "o"
9) "a"
10) "m"
补充:渐进式遍历命令列表
- scan: 遍历字符串类型键;
- hscan: 遍历哈希类型键;
- sscan:遍历集合类型键;
- zscan:遍历有序集合类型键;
3)渐进式遍历命令优缺点;
- 优点:解决了如 hgetall,smemebers, zrange可能产生的阻塞问题
- 缺点:如果在scan过程中键有变化(如新增,删除,修改),那么遍历效果可能会碰到如下问题:
- 新增的键没有遍历到,遍历出了重复键的情况; 即若键有变化,scan命令并不能遍历所有键 ;
【8】数据库管理
【8.1】redis数据库
1)redis数据库概述:
- redis使用数字来区分数据库,redis单个实例默认配置有16个数据库,编号从0~15;
- 当客户端连接到redis服务器,默认连接到0号redis数据库;
- redis数据库之间没有任何关联,如0号数据库与15号数据库可以有相同的键;
【8.1.1】redis切换数据库(仅了解)
- select dbNum 切换到dbNum表示的数据库
# 切换到0号数据库
192.168.163.211:6379[15]> select 0
OK
192.168.163.211:6379> set name0 tom00
OK
192.168.163.211:6379> get name0
"tom00"
# 切换到15号数据库
192.168.163.211:6379> select 15
OK
192.168.163.211:6379[15]> get name0
"tom0"
【显然】0号数据库与15号数据库都可以有name0这个key,但它们的值是不同的;即redis数据库之间是没有物理关联的;
【8.1.1】redis3.0后弱化多数据库
1)redis3.0已经弱化了多数据库功能; 如redis分布式实现redis Cluster只允许使用0号数据库,只不过为了向前兼容,多数据库功能没有被完全废弃;
2)redis3.0弱化多数据库功能原因:
- redis是单线程;
- 多数据库的使用方式,会让调试与运维变得可能;(开发或运维过程中,我们希望技术门槛能够简单一点,上手快一点)
- 部分redis的客户端不支持j;
3)如何模拟实现redis多数据库:可以在一台物理机器上部署多个redis实例;
【8.2】清除数据库(谨慎使用)
1)清除数据库:
- flushdb: 清除当前数据库;
- flushall : 清除所有数据库;
【注意】清除数据库,谨慎使用
- flushdb、flushall会清除所有数据,一旦误操作后果不堪设想;
- 如果键很多,则flushdb、flushall存在阻塞redis的可能性;