目录
一.前言
Redis提供了5种数据结构,理解每种数据结构的特点对于Redis开发运维非常重要,同时掌握Redis的单线程命令处理机制,会使数据结构和命令的选择事半功倍。
二.预备
在正式介绍5种数据结构之前,了解一下Redis的一些全局命令、数据结构和内部编码、单线程命令处理机制是十分有必要的,它们能为后面内容的学习打下一个好的基础,主要体现在两个方面:第一、Redis的命令有上百个,如果纯靠死记硬背比较困难,但是如果理解Redis的一些机制,会发现这些命令有很强的通用性。第二、Redis不是万金油,有些数据结构和命令必须在特定场景下使用,一旦使用不当可能对Redis本身或者应用本身造成致命伤害。
2.1全局命令
Redis有5种数据结构,它们是键值对中的值,对于键来说有一些通用的命令。
命令 | 描述 | 备注 |
---|---|---|
keys * | 查看所有键 | keys命令会遍历所有键,所以它的时间复杂度是O(n),当Redis保存了大量键时,线上环境禁止使用。 |
dbsize | dbsize命令会返回当前数据库中键的总数 | dbsize命令在计算键总数时不会遍历所有键,而是直接获取Redis内置的 键总数变量,所以dbsize命令的时间复杂度是O(1)。 |
exists key | 如果键存在则返回1,不存在则返回0 | |
del key [key ...] | 删除键 | del是一个通用命令,无论值是什么数据结构类型,del命令都可以将其删除,返回结果为成功删除键的个数,假设删除一个不存在的键,就会返回0 |
expire key seconds | Redis支持对键添加过期时间,当超过过期时间后,会自动删除键, | |
ttl | 返回键的剩余过期时间 | 有3种返回值: ·-1:键没设置过期时间。 -2:键不存在 |
type key | 键的数据结构类型 | 键不存在,则返回none:string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(有序集合) |
2.2 数据结构和编码
实际上每种数据结构都有自己底层的内部编码实现,而且是多种实现,这样Redis会在合适的场景选择合适的内部编码,如图2-2所示。可以看到每种数据结构都有两种以上的内部编码实现,例如list数据结构包含了linkedlist和ziplist两种内部编码。同时有些内部编码,例如ziplist,可以作为多种外部数据结构的内部实现,可以通过object encoding命令查询内部编码。
Redis这样设计有两个好处:第一,可以改进内部编码,而对外的数据结构和命令没有影响,这样一旦开发出更优秀的内部编码,无需改动外部数据结构和命令,例如Redis3.2提供了quicklist,结合了ziplist和linkedlist两者的优势,为列表类型提供了一种更为优秀的内部编码实现,而对外部用户来说基本感知不到。第二,多种内部编码实现可以在不同场景下发挥各自的优势,例如ziplist比较节省内存,但是在列表元素比较多的情况下,性能会有所下降,这时候Redis会根据配置选项将列表类型的内部实现转换为linkedlist。
2.3单线程架构
Redis使用了单线程架构和I/O多路复用模型来实现高性能的内存数据库服务。因为Redis是单线程来处理命令的,所以一
条命令从客户端达到服务端不会立刻被执行,所有命令都会进入一个队列中,然后逐个被执行。
为什么Redis使用单线程模型会达到每秒万级别的处理能力呢?可以将其归结为三点:
第一,纯内存访问,Redis将所有数据放在内存中,内存的响应时长大约为100纳秒,这是Redis达到每秒万级别访问的重要基础。
第二,非阻塞I/O,Redis使用epoll作为I/O多路复用技术的实现,再加上Redis自身的事件处理模型将epoll中的连接、读写、关闭都转换为事件,不在网络I/O上浪费过多的时间,如图2-6所示。
第三,单线程避免了线程切换和竞态产生的消耗。
为单线程能带来几个好处:
第一,单线程可以简化数据结构和算法的实现。如果对高级编程语言熟悉的读者应该了解并发数据结构实现不但困难而且开发测试比较麻烦。
第二,单线程避免了线程切换和竞态产生的消耗,对于服务端开发来说,锁和线程切换通常是性能杀手。
但是单线程会有一个问题:
对于每个命令的执行时间是有要求的。如果某个命令执行过长,会造成其他命令的阻塞,对于Redis这种高性能的服务来说是致命的,所以Redis是面向快速执行场景的数据库。
三.字符串
3.1 命令
命令 | 描述 | 备注 | 时间复杂度 |
---|---|---|---|
set key value [ex seconds] [px milliseconds] [nx|xx] | 设置值 | set命令有几个选项: ·ex seconds:为键设置秒级过期时间。 ·px milliseconds:为键设置毫秒级过期时间。 ·nx:键必须不存在,才可以设置成功,用于添加 ·xx:与nx相反,键必须存在,才可以设置成功,用于更新
| O(1) |
setex | 和xx选项是一样的 | O(1) | |
setnx | 和nx选项是一样的 | 由于Redis的单线程命令处理机制,如果有多个客户端同时执行setnx key value,根据setnx的特性只有一个客户端能设置成功,setnx可以作为分布式锁的一种实现方案,Redis官方给出了使用setnx实现分布式锁的方法:http://redis.io/topics/distlock | O(1) |
get key | 获取值 | 如果要获取的键不存在,则返回nil(空) | O(1) |
mset key value [key value ...] | 批量设置值 | O(k) | |
mget key [key ...] | 批量获取值 | 如果有些键不存在,那么它的值为nil(空),结果是按照传入键的顺序返回 | O(k) |
incr key | 用于对值做自增操作 | ·值不是整数,返回错误。 除了incr命令,Redis提供了decr(自减)、incrby(自增指定数字)、decrby(自减指定数字)、incrbyfloat(自增浮点数) | O(1) |
命令 | 描述 | 备注 | 时间复杂度 |
---|---|---|---|
append key value | 追加值 | append可以向字符串尾部追加值 | O(1) |
strlen key | 字符串长度 | O(1) | |
getset key value | 设置并返回原值 | getset和set一样会设置值,但是不同的是,它同时会返回键原来的值 | O(1) |
setrange key offeset value | 设置指定位置的字符 | O(1) | |
getrange key start end | 获取部分字符串 | start和end分别是开始和结束的偏移量,偏移量从0开始计算 | O(n) n是字符串长度 |
3.2 内部编码
字符串类型的内部编码有3种:
·int:8个字节的长整型。
·embstr:小于等于39个字节的字符串。
·raw:大于39个字节的字符串。
Redis会根据当前值的类型和长度决定使用哪种内部编码实现。
3.3典型使用场景
1.缓存功能
2.计数
3.共享Session
4.限速
四.哈希
4.1命令
命令 | 描述 | 备注 | 时间复杂度 |
---|---|---|---|
hset key field value | 设置值 | 设置成功会返回1,反之会返回0。 此外Redis提供了hsetnx命令 | O(1) |
hget key field | 获取值 | 如果键或field不存在,会返回nil | O(1) |
hdel key field [field ...] | 删除field | hdel会删除一个或多个field,返回结果为成功删除field的个数 | O(k) |
hlen key | 计算field个数 | O(1) | |
hmget key field [field ...] hmset key field value [field value ...] | 批量设置或获取field-value | O(k) | |
hexists key field | 判断field是否存在 | 不包含时返回0,反之返回结果为1 | O(1) |
hkeys key | 获取所有field | O(n) | |
hvals key | 获取所有value | O(n) | |
hgetall key | 获取所有的field-value | 在使用hgetall时,如果哈希元素个数比较多,会存在阻塞Redis的可能。如果开发人员只需要获取部分field,可以使用hmget,如果一定要获取全部field-value,可以使用hscan命令,该命令会渐进式遍历哈希类型。 | O(n) |
hincrby hincrbyfloat | hincrby和hincrbyfloat,就像incrby和incrbyfloat命令一样,但是它们的作用域是filed | O(1) | |
hstrlen key field | 计算value的字符串长度(需要edis3.2以上) | O(1) |
4.2内部编码
哈希类型的内部编码有两种:
·ziplist(压缩列表):当哈希类型元素个数小于hash-max-ziplist-entries配置(默认512个)、同时所有值都小于hash-max-ziplist-value配置(默认64字节)时,Redis会使用ziplist作为哈希的内部实现,ziplist使用更加紧凑的结构实现多个元素的连续存储,所以在节省内存方面比hashtable更加优秀。
·hashtable(哈希表):当哈希类型无法满足ziplist的条件时,Redis会使用hashtable作为哈希的内部实现,因为此时ziplist的读写效率会下降,而hashtable的读写时间复杂度为O(1)。
五.列表
列表类型有两个特点:
第一、列表中的元素是有序的,这就意味着可以通过索引下标获取某个元素或者某个范围内的元素列表。
第二、列表中的元素可以是重复的
5.1命令
命令 | 描述 | 备注 | 时间复杂度 | |
---|---|---|---|---|
增加 | rpush key value [value ...] | 从右边插入元素 | O(k) | |
lpush key value [value ...] | 从左边插入元素 | O(k) | ||
linsert key before|after pivot value | 向某个元素前或者后插入元素 | O(n) | ||
查询 | lrange key start end | 获取指定范围内的元素列表 | 索引下标有两个特点: 第二,lrange中的end选项包含了自身,这个和很多编程语言不包含end不太相同 | O(s+n) s是start的偏移量,n是start到end范围 |
lindex key index | 获取列表指定索引下标的元素 | O(n) | ||
llen key | 获取列表长度 | O(1) | ||
删除 | lpop key | 从列表左侧弹出元素 | O(1) | |
rpop key | 从列表右侧弹出 | O(1) | ||
lrem key count value | 删除指定元素 | lrem命令会从列表中找到等于value的元素进行删除,根据count的不同分为三种情况: ·count>0,从左到右,删除最多count个元素。 ·count<0,从右到左,删除最多count绝对值个元素。 ·count=0,删除所有。 | O(n) | |
ltrim key start end | 按照索引范围修剪列表 | 如 ltrim listkey 1 3 会只保留列表listkey第2个到第4个元素: | O(n) | |
修改 | lset key index newValue | 修改指定索引下标的元素 | O(n) | |
堵塞操作 | blpop key [key ...] timeout
brpop key [key ...] timeout | ·key[key...]:多个列表的键。 ·timeout:阻塞时间(单位:秒) | 1)列表为空:如果timeout=3,那么客户端要等到3秒后返回,如果timeout=0,那么客户端一直阻塞等下去 2)列表不为空:客户端会立即返回。 | O(1) |
5.2内部编码
列表类型的内部编码有两种:
·ziplist(压缩列表):当列表的元素个数小于list-max-ziplist-entries配置(默认512个),同时列表中每个元素的值都小于list-max-ziplist-value配置时(默认64字节),Redis会选用ziplist来作为列表的内部实现来减少内存的使用。
·linkedlist(链表):当列表类型无法满足ziplist的条件时,Redis会使用linkedlist作为列表的内部实现。(quicklist是3.2版本新增加的,旧版本的linkedlist基本上是被淘汰掉了,而是使用的为quicklist来代替)
5.3使用场景
1.消息队列
2.文章列表
六. 集合
集合(set)类型也是用来保存多个的字符串元素,但和列表类型不一样的是,集合中不允许有重复元素,并且集合中的元素是无序的,不能通过索引下标获取元素。
6.1命令
命令 | 描述 | 备注 | 时间复杂度 |
---|---|---|---|
sadd key element [element ...] | 添加元素 | 返回结果为添加成功的元素个数, | O(k) |
srem key element [element .. | 删除元素 | 返回结果为成功删除元素个数, | O(k) |
scard key | 计算元素个数 | O(1) | |
sismember key element | 判断元素是否在集合中 | 在集合内返回1,反之返回0 | O(1) |
srandmember key [count] | 随机从集合返回指定个数元素 | O(count) | |
spop key | 从集合随机弹出元素 | Redis从3.2版本开始,spop也支持[count]参数。 | O(1) |
smembers key | 获取所有元素 | smembers和lrange、hgetall都属于比较重的命令,如果元素过多存在阻塞Redis的可能性,这时候可以使用sscan来完成, | O(n) |
6.2内部编码
集合类型的内部编码有两种:
intset(整数集合):当集合中的元素都是整数且元素个数小于set-maxintset-entries配置(默认512个)时,Redis会选用intset来作为集合的内部实现,从而减少内存的使用。
·hashtable(哈希表):当集合类型无法满足intset的条件时,Redis会使用hashtable作为集合的内部实现。
6.3使用场景
集合类型比较典型的使用场景是标签(tag)。例如一个用户可能对娱乐、体育比较感兴趣,另一个用户可能对历史、新闻比较感兴趣,这些兴趣点就是标签。
七.有序集合
有序集合相对于哈希、列表、集合来说会有一点点陌生,但既然叫有序集合,那么它和集合必然有着联系,它保留了集合不能有重复成员的特性,但不同的是,有序集合中的元素可以排序。但是它和列表使用索引下标作为排序依据不同的是,它给每个元素设置一个分数(score)作为排序的依据。
7.1命令
命令 | 描述 | 备注 | 时间复杂度 |
---|---|---|---|
zadd [NX|XX] [CH] [INCR] key score member [score member ...] | 添加成员 | Redis3.2为zadd命令添加了nx、xx、ch、incr四个选项: ·nx:member必须不存在,才可以设置成功,用于添加。 ·xx:member必须存在,才可以设置成功,用于更新。 ·ch:返回此次操作后,有序集合元素和分数发生变化的个数 ·incr:对score做增加,相当于后面介绍的zincrby。 | O(k*log(n)) k是添加成员的个数,n是当前有的个数 |
zcard key | 计算成员个数 | O(1) | |
zscore key member | 计算某个成员的分数 | O(1) | |
zrank key member zrevrank key member | 计算成员的排名 | zrank是从分数从低到高返回排名,zrevrank反之。 | O(log(n)) |
zrem key member [member ...] | 删除成员 | 返回结果为成功删除的个数。 | O(k*log(n)) k是添加成员的个数,n是当前有的个数 |
zincrby key increment member | 增加成员的分数 | O(log(n)) | |
zrange key start end [withscores] zrevrange key start end [withscores] | 返回指定排名范围的成员 | 有序集合是按照分值排名的,zrange是从低到高返回,zrevrange反之。 | O(log(n)) |
zrangebyscore key min max [withscores] [limit offset count] zrevrangebyscore key max min [withscores] [limit offset count] | 返回指定分数范围的成员 | 其中zrangebyscore按照分数从低到高返回,zrevrangebyscore反之。 同时min和max还支持开区间(小括号)和闭区间(中括号),-inf和+inf分别代表无限小和无限大 | O(log(n)) |
zcount key min max | 返回指定分数范围成员个数 | O(log(n)) | |
zremrangebyrank key start end | 删除指定排名内的升序元素 | O(log(n)) | |
zremrangebyscore key min max | 删除指定分数范围的成员 | O(log(n)) |
命令 | 描述 | 备注 | 时间复杂度 |
---|---|---|---|
zinterstore destination numkeys key [key ...] [weights weight [weight ...]] [aggregate sum|min|max] | 交集 | 参数说明: ·destination:交集计算结果保存到这个键。 ·aggregate sum|min|max:计算成员交集后,分值可以按照sum(和)、 | |
zunionstore destination numkeys key [key ...] [weights weight [weight ...]] [aggregate sum|min|max] | 并集 | 该命令的所有参数和zinterstore是一致的,只不过是做并集计算, |
7.2内部编码
有序集合类型的内部编码有两种:
·ziplist(压缩列表):当有序集合的元素个数小于zset-max-ziplistentries配置(默认128个),同时每个元素的值都小于zset-max-ziplist-value配置(默认64字节)时,Redis会用ziplist来作为有序集合的内部实现,ziplist可以有效减少内存的使用。
·skiplist(跳跃表):当ziplist条件不满足时,有序集合会使用skiplist作为内部实现,因为此时ziplist的读写效率会下降。
7.3使用场景
有序集合比较典型的使用场景就是排行榜系统。
八.键管理
命令 | 描述 | 备注 |
---|---|---|
rename key newkey | 键重命名 | 为了防止被强行rename,Redis提供了renamenx命令确保只有newKey不存在时候才被覆盖 由于重命名键期间会执行del命令删除旧的键,如果键对应的值比较大,会存在阻塞Redis的可能性,这点不要忽视 |
randomkey | 随机返回一个键 | |
expireat、pexpire、pexpireat、pttl、persist | 键过期 | ·expireat key timestamp:键在秒级时间戳timestamp后过期。 expire key seconds:键在seconds秒后过期。
·pexpire key milliseconds:键在milliseconds毫秒后过期。 ·pexpireat key milliseconds-timestamp键在毫秒级时间戳timestamp后过期。 |
迁移键 | 迁移键功能非常重要,因为有时候我们只想把部分数据由一个Redis迁移到另一个Redis(例如从生产环境迁移到测试环境),Redis发展历程中提供了move、dump+restore、migrate三组迁移键的方法,它们的实现方式以及使用的场景不太相同,下面分别介绍。 | |
...... | ||
....... |