在 Redis 官网,对于支持的数据类型,有这么一段描述:
它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。
常用命令
Redis 支持的命令比较多,对于常用的命令当然要记住,如果想不起来,可以去 http://www.redis.cn/commands.html 查阅。
基本操作指令
select 0
切换数据库dbsize
查看当前库 key 的数量flushdb
清空当前库flushall
清空所有库
键 key 指令
keys *
查看当前库所有 keyexists key
检查某个 key 是否存在type key
当前 key 的数据类型del key
删除该 key 的数据unlink key
非阻塞删除,仅将key 置为无效,后续异步删除expire key times
为该 key 设置过期时间ttl key
检查该key还有多久过期,-1为永不过期,-2为已过期rename key newkey
将 key 重命名,如果newkey存在,相当于用 key 的 value 覆盖 newkey 的 value
Redis 字符串
字符串 string 是 Redis 最基本的类型,一个 string 最多存储 512M 数据。
String 类型是二进制安全的,你可以在 string 中放任何数据,比如一个序列化的对象,甚至一张图片。
string 常用命令
与 string 常用的命令主要是添加元素、获取元素、更新元素。
set
添加键值对。
set key value [EX seconds | PX milliseconds | KEEPTTL] [NX|XX]
- EX key 失效时间秒数
- PX key 失效时间毫秒数
- NX 当 key 不存在时,可以将key-value 添加成功
- XX 当 key 存在时,可以将 key-value 添加成功
get
查询对应的键值对。
append
将给定值追加到原值末尾。
strlen
字符串的长度。
setnx
当 key 不存在时,设置 key 的值。
SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。
incr
将 key 中存储的数字增加 1,只能操作数据,如果为空,值会设置为 1.
decr
将 key 中存储的数字减少 1,只能操作数字,如果为空,值会被设置为 -1.
incrby / decrby
incr/decr 每次操作 1 个步长,incrby/decrby 可以指定步长。
mset/mget
相对于 set/get,同时处理多个 key 的存取。
setex
添加键值对的同时,设置过期时间,单位为秒。
getset
设置新值时获取旧值,以旧换新。
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> getset k1 s2
"v1"
Redis 字符串数据结构
Redis 为了提升性能,对于 string 的存储有很多处理。
- 整数 字符串长度小于 21 且可以转换为整数
- EmbeddedString 字符串长度小于限定值,不同redis版本限定值不同,一般在 40 左右
- SDS 上两种不符合时,采用 sds
embstr 与 sds 区别也在于性能:
- embstr 创建只分配一次内存,sds 为两次
- embstr 可以更好地利用缓存优势(因为内容很小)
- embstr 数据需要修改时,需要先转换为 sds
string的数据结构为简单动态字符串(Simple Dynamic String,缩写SDS)。是可以修改的字符串,内部结构实现上类似于Java的ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配。
SDS 底层实现
在C语言中,字符串可以用 \0
结尾的char数组标示。这种简单的字符串表示,在大多数情况下都能满足要求,但是不能高效的计算length和append数据。
SDS的数据结构如下,len表示sdshdr中数据的长度,free表示sdshdr中剩余的空间,buf表示实际存储数据的空间。
/*
* 保存字符串对象的结构
*/
struct sdshdr {
// buf 中已占用空间的长度
int len;
// buf 中剩余可用空间的长度
int free;
// 数据空间
char buf[];
};
Redis 为了提升速度,在存储上有额外空间使用,因此 sds 比原始 c 语言的字符串更占用空间。
SDS 扩容
当字符串长度小于 1M 时,扩容都是2倍,如果超过 1M,每次扩容 1M。最大的长度为 512M。
释放内存时,会修改 len 和 free 字段,并不立即释放实际占用的内存空间。
Redis 列表
列表 list 是单键多值的结构。
Redis 列表是简单的字符串列表,按照插入顺序排序。可以添加元素到列表头部或者尾部。
列表常用指令
与 list 常用的命令主要是添加元素、获取元素、更新元素。
lpush
从左边添加一个或多个元素。
127.0.0.1:6379> lpush mylist v1 v2 v3 v4
(integer) 4
rpush
从右边添加一个或多个元素。
lpop
从左边取出一个元素。
127.0.0.1:6379> lpop mylist
"v4"
127.0.0.1:6379> lpop mylist
"v3"
127.0.0.1:6379> lpop mylist
"v2"
127.0.0.1:6379> lpop mylist
"v1"
127.0.0.1:6379> lpop mylist
(nil)
这条指令不仅仅是查询,取出元素后便不在 list 中,所有元素取出后,key 也随即失效。
rpop
从右边取出一个元素。
rpoplpush
从一个key 的右边取出一个元素,插入到另一个key左边。
lrang
按照索引下标获取多个元素(从左到右)
127.0.0.1:6379> lrange mylist 0 -1
1) "v4"
2) "v3"
3) "v2"
4) "v1"
0 表示左边第一个,-1 表示右边第一个。
lindex
按照索引下标获得单个元素(从左到右)。
127.0.0.1:6379> lindex mylist 2
"v2"
llen
获得列表长度。
127.0.0.1:6379> llen mylist
(integer) 4
list 数据结构
List 的数据结构为快速链表 quickList。
在列表元素较少时,Redis 会采用一块连续的内存存储,结构为压缩链表 ziplist。
当数据量比较多时,采用 quicklist 。
这么做的原因是,普通链表需要的附加指针空间较大,会有一定空间浪费。
Redis 将链表和 ziplist 结合起来组成 quicklist。即将多个 ziplist 使用双向指针串起来。这样既满足快速插入、删除元素的性能,又不会出现太大的空间浪费。
集合 Set
set 提供的功能与 list 类似,只不过它的数据是去重的。
此外,set 提供了一个指令可以用来判断某个元素是否在 set 中存在。
常用命令
与 set 常用的命令主要是添加元素、获取元素、删除元素。
sadd
将一个或多个 member 元素加入到集合 key 中,已经存在的 member 元素将被忽略。
127.0.0.1:6379> sadd myset v1 v2 v3
(integer) 3
smembers
取出该集合的所有值。
127.0.0.1:6379> smembers myset
1) "v3"
2) "v2"
3) "v1"
sismember
判断集合中是否包含某个值,包含返回 1 ,不包含返回 0。
127.0.0.1:6379> sismember myset v3
(integer) 1
127.0.0.1:6379> sismember myset v5
(integer) 0
scard
返回该集合的元素个数。
127.0.0.1:6379> scard myset
(integer) 3
srem
删除集合中的某个元素。
127.0.0.1:6379> srem myset v3 v5
(integer) 1
127.0.0.1:6379> smembers myset
1) "v2"
2) "v1"
spop
随机从该集合中取出一个值。
sinter
返回两个集合的交集元素。
127.0.0.1:6379> sadd myset1 v1 v2 v3 v4
(integer) 4
127.0.0.1:6379> sadd myset2 v4 v5 v6
(integer) 3
127.0.0.1:6379> sinter myset1 myset2
1) "v4"
sunion
返回两个集合的并集元素。
127.0.0.1:6379> sadd myset1 v1 v2 v3 v4
(integer) 4
127.0.0.1:6379> sadd myset2 v4 v5 v6
(integer) 3
127.0.0.1:6379> sunion myset1 myset2
1) "v1"
2) "v2"
3) "v6"
4) "v5"
5) "v4"
6) "v3"
sdiff
返回两个集合的差集元素(key1中的,不包含key2中的)
127.0.0.1:6379> sadd myset1 v1 v2 v3 v4
(integer) 4
127.0.0.1:6379> sadd myset2 v4 v5 v6
(integer) 3
127.0.0.1:6379> sdiff myset1 myset2
1) "v3"
2) "v1"
3) "v2"
set 数据结构
set 是 string 类型的无序集合,它底层是一个 value 为 null 的 hash 表,能做到添加/查找/删除元素复杂度 O(1)。
Java中HashSet的内部实现使用的是HashMap,只不过所有的value都指向同一个对象。Redis的set结构也是一样,它的内部也使用hash结构,所有的value都指向同一个内部值。
Hash 哈希
Redis hash 是一个键值对集合。
Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。
hash 类似 Java 里面的 Map<String,Object>
对象存储有多种方案,比较容易想到的为方案一,但是修改数据需要先对 value 反序列化,不是很方便。
方案二也是一种思路,但是这样 key 相对复杂一些,有数据冗余,并且数据比较分散。
Redis 采用的是方案三,整体是键值对,value 又是键值对。
hash 常用指令
与 hash 相关的指令主要是添加与查看元素。
hset
给 <key>
集合中的 <field>
键赋值 <value>
hget
从 <key>
集合 <field>
取出 <value>
hmset
批量设置hash的值
hexists
查看哈希表 <key>
中,给定域 <field>
是否存在。
hkeys
列出该hash集合的所有<field>
hvals
列出该hash集合的所有<value>
hash 数据结构
Hash 类型对应的数据结构是两种:ziplist(压缩列表),hashtable(哈希表)。
当 field-value 长度较短且个数较少时,使用ziplist,否则使用hashtable。
有序集合 zset
Redis 的有序集合 zset(SortedSet)与 set 类似,是没有重复元素的字符串集合。
zset 给每个成员关联了一个评分 score,使得成员可以通过评分进行排序。
集合中的元素不能重复,但是评分可以重复。
常用命令
与 zset 常用的命令主要是添加元素、获取元素、删除元素。
zadd
将一个或多个 member 元素及其 score 值加入到有序集 key 当中。
127.0.0.1:6379> zadd myzset 1 v1 5 v2 3 v4
(integer) 3
zrange
返回有序集 key 中元素。
127.0.0.1:6379> zrange myzset 1 4
1) "v4"
2) "v2"
127.0.0.1:6379> zrange myzset 1 4 withscores
1) "v4"
2) "3"
3) "v2"
4) "5"
zcount
统计该集合,分数区间内的元素个数。
127.0.0.1:6379> zcount myzset 1 5
(integer) 3
zrank
返回该值在集合中的排名,从0开始,不存在的元素返回空 nil。
127.0.0.1:6379> zrank myzset v1
(integer) 0
127.0.0.1:6379> zrank myzset v4
(integer) 1
127.0.0.1:6379> zrank myzset v9
(nil)
zset 数据结构
zset底层使用了两个数据结构:
- hash hash的作用就是关联元素value和权重score,保障元素value的唯一性,可以通过元素value找到相应的score值。
- 跳跃表 跳跃表的目的在于给元素value排序,根据score的范围获取元素列表。
zset 结构比较特殊,一方面它等价于Java的数据结构 Map<String, Double>,可以给每一个元素value赋予一个权重score,另一方面它又类似于TreeSet,内部的元素会按照权重score进行排序,可以得到每个元素的名次,还可以通过score的范围来获取元素的列表。
跳跃表
有序集合在实际应用中较为常见,例如排名、热榜等。
实现有序集合可以采用数组、平衡树、链表等,但是它们都各自的优缺点。
- 数组的插入、删除元素效率较低
- 平衡树效率高但是结构复杂
- 链表查询元素效率不高
Redis 在实现有序集合时,采用了跳跃表,它效率与平衡树相当,而实现复杂度低很多。
对于一个有序链表 1->3->4->5->7->8->9->NULL,从中查找元素8,需要从第一个元素开始依次查找、比较才能找到,共需要6次比较。
找到元素8,经过的元素 1->3->4->5->7->8
如果采用跳跃表。
先从第2层开始比较,找到4以后,第一层结束,开始第二层,找到7以后,其下一个元素已经大于8,开始找第0层,共需查找 4 次。
找到元素8,经过的元素 1->4->7->8