前言
Redis中提供了五种常用的结构:
字符串(string)、列表(list)、集合(set)、散列(hash)、有序集合(zset).
还有三种在特定场景会发挥作用的三种结构:HyperLogLog、Geo、Pub/Sub。
一、字符串
字符串可以存储三种类型的值:字节串、整数、浮点数
命令 | 行为 |
GET | 根据键获取值 |
SET | 设置某个键对应某个值 |
DEL | 删除某个键下的值 |
示例: |
127.0.0.1:6379> set hello world
OK
127.0.0.1:6379> get hello
"world"
127.0.0.1:6379> del hello
(integer) 1
127.0.0.1:6379> get hello
(nil)
127.0.0.1:6379>
nil
的意思就是空,即不存在这个键。
虽然字符串是redis中最简单的结构,但是redis也提供了相关的数值自增和自减操作,以及二进制位和子串的处理命令。
先看看数值自增和自减操作命令。
命令 | 用例和描述 |
INCR | INCR key -------代表将值+1 |
DECR | DECR key -------代表将值-1 |
INCRBY | INCRBY key amount--------将值加上amount |
DECRBY | DECRBY key amount--------将值减去amount |
INCRBYFLOAT | INCRBYFLOAT key amount--------将值加上浮点数amount |
再看子串的操作命令
命令 | 用例和描述 |
APPEND | APPEND key value-----------------将值value追加到某个键的值后面 |
GETRANGE | 获取一个从偏移量start到偏移量end范围的字符组成的子串,包括start和end |
SETRANGE | SETRANGE key offset value------------将从start偏移量 开始的子串设置为指定值 |
Redis中string
底层实现方式:动态字符串sds
或者 long
- 当存储的value是数字时,redis会把其转换为long类型保存,从而节省存储空间,并支持incr、decr等操作
- 否则,采用sds来存储value
sds全称Simple Dynamic String,可动态扩展内存。具有如下优点:
- 如其名,可动态扩展内存
- 兼容C语言类型
- 不仅仅可以存储字符串,可存储任意二进制数据。
redis定义sds结构如下
struct sdshdr{
int free; // buf[]数组未使用字节的数量
int len; // buf[]数组所保存的字符串的长度
char buf[]; // 保存字符串的数组
}
Redis free有什么用?
redis free是预分配冗余空间,有两个好处:
- 字符串增长的时候可以减少扩容次数
- 字符串减短的时候,不直接释放空间,而是记录在free中,后续需要扩容,可以直接使用
Redis 为什么不直接使用 C 字符串,而要自己构建一种字符串抽象类型 SDS(simple dynamic string)?
- 工作中使用redis,经常会通过STRLEN命令得到一个字符串的长度,在SDS结构中
len
属性记录了字符串的长度,所以我们获取一个字符串长度直接取len的值,复杂度是O(1)
- C字符串长度是一定的,所以每次在增长或者缩短字符串时,都要做内存的重分配,而内存重分配算法通常又是一个比较耗时的操作,如果程序不经常修改字符串还是可以接受的。但很不幸,redis作为一个数据库,数据肯定会被频繁修改,如果每次修改都要执行一次内存重分配,那么就会严重影响性能。
二、列表
通俗来讲,列表是一个键对应多个值的结构。
命令 | 行为 |
RPUSH | 将某个值放到列表的右端,命令生效后返回列表的长度 |
LPUSH | 将某个值放到列表的左端,命令生效后返回列表的长度 |
LPOP | 从列表的左端弹出一个值 |
RPOP | 从列表的右端弹出一个值 |
LINDEX | 根据索引获取列表中的某个值 |
示例 |
127.0.0.1:6379> RPUSH hello world
(integer) 1
127.0.0.1:6379> LPUSH hello world1
(integer) 2
127.0.0.1:6379> LPOP hello
"world1"
127.0.0.1:6379> RPOP hello
"world"
127.0.0.1:6379> RPUSH hello world
(integer) 1
127.0.0.1:6379> LINDEX hello 0
"world"
LPOP和RPOP提供了移除列表值的功能,但是每次只能移除一个,LTRIM则让一次移除多个元素变为可能,当然,也可以通过redis的事务特性来实现,这里先不阐述。
LTRIM | 对列表进行修剪,只保留从start偏移量到end偏移量范围内的元素 |
对于列表,除上述之外,还有 |
命令 | 用例和描述 |
BLPOP | BLPOP key…[key] timeout 从第一个非空列表中弹出位于最左端的元素,或者在timeout秒之内阻塞并等待可弹出的元素 |
BRPOP | 与上面对应,但是是弹出最右端的元素 |
RPOPLPUSH | RPOPLPUSH source-key dest-key 从字面意思就可以看出来,这个命令是把source-key对应列表的最右端的元素弹出,并推入dest-key对应列表的最左边 |
BRPOPLPUSH | BPOPLPUSH source-key dest-key timeout 与前面命令类似,这个命令是把source-key对应列表的最右端的元素弹出,并推入dest-key对应列表的最左边,不同的是,如果source-key为空,那么在timeout秒之内阻塞并等待可弹出的元素出现 |
三、集合
与我们常见的集合一样,redis中的集合是通过散列表来保证自己存储的每个字符串都是各不相同的。
命令 | 行为 |
SADD | 将给定元素添加到集合 |
SMEMBERS | 返回集合包含的所有元素 |
SISMEMBER | 检查某元素是否存在于集合中 |
SREM | 检查某元素是否存在,若存在则移除 |
示例 |
127.0.0.1:6379> SADD hello world
(integer) 1
127.0.0.1:6379> SMEMBERS hello
1) "world"
127.0.0.1:6379> SISMEMBER hello world
(integer) 1
127.0.0.1:6379> SREM hello world
(integer) 1
集合这种数据结构可以用来求交集、并集、差集,具体的应用有用户的共同喜好等。
- 当set存储的是整数,并且长度小于512时,内部采用
intset
- 否则,使用
dict
来存储
redis中的dict是基于哈希算法实现的,并且采用某个哈希函数从key计算得到在哈希表中的位置,采用拉链法解决冲突,并在装载因子(load factor)超过预定值时自动扩展内存,引发重哈希(rehashing)
四、散列
在某种程度上来说,散列可以看作是缩小版的redis,散列可以存储多个键值对。通俗来说,散列是一种一个键对应多个键值对的数据结构。
命令 | 行为 |
HSET | 在散列中添加键值对,并且会返回1或0来标识是添加的键值对是否本来已经在里面 |
HGET | 在散列中,根据某个键获取该键对应的值 |
HGETALL | 根据散列的键,获取该散列中所有的键值对 |
HDEL | 如果给定的键存在于散列,则删除这个键 |
示例 |
127.0.0.1:6379> hset sanlie hello world
(integer) 1
127.0.0.1:6379> hset sanlie hello1 world
(integer) 1
127.0.0.1:6379> hset sanlie hello world
(integer) 0
127.0.0.1:6379> hget sanlie hello
"world"
127.0.0.1:6379> hgetall sanlie
1) "hello"
2) "world"
3) "hello1"
4) "world"
在第三行命令执行后,返回0,代表在该散列中,已经存在该键。
散列亦有用于添加和删除键值对的散列操作
命令 | 用例和描述 |
HMGET | 获取散列中某个键对应的值 |
HMSET | 设置散列中某个键对应的值 |
HDEL | 删除散列里面的一个或多个键值对 |
HLEN | 返回散列中包含的键值对数量 |
五、有序集合
与散列一样,有序集合也是用来存储键值对的。
不同的是,有序集合中键值对的键被称为成员
,值被称为分值
,分值必须为浮点数。
命令 | 行为 |
ZADD | 将一个带有给定分值的成员添加到有序集合中,返回添加元素的个数 |
ZRANGE | 根据元素在有序排列中的位置,从有序集合里面获取多个元素 |
ZRANGEBYSCORE | 根据一个分值段来获取在该分值段的所有元素 |
ZREM | ZREM key member-------如果给定成员存在于该有序集合,则删除该成员 |
ZCARD | ZCARD key--------返回有序集合包含的成员数量 |
ZCOUNT | ZCOUNT key min max----------返回分值介于min 和max之间的成员数量 |
ZSCORE | ZSOCRE key member --------返回成员的分值 |
ZINCRBY | ZINCRBY key increment member -----将member成员的分值加上increment |
示例 |
127.0.0.1:6379> ZADD ZZ 728 member1
(integer) 1
127.0.0.1:6379> ZADD ZZ 729 member2
(integer) 1
127.0.0.1:6379> ZADD ZZ 729 member2
(integer) 0
127.0.0.1:6379> ZRANGE ZZ 0 -1
1) "member1"
2) "member2"
127.0.0.1:6379> ZRANGEBYSCORE ZZ 728 728
1) "member1"
127.0.0.1:6379> ZREM ZZ member1
(integer) 1
第三行我们在键为ZZ的有序集合中添加了已经存在的键,所以返回0标识已存在,但是会覆盖原来的值。
第四行我们通过ZRANGE命令来获取下标范围的键值对,这里只返回显示了成员,如果需要返回分值,则在命令后加withscores
即可。
有序集合的底层实现方式:压缩列表ziplist
或者 zset
当存储的数据同时满足下面这两个条件的时候,Redis就使用压缩列表ziplist实现sorted set
- 集合中每个数据的大小都要小于 64 字节
- 元素个数要小于 128 个,也就是ziplist数据项小于256个
当不能同时满足这两个条件的时候,Redis 就使用zset
来实现sorted set,这个zset包含一个dict + 一个skiplist。
dict
用来查询数据到分数(score)的对应关系,而skiplist
用来根据分数查询数据(可能是范围查找)。
HyperLogLog、Geo、Pub/Sub
HyperLogLog
HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定 的、并且是很小的。
Geo
Redis 的 GEO 是 3.2 版本的新特性。这个功能可以将用户给定的地理位置信息储存起来, 并对这些信息进行操作.
Pub/Sub
redis被设计的非常轻量级和简洁,它做到了消息的“发布”和“订阅”的基本能力.
一个Redis client发布消息,其他多个redis client订阅消息,发布的消息“即发即失”,redis不会持久保存发布的消息;
消息订阅者也将只能得到订阅之后的消息,通道中此前的消息将无从获得。