前言
Redis作为基于键值对的NoSQL数据库,具有高性能、丰富的数据结构、持久化、高可用、分布式等特性。本期将对Redis进行详细的介绍,将介绍Redis的5个数据结构及命令。同时在正式介绍5种数据结构之前,了解一下Redis的一些全局命令、数据结构和内部编码、单线程命令处理机制。
Redis概述
Redis是一种基于键值对(key-value)的NoSQL数据库,与很多键值对数据库不同的是,Redis中的值可以是由string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(有序集合)、Bitmaps(位图)、HyperLogLog、GEO(地理信息定位)等多种数据结构和算法组成,因此Redis可以满足很多的应用场景,而且因为Redis会将所有数据都存放在内存中,所以它的读写性能非常惊人。Redis还可以将内存的数据利用快照和日志的形式保存到硬盘上,以外,Redis还提供了键过期、发布订阅、事务、流水线、Lua脚本等附加功能。
全局命令
Redis有5种数据结构,它们是键值对中的值,对于键来说有一些通用的命令。
1 查看所有键
keys *
keys *命令会将所有的键输出:
2 键总数
dbsize
dbsize命令会返回当前数据库中键的总数。
3 检查键是否存在
exists key
如果键存在则返回1,不存在则返回0:
4 删除键
del key [key ...]
del是一个通用命令,无论值是什么数据结构类型,del命令都可以将其删除。返回结果为成功删除键的个数.
5 键过期
expire key seconds
Redis支持对键添加过期时间,当超过过期时间后,会自动删除键。
6 查看键过期时间
ttl key
ttl命令会返回键的剩余过期时间,它有3种返回值:
- 大于等于0的整数:键剩余的过期时间。
- -1:键没设置过期时间。
- -2:键不存在
7 键的数据结构类型
type key
返回键的类型,如果键不存在,则返回none。
数据结构和内部编码
type命令实际返回的就是当前键的数据结构类型,它们分别是:string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(有序集合)。
但这些只是Redis对外的数据结构。实际上每种数据结构都有自己底层的内部编码实现,而且是多种实现,这样Redis会在合适的场景选择合适的内部编码。我们可以通过object encoding命令查询内部编码
127.0.0.1:6379> object encoding hello
"embstr"
127.0.0.1:6379> object encoding mylist
"ziplist"
Redis这样设计有两个好处:
第一,可以改进内部编码,而对外的数据结构和命令没有影响,这样一旦开发出更优秀的内部编码,无需改动外部数据结构和命令,例如Redis3.2提供了quicklist,结合了ziplist和linkedlist两者的优势,为列表类型提供了一种更为优秀的内部编码实现,而对外部用户来说基本感知不到。
第二,多种内部编码实现可以在不同场景下发挥各自的优势,例如ziplist比较节省内存,但是在列表元素比较多的情况下,性能会有所下降,这时候Redis会根据配置选项将列表类型的内部实现转换为linkedlist。
单线程架构
Redis使用了单线程架构和I/O多路复用模型来实现高性能的内存数据库服务。因为Redis是单线程来处理命令的,所以一条命令从客户端达到服务端不会立刻被执行,所有命令都会进入一个队列中,然后逐个被执行。但是为什么单线程的Redis这么快呢。
第一,纯内存访问,Redis将所有数据放在内存中,内存的响应时长大约为100纳秒,这是Redis达到每秒万级别访问的重要基础。
第二,非阻塞I/O,Redis使用epoll作为I/O多路复用技术的实现,再加上Redis自身的事件处理模型将epoll中的连接、读写、关闭都转换为事件,不在网络I/O上浪费过多的时间。非阻塞I/O的意思,简单理解,比如多个客户端发送请求,Redis需要写回数据,假设写入的容量只能是512KB,Redis完成后,不会等容量(套接字缓冲区)清空继续写入,而是先处理其他客户端的请求,等到套接字缓冲区容量清空,再来继续写入剩下的,这样实现了I/O多路复用,
第三,单线程避免了线程切换和竞态产生的消耗。既然采用单线程就能达到如此高的性能,那么也不失为一种不错的选择,因为单线程能带来几个好处:第一,单线程可以简化数据结构和算法的实现。如果对高级编程语言熟悉的读者应该了解并发数据结构实现不但困难而且开发测试比较麻烦。第二,单线程避免了线程切换和竞态产生的消耗,对于服务端开发来说,锁和线程切换通常是性能杀手。
字符串
字符串类型是Redis最基础的数据结构。首先键都是字符串类型,而且其他几种数据结构都是在字符串类型基础上构建的。字符串类型的值实际可以是字符串(简单的字符串、复杂的字符串(例如JSON、XML))、数字(整数、浮点数),甚至是二进制(图片、音频、视频),但是值最大不能超过512MB。
命令及内部编码
字符串类型的命令比较多,我们介绍一些常用的。
1 设置值
set key value [ex seconds] [px milliseconds] [nx|xx]
set命令有几个选项:
- ex seconds:为键设置秒级过期时间。
- px milliseconds:为键设置毫秒级过期时间。
- nx:键必须不存在,才可以设置成功,用于添加。
- xx:与nx相反,键必须存在,才可以设置成功,用于更新。
除了set选项,Redis还提供了setex和setnx两个命令,它们的作用和ex和nx选项是一样的。
由于Redis的单线程命令处理机制,如果有多个客户端同时执行setnx key value,根据setnx的特性只有一个客户端能够设置成功,setnx可以作为分布式锁的一种实现方案。
2 获取值
get key
获取对应键的对应值,如果要获取的键不存在,则返回nil(空)。
3 批量设置值
mest key value [key value ...]
4 批量获取值
mget key [key ...]
如果n个命令,有了mget相较于get就可以只需要一次的命令传输以及结果返回。
5 计数
incr key
incr命令用于对值做自增操作,返回结果分为三种情况:
- 值不是整数,返回错误。
- 值是整数,返回自增后的结果
- 键不存在,按照值为0自增,返回结果1
6 追加值
append key value
append可以向value字符串尾部追加值。
7 字符串长度
strlen key
返回value字符串得长度
8 设置并返回原值
getset key value
getset和set一样会设置值,但是不同的是,它同时会返回键原来的值。
9 设置指定位置的字符
setrange key offeset value
10 获取部分字符串
getrange key start end
start和end分别是开始和结束的偏移量,偏移量从0开始计算
字符串的内部编码有三钟:
- int:8个字节的长整型
- embstr:小于等于39个字节的字符串
- raw:大于39个字节的字符串
Redis会根据当前值的类型和长度决定使用哪种内部编码实现。
哈希
几乎所有的编程语言都提供了哈希类型。在Redis中,哈希类型是指键值是一个键值对结构。形如value={{field1,value1},…{fieldN,valueN}}
命令以及内部编码
1 设置值
hset key field value
设置成功返回1,反之返回0。此外,Redis还提供了hsetnx命令,与setnx类似。
2 获取值
hget key field
3 删除field
hdel key field [field ...]
4 计算field个数
hlen key
5 批量设置或获取field-value
hmget key field [field ...]
hmset key field value [field value ...]
6 判断field是否存在
hexists key field
7 获取所有field
hkeys key
8 获取所有value
hvals key
9 获取所有的field-value
hgetall key
10 hincrby hincrbyfloat
hincrby key field increment
hincrbyfloat key field increment
hincrby对哈希中指定字段field的值增加一个整数值increment,hincrbyfloat给哈希字段中的值增加一个浮点数increment。
11 计算value的字符串长度
hstrlen key field
各个命名的时间复杂度如下:
哈希类型的内部编码有两种:
- ziplist:当哈希类型元素个数小于hash-max-ziplist-entries配置(默认512个)、同时所有值都小于hash-max-ziplist-value配置(默认64字节)时,Redis会使用ziplist作为哈希的内部实现,ziplist使用更加紧凑的结构实现多个元素的连续存储,所以在节省内存方面比hashtable更加优秀。
- hashtable:当哈希类型无法满足ziplist的条件时,Redis会使用hashtable作为哈希的内部实现,因为此时ziplist的读写效率会下降,而hashtable的读写时间复杂度为O(1)。
列表
列表是用来存储多个有序的字符串,列表的每个字符串都称为元素,一个列表最多存储
2
32
−
1
2^{32}-1
232−1个元素,在Redis中,可以对列表的两端插入和弹出,还可以获取指定范围的元素列表、获取指定索引下标的元素等。列表是一种比较灵活的数据结构,它可以充当栈和队列的角色。
列表类型有两个特点:
第一、列表中的元素是有序的,这就意味着可以通过索引下标获取某个元素或者某个范围内的元素列表。列表(List)的「有序」指的是元素在列表中的存储顺序由插入或操作的顺序决定,而非根据元素的值大小或其他规则排序。
第二、列表中的元素可以是重复的。
命令以及内部编码
按照对列表的5种操作类型对命令进行介绍:
1 从右边插入元素
rpush key value [value ...]
插入到列表最右边。
2 从左边插入元素
lpush key value [value ...]
插入到列表最左边
3 从某元素前或后插入元素
linsert key before|after pivot value
linsert命令会从列表中找到等于pivot的元素,在其前(before)或者后(after)插入一个新的元素value。
4 获取指定范围内的元素列表
lrange key start end
lrange操作会获取列表指定索引范围所有的元素。索引下标有两个特点:
第一,索引下标从左到右分别是0到N-1,但是从右到左分别是-1到-N。
第二,lrange中的end选项包含了自身,这个和很多编程语言不包含end不太相同
5 获取列表指定索引下标的元素
lindex key index
获取指定索引的元素。
6 获取列表长度
llen key
7 从列表左侧弹出元素
lpop key
8 从列表右侧弹出元素
rpop key
9 删除指定元素
lrem key count value
lrem命令会从列表中找到等于value的元素进行删除,根据count的不同分为三种情况:
- count>0,从左到右,删除最多count个元素。
- count<0,从右到左,删除最多count绝对值个元素。
- count=0,删除所有。
10 按照指定范围修剪列表
ltrim key start end
保留start到end的元素,下标从0开始。
11 修改
lset key index newValue
修改index位置的元素,下标从0开始。
12 阻塞操作
blpop key [key ...] timeout
brpop key [key ...] timeout
如果列表为空,客户端会阻塞等待,直到以下两种情况发生:
- 任意一个列表被推入新元素(其他客户端执行 LPUSH/RPUSH 等操作)。
- 等待时间超过 timeout(返回 nil)。
list的内部编码有两种:
- ziplist(压缩列表):当列表的元素个数小于list-max-ziplist-entries配置(默认512个),同时列表中每个元素的值都小于list-max-ziplist-value配置时(默认64字节),Redis会选用ziplist来作为列表的内部实现来减少内存的使用。
- linkedlist(链表):当列表类型无法满足ziplist的条件时,Redis会使用linkedlist作为列表的内部实现。
注意:Redis3.2版本提供了quicklist内部编码,简单地说它是以一个ziplist为节点的linkedlist,它结合了ziplist和linkedlist两者的优势,为列表类型提供了一种更为优秀的内部编码实现。
集合
集合(set)类型也是用来保存多个的字符串元素,但和列表类型不一样的是,集合中不允许有重复元素,并且集合中的元素是无序的,不能通过索引下标获取元素。一个集合最多可以存储
2
32
−
1
个
2^{32}-1个
232−1个元素。
命令以及内部编码
1 添加元素
sadd key element [element ...]
2 删除元素
srem key element [element ...]
3 计算元素的个数
scard key
scard的时间复杂度为O(1),它不会遍历集合所有元素,而是直接用Redis内部的变量
4 判断元素是否在集合中
sismember key element
在返回1,反之返回0
5 随机从集合中返回指定个数的元素
srandmember key [count]
[count]是可选参数,不选默认是1
6 从集合中随机弹出元素
spop key
7 获取所有元素
smembers key
8 求多个集合的交集
sinter key [key ...]
9 求多个集合的并集
suinon key [key ...]
10 求多个集合的差集
sdiff key [key ...]
11 将交集、并集、差集的结果保存
sinterstore destination key [key …]
suionstore destination key [key …]
sdiffstore destination key [key …]
将集合间交集、并集、差集的结果保存在destination键中,原集合不会被修改。
集合类型的内部编码有两种:
- intset(整数集合):当集合中的元素都是整数且元素个数小于set-max-intset-entries配置(默认512个)时,Redis会选用intset来作为集合的内部实现,从而减少内存的使用。
- hashtable(哈希表):当集合类型无法满足intset的条件时,Redis会使用hashtable作为集合的内部实现。
有序集合
有序集合相对于哈希、列表、集合来说会有一点点陌生,但既然叫有序集合,那么它和集合必然有着联系,它保留了集合不能有重复成员的特性,但不同的是,有序集合中的元素可以排序。但是它和列表使用索引下标作为排序依据不同的是,它给每个元素设置一个分数(score)作为排序的依据。
我们简单对比一下列表、集合、有序集合。
命令
1 添加成员
zadd key score member [score member ...]
Redis3.2为zadd命令添加了nx、xx、ch、incr四个选项:
- nx:member必须不存在,才可以设置成功,用于添加。
- xx:member必须存在,才可以设置成功,用于更新。
- ch:返回此次操作后,有序集合元素和分数发生变化的个数
- incr:对score做增加,相当于后面介绍的zincrby。
2 计算成员个数
zcard key
3 计算某个成员得分数
zscore key member
4 计算成员的排名
zrank key member
zrevrank key member
zrank是从分数从低到高返回排名,zrevrank反之。 排名从0开始。
5 删除成员
zrem key member[member ...]
6 增加成员的分数
zincrby key increment member
给member增加了increment 分
7 返回指定排名范围的成岩
zrange key start end [withscores]
zrevrange key start end [withscores]
有序集合是按照分值排名的,zrange是从低到高返回,zrevrange反之。如果加上withscores选项,同时会返回成员的分数
8 返回指定分数范围的成员
zrangebyscore key min max [withscores] [limit offset count]
zrevrangebyscore key max min [withscores] [limit offset count]
其中zrangebyscore按照分数从低到高返回,zrevrangebyscore反之。
9 返回指定分数范围成员个数
zcount key min max
10 删除指定排名内的升序元素
zremrangebyrank key start end
分数升序排序,删除第start到第end名的成员。
11 删除指定分数范围的成员
zremrangebyscore key min
12 交集
zinterstore destination numkeys key [key …] [weights weight [weight …]]
[aggregate sum|min|max]
- destination:交集计算结果保存到这个键。
- numkeys:需要做交集计算键的个数。
- key[key…]:需要做交集计算的键。
- weights weight[weight…]:每个键的权重,在做交集计算时,每个键中的每个member会将自己分数乘以这个权重,每个键的权重默认是1。
- aggregate sum|min|max:计算成员交集后,分值可以按照sum(和)、min(最小值)、max(最大值)做汇总,默认值是sum。
13 并集
zunionstore destination numkeys key [key …] [weights weight [weight …]]
[aggregate sum|min|max]
该命令的所有参数和zinterstore是一致的,只不过是做并集计算
有序集合类型的内部编码有两种:
- ziplist(压缩列表):当有序集合的元素个数小于zset-max-ziplist-entries配置(默认128个),同时每个元素的值都小于zset-max-ziplist-value配置(默认64字节)时,Redis会用ziplist来作为有序集合的内部实现,ziplist可以有效减少内存的使用。
- skiplist(跳跃表):当ziplist条件不满足时,有序集合会使用skiplist作为内部实现,因为此时ziplist的读写效率会下降。
通用命令
1 键重命名
rename key newkey
如果在rename之前,key键已经存在,那么它的值也将被覆盖,为了防止被强行rename,Redis提供了renamenx命令,确保只有newKey不存在时候才被覆盖。
2 随机返回一个键
randomkey
3 键过期
除了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:键不存在。
expireat命令可以设置键的秒级过期时间戳,例如如果需要将键hello在2016-08-0100:00:00(秒级时间戳为1469980800)过期
pexpire key milliseconds:键在milliseconds毫秒后过期。
pexpireat key milliseconds-timestamp键在毫秒级时间戳timestamp后过期。
4 迁移键
迁移键功能非常重要,因为有时候我们只想把部分数据由一个Redis迁移到另一个Redis.
(1)move
move key db
(2) dump+restore
dump key
restore key ttl value
dump+restore可以实现在不同的Redis实例之间进行数据迁移的功能,整个迁移的过程分为两步:
1)在源Redis上,dump命令会将键值序列化,格式采用的是RDB格式。
2)在目标Redis上,restore命令将上面序列化的值进行复原,其中ttl参数代表过期时间,如果ttl=0代表没有过期时间。
(3)migrate
migrate host port key|"" destination-db timeout [copy] [replace] [keys key [key …]]
migrate命令也是用于在Redis实例间进行数据迁移的,实际上migrate命令就是将dump、restore、del三个命令进行组合,从而简化了操作流程。migrate命令具有原子性,而且从Redis3.0.6版本以后已经支持迁移多个键的功能,有效地提高了迁移效率,migrate在10.4节水平扩容中起到重要作用。
下面对migrate的参数进行逐个说明:
- host:目标Redis的IP地址。
- port:目标Redis的端口。
- key|“”:在Redis3.0.6版本之前,migrate只支持迁移一个键,所以此处是要迁移的键,但Redis3.0.6版本之后支持迁移多个键,如果当前需要迁移多个键,此处为空字符串""。
- destination-db:目标Redis的数据库索引,例如要迁移到0号数据库,这里就写0。
- timeout:迁移的超时时间(单位为毫秒)。
- [copy]:如果添加此选项,迁移后并不删除源键。
- [replace]:如果添加此选项,migrate不管目标Redis是否存在该键都会正常迁移进行数据覆盖。
- [keys key[key…]]:迁移多个键,例如要迁移key1、key2、key3,此处填写“keys key1 key2 key3”。
总结
本期介绍了Redis五种数据结构常用命令以及其内部编码,同时对一些通用命令进行介绍。下一期将用java对其进行操作。
参考文献
Redis开发与运维 付磊 张益军
写在文末
有疑问的友友,欢迎在评论区交流,笔者看到会及时回复。
请大家一定一定要关注!!!
请大家一定一定要关注!!!
请大家一定一定要关注!!!
友友们,你们的支持是我持续更新的动力~