- 全局命令
- 单线程架构💖💖
- 字符串类型
- 字符串操作
- 设置值:
set key value [ex seconds] [px milliseconds] [nx|xx]
- 获取值:
get key
成功返回获取到的值,失败返回nil - setex和setnx
- 批量设置/获取值:
mset key value [key value ...] mget key [key ...]
- 计数
- 追加值:
append _key_ val
向key代表的字符串中追加值val - 求字符串长度:
strlen _key_
- 设置并返回值:
getset _key_ value
getset会设置key的为value,并返回原来的值 - 设置指定位置的字符:
setrange _key_ offset value
- 获取部分字符串:
getrange _key_ start end
- 设置值:
- 字符串操作时间复杂度
- 字符串编码
- 哈希操作时间复杂度
- 哈希的内部编码
- 字符串操作
- 列表
- 集合
- 有序集合
- 有序集合操作
- 添加成员:
zadd key [nx|xx] [ch] [incr] score member [score2 member2 ...]
- 计算成员个数:
zcard key
- 计算某个成员的分数:
zscore key member
如成员不存在则返回nil 。 - 计算成员排名:
zrank key member
、zrevrank key member
- 删除成员:
zrem key member [member2 ...]
返回成功删除的个数 - 增加成员分数:
zincrby key _incrnum_ member
- 返回指定排名范围内的成员:
zrange|zrevrange key start end [withscores]
- 返回指定分数范围内的成员:
zrangebyscore key min max [withscores] [limit offset count]
- 返回指定分数范围成员个数 :
zcount key min max
- 删除指定排名内的升序元素:
zremrangebyrank key start end
- 删除指定分数范围的成员:
zremrangebyscore key min max
- 集合间操作
- 添加成员:
- 有序集合操作时间复杂度
- 内部编码
- 使用场景
- 有序集合操作
- 键管理
- 遍历键
- 数据库管理
Redis数据结构详解:https://www.cnblogs.com/xiaolincoding/p/15628854.html
全局命令
查看当前数据库所有的键: keys *
127.0.0.1:6379> set "nihao" shijie # 插入两个键值对
127.0.0.1:6379> set hello world
127.0.0.1:6379> keys * # 查看所有的键
1) "hello"
2) "nihao"
获取当前数据库的键总数:dbsize
127.0.0.1:6379> dbsize # 默认数据库的index是0
(integer) 2
127.0.0.1:6379> select 1 # 选择1号数据库, 当前数据库中的键总数为0
OK
127.0.0.1:6379[1]> dbsize
(integer) 0
dbsize命令在计算键总数时不会遍历所有键,而是直接获取Redis内置的键总数变量,所以dbsize命令的时间复杂度是O(1)。
而keys命令会遍历所有键,所以它的时间复杂度是O(n),当Redis保存了大量键时,keys *可能会阻塞,所以线上环境禁止使用。
检查键是否存在:exists key
127.0.0.1:6379> exists nihao # 存在键返回1, 不存在则返回0
(integer) 1
127.0.0.1:6379> exists nihaoya
(integer) 0
删除键:del key [key2 ...]
del是一个通用命令,无论值是什么数据结构类型,del命令都可以将其删除。[key2,…]表示del后面可以跟多个参数,以空格间隔。
127.0.0.1:6379> set java "v1.0.1"
OK
127.0.0.1:6379> set python "v3.8.1"
OK
127.0.0.1:6379> lpush listchar a b c d e
(integer) 4
127.0.0.1:6379> del java python listchar # del删除string 类型和list类型
(integer) 3
127.0.0.1:6379> keys *
1) "hello"
2) "nihao"
设置键多久过期:expire key _seconds_
Redis支持对键添加过期时间(单位为秒),当超过过期时间后,会自动删除键 。**ttl**
命令会返回键的剩余过期时间,它有3种返回值 :
- 大于等于0的整数:键剩余的过期时间;
**-1**
:键没设置过期时间;**-2**
:键不存在。127.0.0.1:6379> get hello
"world"
127.0.0.1:6379> expire hello 10 # 10秒后hello过期, 再次取hello的值返回为空
(integer) 1
127.0.0.1:6379> get hello
(nil)
判断键的数据类型:
type _key_
key存在则返回key对应的类型,如果key不存在则返回
none
。127.0.0.1:6379> keys *
1) "listc"
2) "nihao"
127.0.0.1:6379> type listc
list
127.0.0.1:6379> type nihao
string
127.0.0.1:6379> object encoding nihao
"embstr"
type命令实际返回的就是当前键的数据结构类型,它们分别是: string、hash、list、set、zset,但这些只是Redis对外的数据结构。实际上每种数据结构都有自己底层的内部编码实现,而且是多种实现, 这样Redis会在合适的场景选择合适的内部编码 。可以通过
**object encoding**
命令查询内部编码 。
这样设计的好处:可以改进内部编码,而对外的数据 结构和命令没有影响,这样一旦开发出更优秀的内部编码,无需改动外部数据结构和命令;
- 多种内部编码实现可以在不同场景下发挥各自的优势,例如ziplist比较节省内存,但是在列表元素比较多的情况下,性能会有所下降,这时候Redis会根据配置选项将列表类型的内部实现转换为 linkedlist。
单线程架构💖💖
因为Redis是单线程来处理命令的,所以一条命令从客户端达到服务端不会立刻被执行,所有命令都会进入一个队列中,然后逐个被执行。
Redis单线程还能这么快的原因主要有以下三点:
- 纯内存访问,Redis将所有数据放在内存中,内存的响应时长大约为100纳秒,这是Redis达到每秒万级别访问的重要基础;
- 非阻塞I/O,Redis使用epoll作为I/O多路复用技术的实现,再加上 Redis自身的事件处理模型将epoll中的连接、读写、关闭都转换为事件,不在网络I/O上浪费过多的时间;
单线程避免了线程切换和竞态产生的消耗。
单线程存在的问题:对于每个命令的执行时间是有要求的。如果某个命令执行过长,会造成其他命令的阻塞,对于Redis这种高性能的服务来说是致命的,所以Redis是面向快速执行场景的数据库。
字符串类型
字符串类型的值实际可以是简单的字符串、复杂的字符串(例如JSON、XML)、数字(整数、浮点数),甚至是二进制(图片、音频、视频),但是值最大不能超过512MB 。
字符串操作
设置值:set key value [ex seconds] [px milliseconds] [nx|xx]
- key : 键名
- value : 值
- ex seconds: 为键设置秒级过期时间。
- px milliseconds: 为键设置毫秒级过期时间。
- nx:键必须不存在,才可以设置成功,用于添加。
- xx:与nx相反,键必须存在,才可以设置成功,用于更新。
获取值:
get key
成功返回获取到的值,失败返回nil127.0.0.1:6379> set hello world
OK
127.0.0.1:6379> set hello jedis xx
OK
127.0.0.1:6379> get hello
"jedis"
setex和setnx
它们的作用和ex和nx选项是一样的。 由于Redis的单线程命令处理机制,如果有多个客户端同时执行setnx _key_ _value_
, 根据setnx的特性只有一个客户端能设置成功,setnx可以作为分布式锁的一种实现方案,Redis官方给出了使用setnx实现分布式锁的方法:http://redis.io/topics/distlock。
批量设置/获取值:mset key value [key value ...] mget key [key ...]
mget获取值时,如果有些键不存在,那么它的值为nil(空),mget获取的结果是按照传入键的顺序返回 。
127.0.0.1:6379> mset a1 A1 a2 A2 a3 A3
OK
127.0.0.1:6379> mget a1 a2 a3
1) "A1"
2) "A2"
3) "A3"
批量操作命令可以有效提高开发效率,假如没有mget这样的命令,要执行n次get命令需要按照下图方式来执行 ,具体耗时如下:n次get时间 = n次网络时间 + n次命令时间
使用mget命令后,要执行n次get命令操作只需要按照下图方式来完成,具体耗时如下 : n次get时间 = 1次网络时间 + n次命令时间
每次批量操作所发送的命令数不是无节制的,如果数量过多可能造成Redis阻塞或者网络拥塞。
计数
incr _key_
:将key自增1。返回值可能如下:- 值不是整数,返回错误。
- 值是整数,返回自增后的结果。
- 键不存在,按照值为0自增,返回结果为1。
incrby _key _num
: 将key增加numdecr _key_
:将key自减1;decrby _key_ num
:将key减少num127.0.0.1:6379> set count 10 # 设置count的初值为10
OK
127.0.0.1:6379> incr count # count自增1, 此时count为11
(integer) 11
127.0.0.1:6379> get count
"11"
127.0.0.1:6379> incrby count 10 # 将count增加10,此时count为21
(integer) 21
127.0.0.1:6379> get count
"21"
127.0.0.1:6379> decr count # count自减1,此时count为20
(integer) 20
127.0.0.1:6379> get count
"20"
127.0.0.1:6379> decrby count 10 # 将count减少10,此时count为10
(integer) 10
127.0.0.1:6379> get count
"10"
127.0.0.1:6379> incrbyfloat count 3.5 # 将count增加浮点数3.5,此时count为13.5
"13.5"
127.0.0.1:6379> get count
"13.5"
追加值:
append _key_ val
向key代表的字符串中追加值val求字符串长度:
strlen _key_
设置并返回值:
getset _key_ value
getset会设置key的为value,并返回原来的值设置指定位置的字符:
setrange _key_ offset value
获取部分字符串:
start和end分别是开始和结束的偏移量,偏移量从0开始计算 。如果end为-1则获取全部字符串。getrange _key_ start end
127.0.0.1:6379> append count 123
(integer) 7
127.0.0.1:6379> get count
"13.5123"
127.0.0.1:6379> strlen count
(integer) 7
127.0.0.1:6379> getset count 14253
"13.5123"
127.0.0.1:6379> setrange count 1 ooo
(integer) 5
127.0.0.1:6379> get count
"1ooo3"
127.0.0.1:6379> getrange count 0 1
"1o"
127.0.0.1:6379> getrange count 0 -1
"1ooo3"
字符串操作时间复杂度
字符串编码
字符串类型的内部编码有3种:
**int**
:8个字节的长整型。**embstr**
:小于等于39个字节的字符串。**raw**
:大于39个字节的字符串。Redis会根据当前值的类型和长度决定使用哪种内部编码实现。
```bash 127.0.0.1:6379> set key 8563 OK 127.0.0.1:6379> object encoding key “int” 127.0.0.1:6379> set key “hello world” OK 127.0.0.1:6379> object encoding key “embstr” 127.0.0.1:6379> object encoding key “embstr” 127.0.0.1:6379> set key “hello world sadasdasd sdwdaskhfksafj;sajdla asdsafasdfkasdfsalkfjsakjfsalkjfalkdjflsakjfdlksajfdlksajdflkjsalkfjlskfd” OK 127.0.0.1:6379> object encoding key “raw”
<a name="VmXKP"></a>
### 字符串的使用场景
<a name="Lmv01"></a>
#### 缓存
典型的缓存使用场景如下图,其中Redis作为缓存层,MySQL作 为存储层,绝大部分请求的数据都是从Redis中获取。由于Redis具有支撑高并发的特性,所以缓存通常能起到加速读写和降低后端压力的作用。<br /><br />与关系型数据库不同的是,Redis没有命令空间,而且也没有对键名有强制要求(除了不能使用一些特殊字符)。但设计合理的键名,有利于防止键冲突和项目的可维护性,比较推荐的方式是使用`**业务名:对象 名:id:[属性]**`作为键名 。在能描述清楚键含义的前提下,尽可能地减少键长度,从而减少由于键过长导致的内存浪费。
<a name="r7P4I"></a>
#### 计数
许多应用都会使用Redis作为计数的基础工具,它可以实现快速计数、查询缓存的功能,同时数据可以异步落地到其他数据源。 <br />实际上一个真实的计数系统要考虑的问题会很多:防作弊、按照不同维 度计数,数据持久化到底层数据源等。
<a name="k6tyd"></a>
#### 共享Session
一个分布式Web服务将用户的Session信息(例如用户登录信息)保存在各自服务器中,这样会造成一个问题,出于负载均衡的考虑,分布式服务会将用户的访问均衡到不同服务器上,用户刷新一次访问可 能会发现需要重新登录,这个问题是用户无法容忍的。<br />为了解决这个问题,可以使用Redis将用户的Session进行集中管理,如 图2-12所示,在这种模式下只要保证Redis是高可用和扩展性的,每次用户更新或者查询登录信息都直接从Redis中集中获取。 <br />
<a name="Z4zWl"></a>
#### 限速
很多应用出于安全的考虑,会在每次进行登录时,让用户输入手机验证码,从而确定是否是用户本人。但是为了短信接口不被频繁访问,会限制用 户每分钟获取验证码的频率,例如一分钟不能超过5次。一些网站限制一个IP地址不 能在一秒钟之内访问超过n次也可以采用类似的思路。
<a name="uwcz3"></a>
## 哈希
<a name="psvbV"></a>
### 哈希操作
<a name="lwH8C"></a>
#### 设置值:`hset _key_ field value [field2 value2 ...]`
如果设置成功会返回1,否则返回0。此外Redis提供了hsetnx命令,它们的关系就像set和setnx命令一样,只不过作用域由键变为field。
<a name="CHlnm"></a>
#### 获取值:`hget _key_ field`
如果field不存在则返回`nil`。
<a name="KUlSi"></a>
#### 删除field:`hdel key field [ field2 ...]`
hdel会删除一个或多个field,返回结果为成功删除field的个数 。
<a name="toj2x"></a>
#### 计算field个数:`hlen _key_`
<a name="gRLu5"></a>
#### 批量设置/获取field-value
`hmset key field value [field value ...] `<br />`hmget key field [field ...]`<br /> hmset需要的参数是key 和多对field-value,hmget需要的参数是key和多个field。
<a name="LjrLu"></a>
#### 判断field是否存在: `hexists _key_ field`
存在field则返回1,不存在则返回0。
<a name="ogFaA"></a>
#### 获取所有的field:`hkeys _key_`
<a name="YMn51"></a>
#### 获取所有的value:`hvals _key_`
<a name="wGoz5"></a>
#### 获取所有的field-value:`hgetall`
在使用hgetall时,如果哈希元素个数比较多,会存在阻塞Redis的可能。 如果只需要获取部分field,可以使用hmget,如果一定要获取全部 field-value,可以使用`**hscan**`命令,该命令会渐进式遍历哈希类型 。
<a name="osEmq"></a>
#### hincrby和hincrbyfloat:增加field域对应的整型值
`hincrby key field `<br />`hincrbyfloat key field`<br />field域对应的值必须是整数/浮点数,否则报错。
<a name="YKPPq"></a>
#### 计算value的字符串长度:`hstrlen key field`
```bash
127.0.0.1:6379> hset user:1 name Tom age 20 sex male
(integer) 3
127.0.0.1:6379> hget user:1 name
"Tom"
127.0.0.1:6379> hdel user:1 age sex
(integer) 2
127.0.0.1:6379> hset user:1 city beijing school thinghua
(integer) 2
127.0.0.1:6379> hlen user:1
(integer) 3
127.0.0.1:6379> hmset user:2 name Mike city tianjin age 12
OK
127.0.0.1:6379> hmget user:2 name city age
1) "Mike"
2) "tianjin"
3) "12"
127.0.0.1:6379> hexists user:2 name
(integer) 1
127.0.0.1:6379> hexists user:2 dsad
(integer) 0
127.0.0.1:6379> hkeys user:2
1) "name"
2) "city"
3) "age"
127.0.0.1:6379> hvals user:2
1) "Mike"
2) "tianjin"
3) "12"
127.0.0.1:6379> hgetall user:2
1) "name"
2) "Mike"
3) "city"
4) "tianjin"
5) "age"
6) "12"
127.0.0.1:6379> hincrby user:2 age 1
(integer) 13
127.0.0.1:6379> hget user:2 age
"13"
127.0.0.1:6379> hstrlen user:2 name
(integer) 4
哈希操作时间复杂度
哈希的内部编码
**ziplist(压缩列表)**
:当哈希类型元素个数小于**hash-max-ziplist-entries **
配置(默认512个)、同时所有值都小于**hash-max-ziplist-value**
配置(默认64 字节)时,Redis会使用ziplist作为哈希的内部实现,ziplist使用更加紧凑的结构实现多个元素的连续存储,所以在节省内存方面比hashtable更加优秀。**hashtable(哈希表)**
:当哈希类型无法满足ziplist的条件时(field个数大于512,或value大于64字节),Redis会使 用hashtable作为哈希的内部实现,因为此时ziplist的读写效率会下降,而 hashtable的读写时间复杂度为O(1)。 ```bash 127.0.0.1:6379> hmset hsk1 f1 dasda f2 sadsad OK 127.0.0.1:6379> object encoding hsk1 “ziplist” 127.0.0.1:6379> hmset hsk2 f1 dasda f2 sadsadsad-dadasd-dsadsad-asdasdas-dasdagsadg-dsgadafgh-xzcvxz-asdasdgadg-sdfsfdasdfsvzvc-fsSFAASDFSAFAS-safasvasdf33—dasdasdafsafasfasvadsAFsgdsgdasgsa5asfashda6fahfu7 OK 127.0.0.1:6379> object encoding hsk2 “hashtable”
<a name="mT76m"></a>
### 使用场景:缓存用户信息
<br /><br /> 相比于使用字符串序列化缓存用户信息,哈希类型变得更加直观,并且 在更新操作上会更加便捷。是哈希类型和关系型数据库有两点不同之处 :
- 哈希类型是稀疏的,而关系型数据库是完全结构化的,例如哈希类型每个键可以有不同的field,而关系型数据库一旦添加新的列,所有行都要为其设置值(即使为NULL);
- 关系型数据库可以做复杂的关系查询,而Redis去模拟关系型复杂查询 开发困难,维护成本高。
** 三种缓存用户信息的实现方法和优缺点分析**
1. 原生字符串类型:每个属性一个键
```bash
set user:1:name tom
set user:1:age 23
set user:1:city beijing
优点:简单直观,每个属性都支持更新操作。
缺点:占用过多的键,内存占用量较大,同时用户信息内聚性比较差, 所以此方案一般不会在生产环境使用。
序列化字符串类型:将用户信息序列化后用一个键保存
set user:1 serialize(userInfo)
优点:简化编程,如果合理的使用序列化可以提高内存的使用效率。
缺点:序列化和反序列化有一定的开销,同时每次更新属性都需要把全部数据取出进行反序列化,更新后再序列化到Redis中。哈希类型:每个用户属性使用一对field-value,但是只用一个键保存。
hmset user:1 name tomage 23 city beijing
优点:简单直观,如果使用合理可以减少内存空间的使用。
缺点:要控制哈希在ziplist和hashtable两种编码的转换,hashtable会消耗更多内存。
列表
列表中的每个字符串 称为元素(element),一个列表最多可以存储232 -1个元素。 在Redis中,可以对列表两端插入(push)和弹出(pop),还可以获取指定范围的元素列 表、获取指定索引下标的元素等。 列表是一种比 较灵活的数据结构,它可以充当栈和队列的角色,在实际开发上有很多应用场景。
列表的特点:①列表中的元素可以通过下标获取某个元素或则某个范围类的元素列表;②列表中的元素是可以重复的。
列表操作
添加元素
- 从右边插入元素:
rpush key value [value ...]
- 从左边插入元素:
lpush key value [value ...]
向某个元素前或后插入元素:
linsert key before|after pivot value
linsert命令会从列表中找到第一个等于pivot的元素,在其前(before)或者后 (after)插入一个新的元素value。查找元素
获取指定范围内的元素列表:
lrange key start end
lrange会获取**[start, end]**
之间的所有元素。从左到右获取,下标的范围是**0~N-1**
;从右到左获取,下标的范围是**-N~-1**
。
lrem会从列表中找到等于value 的元素进行删除,根据count的不同分为三种情况:
**count>0**
, 从左到右,删除最多count个元素 ;**count<0**
,从右到左,删除最多count绝对值个元素;**count=0**
,删除所有。
- 修改指定下标的元素:
lset key index newVal
阻塞操作
blpop和brpop是lpoop和rpop的阻塞版本。 参数:key[key…]:多个列表的键。timeout:阻塞时间(单位:秒)。
blpop key [key ...] timeout
brpop key [key ...] timeout
在使用阻塞操作时,有以下两种情况:
- 列表为空:如果timeout > 0, 那么客户端会等待timeout秒后返回 ,如果timeout=0,那么客户端会一直阻塞下去,直到有其他客户端向列表中添加了数据,然后立即返回。
- 列表不为空:客户端立即返回。
在使用brpop时,有两点需要注意:①如果参数是多个键,那么brpop从左至右遍历键,一但有一个键能弹出元素,客户端立即返回;② 如果多个客户端对同一个键执行brpop,那么最先执行brpop命令的客户端可以获取到弹出的值。 blpop情况类似。
127.0.0.1:6379> rpush lst1 1 2 3 4 5 6
(integer) 6
127.0.0.1:6379> lpush lst1 1 2 3 4 5 6
(integer) 12
127.0.0.1:6379> lrange lst1 0 -1
1) "6"
2) "5"
3) "4"
4) "3"
5) "2"
6) "1"
7) "1"
8) "2"
9) "3"
10) "4"
11) "5"
12) "6"
127.0.0.1:6379> linsert lst1 before 1 hello # hello插入在第一个"1"之前
(integer) 13
127.0.0.1:6379> linsert lst1 after 1 hello # hello插入在第一个"1"之后
(integer) 14
127.0.0.1:6379> lrange lst1 0 -1
1) "6"
2) "5"
3) "4"
4) "3"
5) "2"
6) "hello" # hello插入在第一个"1"之前
7) "1"
8) "hello" # hello插入在第一个"1"之后
9) "1"
10) "2"
11) "3"
12) "4"
13) "5"
14) "6"
127.0.0.1:6379> lindex lst1 0
"6"
127.0.0.1:6379> lindex lst1 -1
"6"
127.0.0.1:6379> llen lst1
(integer) 14
127.0.0.1:6379> lpop lst1
"6"
127.0.0.1:6379> rpop lst1
"6"
127.0.0.1:6379> lrange lst1 0 -1
1) "5"
2) "4"
3) "3"
4) "2"
5) "hello"
6) "1"
7) "hello"
8) "1"
9) "2"
10) "3"
11) "4"
12) "5"
127.0.0.1:6379> lrem lst1 2 1
(integer) 2
127.0.0.1:6379> lrange lst1 0 -1
1) "5"
2) "4"
3) "3"
4) "2"
5) "hello"
6) "hello"
7) "2"
8) "3"
9) "4"
10) "5"
127.0.0.1:6379> ltrim lst1 0 3
OK
127.0.0.1:6379> lrange lst1 0 -1
1) "5"
2) "4"
3) "3"
4) "2"
127.0.0.1:6379> lset lst1 0 'hello'
OK
127.0.0.1:6379> lindex lst1 0
"hello"
列表操作时间复杂度
列表内部编码
列表类型的内部编码右两种:
**ziplist(压缩列表)**
:当列表的元素个数小于**list-max-ziplist-entries**
配置 (默认512个),同时列表中每个元素的值都小于**list-max-ziplist-value**
配置时 (默认64字节),Redis会选用ziplist来作为列表的内部实现来减少内存的使用。 Redis3.2版本提供了**quicklist**
内部编码。**linkedlist(链表)**
:当列表类型无法满足ziplist的条件时,Redis会使用 linkedlist作为列表的内部实现。
使用场景
消息队列
Redis的lpush+brpop命令组合即可实现阻塞队列,生产者客户端使用lrpush从列表左侧插入元素,多个消费者客户端使用brpop命令 阻塞式的“抢”列表尾部的元素,多个客户端保证了消费的负载均衡和高可用性。
在不同场景中应用Redis列表时可以根据下面的公式进行选择:
**lpush+lpop=Stack(栈)**
**lpush+rpop=Queue(队列)**
**lpsh+ltrim=Capped Collection(有限集合)**
**lpush+brpop=Message Queue(消息队列)**
集合
集合中不允许有重复元素,并且集合中的元素是无序的,不能通过 索引下标获取元素。一个集合最多可以存储**2****32**** -1**
个元 素。Redis除了支持集合内的增删改查,同时还支持多个集合取交集、并 集、差集。
集合操作
添加元素:sadd key element [element ...]
返回结果为添加成功的元素个数
删除元素:srem key element [eleemnt ...]
返回结果为添加删除元素个数
计算元素个数:scard key
判断元素是否在集合中:sismember key element
给定元素在集合中则返回1,否则返回0
随机从集合中返回指定个数元素:srandmember key [count]
count默认为1
从集合中随机弹出元素:spop key [count]
count默认值为1
获取所有元素:smembers key
smembers和lrange、hgetall都属于比较重的命令,如果元素过多会阻塞Redis。可以使用sscan代替smembers。
集合间操作
- 求多个集合的交集:
sinter key1 key2 [key3 ...]
- 求多个集合的并集:
sunion key1 key2 [key3 ...]
- 求多个集合的差集:
sdiff key1 key2 [key3 ...]
集合间的运算在元素较多的情况下会比较耗时,所以Redis提供了以下三个命令(原命令+store)将集合间交集、并集、差集的结果保存在 destination 集合中。 sinterstore destination key1 key2 [key3 ...]
sunionstore destination key1 key2 [key3 ...]
sdiffstore destination key1 key2 [key3 ...]
127.0.0.1:6379> sadd st1 a b c
(integer) 3
127.0.0.1:6379> srem st1 a
(integer) 1
127.0.0.1:6379> smembers st1
1) "c"
2) "b"
127.0.0.1:6379> scard st1
(integer) 2
127.0.0.1:6379> spop st1
"b"
127.0.0.1:6379> smembers st1
1) "c"
127.0.0.1:6379> sismember st1 d
(integer) 0
127.0.0.1:6379> sadd st1 cd ef gh
(integer) 3
127.0.0.1:6379> srandmember st1
"cd"
127.0.0.1:6379> spop st1 2
1) "ef"
2) "cd"
# 集合间操作
127.0.0.1:6379> sadd st1 a b c d e
(integer) 5
127.0.0.1:6379> sadd st2 d e f g h
(integer) 5
127.0.0.1:6379> sadd st3 d e f a n
(integer) 5
127.0.0.1:6379> sinter st1 st2 st3 # st1 ∩ st2 ∩ st3
1) "d"
2) "e"
127.0.0.1:6379> sunion st1 st2 st3 # st1 ∪ st2 ∪ st3
1) "h"
2) "g"
3) "c"
4) "f"
5) "e"
6) "n"
7) "a"
8) "b"
9) "d"
127.0.0.1:6379> sdiff st1 st2 st3 # st1 - ( (st1 ∩ st2) ∩ st3 )
1) "b"
2) "c"
127.0.0.1:6379> sunionstore resunion st1 st2 st3
(integer) 9
127.0.0.1:6379> smembers resunion
1) "h"
2) "g"
3) "c"
4) "f"
5) "e"
6) "n"
7) "a"
8) "d"
9) "b"
集合操作时间复杂度
集合内部编码
集合类型的内部编码有两种:
**intset(整数集合)**
:当集合中的元素都是整数且元素个数小于set-max-intset-entries
配置(默认512个)时,Redis会选用intset来作为集合的内部实现,从而减少内存的使用。**hashtable(哈希表)**
:当集合类型无法满足intset的条件时,Redis会使用hashtable作为集合的内部实现。
使用场景
集合类型的应用场景通常为以下几种 :
- sadd=Tagging(标签)
- spop/srandmember=Random item(生成随机数,比如抽奖)
- sadd+sinter=Social Graph(社交网络)
有序集合
有序集合保留了集合不能有重复成员的特性, 不同的是,有序集合中的元素可以排序。与列表使用索引下标作为排序依据不同的是,它给每个元素设置一个分数(score)作为排序的依据 。有序集合中的元素不能重复,但是score可以重复 。
有序集合操作
添加成员:zadd key [nx|xx] [ch] [incr] score member [score2 member2 ...]
- nx:member必须不存在,才可以设置成功,用于添加;
- xx:member必须存在,才可以设置成功,用于更新;
- ch:返回此次操作后,有序集合元素和分数发生变化的个数;
- incr:对score做增加,相当于后面介绍的zincrby。
计算成员个数:zcard key
计算某个成员的分数:zscore key member
如成员不存在则返回nil 。
计算成员排名:zrank key member
、zrevrank key member
zrank是从分数从低到高返回排名,zrevrank反之。
删除成员:zrem key member [member2 ...]
返回成功删除的个数
增加成员分数:zincrby key _incrnum_ member
返回指定排名范围内的成员:zrange|zrevrange key start end [withscores]
zrange是从低到高返回,zrevrange反之 。 如果加上withscores选项,同时会返回成员的分数。
返回指定分数范围内的成员:
zrangebyscore key min max [withscores] [limit offset count]
**zrevrangebyscore key max min [withscores] [limit offset count]**
zrangebyscore按照分数从低到高返回,zrevrangebyscore反之。 withscores选项会同时返回每个 成员的分数。[limit offset count]选项可以限制输出的起始位置和个数 。
同时min和max还支持开区间(小括号)和闭区间(中括号),-inf
和+inf
分别代表无限小和无限大。
返回指定分数范围成员个数 :zcount key min max
删除指定排名内的升序元素:zremrangebyrank key start end
删除指定分数范围的成员:zremrangebyscore key min max
集合间操作
- 交集
**zinterstore** destination numkeys key key2 [key3 ...] [weights weight1 weight2 [weight3 ...]] [aggregate sum|min|max]
- destination:交集计算结果保存到这个键。
- numkeys:需要做交集计算键的个数,最少为2。
- key1 key2 [key3…]:需要做交集计算的键。
- weights weight1 weight2 [weight3…]:每个键的权重,在做交集计算时,每个键中的每个member会将自己分数乘以这个权重,每个键的默认权重是1。
- aggregate sum|min|max:计算成员交集后,分值可以按照sum(和)、 min(最小值)、max(最大值)做汇总,默认值是sum。
- 并集
**zunionstore** destination numkeys key key2 [key3 ...] [weights weight1 weight2 [weight3 ...]] [aggregate sum|min|max]
# 单个集合操作
127.0.0.1:6379> zadd zst 10 usr1 10 usr2 20 usr3 20 usr4 50 usr5 60 usr4
(integer) 5
127.0.0.1:6379> zscore zsst 10
(nil)
127.0.0.1:6379> zscore zsst usr1
(nil)
127.0.0.1:6379> zscore zst usr1
"10"
127.0.0.1:6379> zrank zst usr1 # rank从0开始
(integer) 0
127.0.0.1:6379> zrank zst usr4
(integer) 4
127.0.0.1:6379> zscore zst usr4
"60"
127.0.0.1:6379> zrevrank zst usr4
(integer) 0
127.0.0.1:6379> zrem zst usr1
(integer) 1
127.0.0.1:6379> zrange zst 0 -1
1) "usr2"
2) "usr3"
3) "usr5"
4) "usr4"
127.0.0.1:6379> zrangebyscore zst 10 30
1) "usr2"
2) "usr3"
127.0.0.1:6379> zrangebyscore zst 10 30 withscores
1) "usr2"
2) "10"
3) "usr3"
4) "20"
127.0.0.1:6379> zrevrangebyscore zst 30 10
1) "usr3"
2) "usr2"
127.0.0.1:6379> zrevrangebyscore zst 30 10 withscores
1) "usr3"
2) "20"
3) "usr2"
4) "10"
127.0.0.1:6379> zcount zst 10 30
(integer) 2
127.0.0.1:6379> zremrangebyrank zst 1 2
(integer) 2
127.0.0.1:6379> zrange zst 0 -1 withscores
1) "usr2"
2) "10"
3) "usr4"
4) "60"
127.0.0.1:6379> zremrangebyscore zst 50 60
(integer) 1
127.0.0.1:6379> zrange zst 0 -1 withscores
1) "usr2"
2) "10"
# 集合间操作
127.0.0.1:6379> zadd user:ranking:1 1 kris 91 mike 200 frank 220 tim 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
127.0.0.1:6379> zinterstore user:ranking:1_inter_2 2 uesr:ranking:1 uesr: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"
127.0.0.1:6379> zinterstore uesr:ranking:1_intermax_2 2
uesr:ranking:1 uesr:ranking:2 weights 1 0.5 aggregate max
(integer) 3
127.0.0.1:6379> zrange uesr:ranking:1_intermax_2 0 -1 withscores
1) "mike"
2) "91"
3) "martin"
4) "312.5"
5) "tom"
6) "444"
127.0.0.1:6379> zunionstore uesr:ranking:1_union_2 2 uesr:ranking:1 uesr:ranking:2
(integer) 7
127.0.0.1:6379> zrange uesr:ranking:1_union_2 0 -1 withscores
1) "kris"
2) "1"
3) "james"
4) "8"
5) "mike"
6) "168"
7) "frank"
8) "200"
9) "tim"
10) "220"
11) "martin"
12) "875"
13) "tom"
14) "1139"
有序集合操作时间复杂度
内部编码
有序集合类型的内部编码有两种:
**ziplist(压缩列表)**
:当有序集合的元素个数小于zset-max-ziplist-entries
配置(默认128个),同时每个元素的值都小于zset-max-ziplist-value
配 置(默认64字节)时,Redis会用ziplist来作为有序集合的内部实现,ziplist 可以有效减少内存的使用。**skiplist(跳跃表)**
:当ziplist条件不满足时,有序集合会使用skiplist作为内部实现,因为此时ziplist的读写效率会下降。127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> zadd zst1 50 e1 60 e2 30 e3
(integer) 3
127.0.0.1:6379> object encoding zst1
"ziplist"
127.0.0.1:6379> zadd zst1 85 12345789012345678901234567890123456789012345678901234567890123456714725825821478214785214785211478521478521445213135rasfgsdfsdfsdfdsfssdf25dsfsdf
(integer) 1
127.0.0.1:6379> object encoding zst1
"skiplist"
使用场景
有序集合比较典型的使用场景是排行榜系统。例如视频网站需要对用 户上传的视频做排行榜,榜单的维度可能是多个方面的:按照时间、按照播放数量、按照获得的赞数。
键管理
除了type
、del
、object
、exists
、expire
等,还有如下命令:
键重命名:rename|renamenx key newkey
。
如果rename之前newkey已经存在,则newkey的值会被覆盖。为了防止被强行rename,Redis提供了renamenx命令,确保只有newkey不存在时才被覆盖。 由于重命名键期间会执行del命令删除旧的键,如果键对应的值比较大,会存在阻塞Redis的可能性,这点不要忽视。
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> set lan "redis"
OK
127.0.0.1:6379> rename lan language
OK
127.0.0.1:6379> get language
"redis"
随机返回一个键:randomkey
127.0.0.1:6379> keys *
1) "lang"
2) "hh"
3) "hello"
4) "nihao"
5) "language"
127.0.0.1:6379> randomkey
"nihao"
键过期
除了expire、ttl命令以外,Redis还提供了 expireat、pexpire、pexpireat、pttl、persist等一系列命令 。expire key seconds
:键在seconds秒后过期。expireat key timestamp
:键在秒级时间戳timestamp后过期。ttl与pttl
:二者都可以查询剩余过期时间,但是pttl精度更高可以达到 毫秒级别,有3种返回值: ①大于等于0的整数:键剩余的过期时间(ttl是秒,pttl是毫秒)。 ②**-1**
:键没有设置过期时间。 ③**-2**
:键不存在。pexpire key milliseconds
:键在milliseconds毫秒后过期。pexpireat key milliseconds-timestamp
:键在毫秒级时间戳timestamp后过期。
无论是使用过期时间还是时间戳,秒级还是毫秒级,在Redis内部最 终使用的都是pexpireat
使用过期相关命令时需要注意:💖
- 如果expire key命令中,key不存在,则返回0;
- 如果过期时间为负值,键会被立即删除;
- persit命令可以将键的过期时间清除;
- 对于字符串类型键,执行set命令会去掉过期时间。
- setex命令为set+expire的组合,不但是原子操作,同时减少了一次网络通讯时间。
- Redis不支持对二级数据结构(如哈希、列表)内部元素的设置过期时间。
键迁移💖
有时需要把部分数据由一个Redis迁移到另一个Redis(如从生产环境迁移到测试环境)则需要键迁移功能。由以下三种方法。
**move**
:**move key dbindex**
Redis内部可以有多个数据库,彼此在数据上是相互隔离的,move key dbindex就是把指定的键从源数据库移动到目标数据库中 。多数据库功能不建议在生产环境使用,所以move命令不常用。
127.0.0.1:6379> keys *
1) "hello"
127.0.0.1:6379> move hello 1
(integer) 1
127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]> keys *
1) "hello"
127.0.0.1:6379[1]> get hello
"hhh"
**dump+resstore**
dump+restore可以实现在不同的Redis实例之间进行数据迁移的功能,整个迁移的过
程分为两步:
(1) 在源Redis上,dump命令会将键值序列化,格式采用的是RDB格式。
(2) 在目标Redis上,restore命令将上面序列化的值进行复原,其中ttl参 数代表过期时
间,如果ttl=0代表没有过期时间。
有关dump+restore有两点需要注意:第一,整个迁移过程并非原子性的,而是通过客户端分步完成的。第二,迁移过程是开启了两个客户端连接,所以dump的结果不是在源Redis和目标Redis之间进行传输 。
127.0.0.1:6379[1]> dump hello # 数据库1中dump键hello
"\x00\x03hhh\t\x00?\x1f&]\x8b\x98=Z"
127.0.0.1:6379> flushdb # 数据库0中restore键hello
OK
127.0.0.1:6379> restore hello 0 "\x00\x03hhh\t\x00?\x1f&]\x8b\x98=Z"
OK
127.0.0.1:6379> get hello
"hhh"
**migrate**
💖
migrate命令也是用于在Redis实例间进行数据迁移的,migrate命令实际上就是将dump、restore、del三个命令进行组合,从而简化了操作流程。 migrate命令具有原子性,且支持迁移多个键。migrate host port key|"" des-db timeout [copy] [replace] [auth password] [auth2 username password] [keys key [key2 ...]]
host
:目标Redis的IP地址。 port
:目标Redis的端口。key|""
:在Redis 3.0.6之前migrate只支持迁移一个键,此处为要迁移的键,Redis 3.0.6之后支持迁移多个键,如果需要迁移多个键,此参数填””。des-db
:目标Redis的数据库索引。timeout
:迁移超时时间,单位为毫秒。cpoy
:如果添加此选项,则迁移后不删除源数据库中的键。replace
:如果添加此选项,则迁移后不管目标数据库中是否存在相同键都会覆盖。auth password
:使用密码password访问目标数据库。auth2 username password
:使用用户名username和密码password访问目标数据库keys key [key2 ...]
:要迁移的键们
migrate 实现过程和dump+restore基本类似,但是有3点不同:
第一,整个过程是原子执行的,不需要在多个Redis实例上开启 客户端的,只需要在源Redis上执行migrate命令即可。
第二,migrate命令的 数据传输直接在源Redis和目标Redis上完成的。
第三,目标Redis完成restore 后会发送OK给源Redis,源Redis接收后会根据migrate对应的选项来决定是否 在源Redis上删除对应的键。
遍历键
keys pattern
pattern实际上是一个模式匹配串,使用glob风格的通配符。
*****
代表任意字符**[]**
代表匹配部分字符,如[1,3]代表匹配1,3;[1-10]代表匹配1到10的任意数字**\x**
用来做转义,例如匹配*
、?
需要进行转义127.0.0.1:6379> mset hello world redis best jedis bes hill high
OK
127.0.0.1:6379>
127.0.0.1:6379> keys *
1) "hello"
2) "hill"
3) "jedis"
4) "redis"
127.0.0.1:6379> keys [j,r]edis
1) "jedis"
2) "redis"
127.0.0.1:6379> keys h*
1) "hello"
2) "hill"
考虑到Redis的单线程架构就不那么美妙了,如果Redis包含了大量的键,执行keys命令很可能会造成Redis阻塞,所以一般建议不要在生产环境下使用keys命令。
scan
scan能有效的解决keys命令存在的问题。和keys命令执行时会遍历所有的键不同,scan采用渐进式遍历的方式来解决keys命令可能带来的阻塞问题,每次执行scan命令的时间复杂度是O(1), 但是要真正实现keys的功能,需要执行多次scan。
使用方式:scan cursor [match pattern] [count number]
cursor:必须参数,cursor实际上是一个游标,第一次遍历 0开始,每次scan遍历完都会返回当前游标的值,直到游标值为0,表示遍历结束。
match pattern
:可选参数,作用的是做模式的匹配,这点和keys的模式匹配像。count number
:可选参数,它的作用是表明每次要遍历的键个数,默认值是10,此参数可以适当增大。127.0.0.1:6379> keys *
1) "hello"
2) "hill"
3) "jedis"
4) "redis"
127.0.0.1:6379> scan 0
1) "0" # scan遍历到0, 表示遍历结束
2) 1) "hello"
2) "hill"
3) "jedis"
4) "redis"
scan 可以有效的解决keys命令可能产生的阻塞问题,但如果在scan的过程中如果有键的变化(增加、删除、修改), 那么遍历效果可能会碰到如下问题:新增的键可能没有遍历到,遍历出了重 复的键等情况,也就是说scan并不能保证完整的遍历出来所有的键 。
此外,Redis还提供了hscan、sscan、zscan渐进式地遍历hash、set、zset。数据库管理
切换数据库:
select _index_
与关系型数据库用字符来区分不同数据库名不同,Redis只用数字作为多个数据库的实现,Redis默认配置中是有16个数据库,默认使用0号数据库 。index取0~15之间的数字。
生产环境一般每个Redis实例只使用0号库,原因在于:Redis是单线程的。如果使用多个数据库,那么这些数据库仍然是使用 一个CPU,彼此之间还是会受到影响的。
- 多数据库的使用方式,会让调试和运维不同业务的数据库变的困难, 假如有一个慢查询存在,依然会影响其他数据库,这样会使得别的业务方定 位问题非常的困难 。
- 部分Redis的客户端根本就不支持这种方式。即使支持,在开发的时候 来回切换数字形式的数据库,很容易弄乱。
select index的实现原理:
服务器内部,客户端状态redisClient
结构的db
属性记录了客户端当前的目标数据库,redisClient.db
指向redisServer.db
数组中的一个元素,而被指向的元素就是客户端的目标数据库。通过修改redisClient.db
指针,让它指向服务器中不同的数据库,从而实现切换数据库的功能。
如果需要使用多个Redis数据库,可以在一台机器上部署多个Redis实例,彼此用端口来做区分。现在计算机通常是由多个CPU的,这样既保证了业务之间不会受到影响,又合理使用了CPU资源。
清空数据库
flushdb/flushall
命令用于清除数据库,两者的区别的是flushdb只清除当前数据库,flushall会清除所有数据库。如果当前数据库键值数量比较多,flushdb/flushall存在阻塞Redis的可能性。