Redis 基础数据结构
Redis 有 5 种基础数据结构,分别为:string (字符串)、list (列表)、set (集合)、hash (哈希) 和 zset (有序集合)。
import redis
r = redis.StrictRedis(host='localhost',port=6379,db=0)
string (字符串)
字符串 string 是 Redis 最简单的数据结构。
Redis没有Int、Float、Boolean等数据类型的概念,所有的基本类型在Redis中都以String体现。
Redis 所有的数据结构都是以唯一的 key 字符串作为名称,然后通过这个唯一 key 值来获取相应的 value 数据。不同类型的数据结构的差异就在于 value 的结构不一样。
键值对
r.set('foo','bar')
True
r.get('foo')
b'bar'
r.exists('foo')
True
r.delete('foo')
0
r.exists('foo')
False
批量键值对
可以批量对多个字符串进行读写,节省网络耗时开销。
r.mset({'China':'Beijing','Japan':'Tokyo'})
True
r.mget('China','Japan')
[b'Beijing', b'Tokyo']
过期和 set 命令扩展
可以对 key 设置过期时间,到点自动删除,这个功能常用来控制缓存的失效时间。
!redis-cli -h 127.0.0.1 setnx china shanghai
(integer) 0
!redis-cli -h 127.0.0.1 ttl china
(integer) -1
!redis-cli -h 127.0.0.1 setex china 10 shanghai
!redis-cli -h 127.0.0.1 ttl china
OK
(integer) 10
!redis-cli -h 127.0.0.1 ttl china
(integer) 4
!redis-cli -h 127.0.0.1 set China Beijing EX 20 XX
OK
计数
如果 value 值是一个整数,还可以对它进行自增操作。自增是有范围的,它的范围是 signed long 的最大最小值,超过了这个值,Redis 会报错。
!redis-cli -h 127.0.0.1 set age 20
!redis-cli -h 127.0.0.1 incr age
!redis-cli -h 127.0.0.1 incrby age 4
!redis-cli -h 127.0.0.1 incrby age -5
!redis-cli -h 127.0.0.1 decrby age 5
!redis-cli -h 127.0.0.1 decrby age -5
!redis-cli -h 127.0.0.1 decr age
OK
(integer) 21
(integer) 25
(integer) 20
(integer) 15
(integer) 20
(integer) 19
归纳与String相关的常用命令:
-
SET:为一个key设置value,可以配合EX/PX参数指定key的有效期,通过NX/XX参数针对key是否存在的情况进行区别操作,时间复杂度O(1)
-
GET:获取某个key对应的value,时间复杂度O(1)
-
GETSET:为一个key设置value,并返回该key的原value,时间复杂度O(1)
-
MSET:为多个key设置value,时间复杂度O(N)
-
MSETNX:同MSET,如果指定的key中有任意一个已存在,则不进行任何操作,时间复杂度O(N)
-
MGET:获取多个key对应的value,时间复杂度O(N)
Redis的基本数据类型只有String,但Redis可以把String作为整型或浮点型数字来使用,主要体现在INCR、DECR类的命令上:
-
INCR:将key对应的value值自增1,并返回自增后的值。只对可以转换为整型的String数据起作用。时间复杂度O(1)
-
INCRBY:将key对应的value值自增指定的整型数值,并返回自增后的值。只对可以转换为整型的String数据起作用。时间复杂度O(1)
-
DECR/DECRBY:同INCR/INCRBY,自增改为自减。
INCR/DECR系列命令要求操作的value类型为String,并可以转换为64位带符号的整型数字,否则会返回错误。
也就是说,进行INCR/DECR系列命令的value,必须在[-2^63 ~ 2^63 - 1]范围内。
Redis采用单线程模型,天然是线程安全的,这使得INCR/DECR命令可以非常便利的实现高并发场景下的精确控制
- 库存控制
- 自增序列
list(列表)
Redis将列表数据结构命名为list而不是array,是因为列表的存储结构用的是链表而不是数组,而且链表还是双向链表。
这意味着 list 的插入和删除操作非常快,时间复杂度为 O(1),但是索引定位很慢,时间复杂度为 O(n)
当列表弹出了最后一个元素之后,该数据结构自动被删除,内存被回收。
Redis 的列表结构常用来做异步队列使用。
将需要延后处理的任务结构体序列化成字符串塞进 Redis 的列表,另一个线程从这个列表中轮询数据进行处理。
负下标 链表元素的位置使用自然数0,1,2,….n-1表示,还可以使用负数-1,-2,…-n来表示,-1表示「倒数第一」,-2表示「倒数第二」,那么-n就表示第一个元素,对应的下标为0。
右进左出| 左进右出:队列
!redis-cli -h 127.0.0.1 del books
!redis-cli -h 127.0.0.1 rpush books php go python
!redis-cli -h 127.0.0.1 llen books
!redis-cli -h 127.0.0.1 lrange books 0 -1
(integer) 3
(integer) 3
1) "php"
2) "go"
3) "python"
!redis-cli -h 127.0.0.1 lpop books
!redis-cli -h 127.0.0.1 lpop books
!redis-cli -h 127.0.0.1 lpop books
!redis-cli -h 127.0.0.1 lpop books
"php"
"go"
"python"
(nil)
右进右出|左进左出:栈
!redis-cli -h 127.0.0.1 del books
!redis-cli -h 127.0.0.1 rpush books php go python
!redis-cli -h 127.0.0.1 llen books
!redis-cli -h 127.0.0.1 lrange books 0 -1
!redis-cli -h 127.0.0.1 rpop books
!redis-cli -h 127.0.0.1 rpop books
!redis-cli -h 127.0.0.1 rpop books
!redis-cli -h 127.0.0.1 rpop books
(integer) 1
(integer) 3
(integer) 3
1) "php"
2) "go"
3) "python"
"python"
"go"
"php"
(nil)
慢操作
lindex 它需要对链表进行遍历,性能随着参数index增大而变差。
ltrim 跟的两个参数start_index和end_index定义了一个区间,在这个区间内的值,ltrim 要保留,区间之外统统砍掉。
我们可以通过ltrim来实现一个定长的链表,这一点非常有用。
index 可以为负数,index=-1表示倒数第一个元素,同样index=-2表示倒数第二个元素。
!redis-cli -h 127.0.0.1 rpush lang php go python
!redis-cli -h 127.0.0.1 lindex lang 1
(integer) 3
"go"
!redis-cli -h 127.0.0.1 ltrim lang 1 -1
!redis-cli -h 127.0.0.1 lrange lang 0 -1
OK
归纳与List相关的常用命令
- LPUSH:向指定List的左侧(即头部)插入1个或多个元素,返回插入后的List长度。时间复杂度O(N),N为插入元素的数量
- RPUSH:同LPUSH,向指定List的右侧(即尾部)插入1或多个元素
- LPOP:从指定List的左侧(即头部)移除一个元素并返回,时间复杂度O(1)
- RPOP:同LPOP,从指定List的右侧(即尾部)移除1个元素并返回
- LPUSHX/RPUSHX:与LPUSH/RPUSH类似,区别在于,LPUSHX/RPUSHX操作的key如果不存在,则不会进行任何操作
- LLEN:返回指定List的长度,时间复杂度O(1)
- LRANGE:返回指定List中指定范围的元素(双端包含,即LRANGE key 0 10会返回11个元素),时间复杂度O(N)。应尽可能控制一次获取的元素数量,一次获取过大范围的List元素会导致延迟,同时对长度不可预知的List,避免使用LRANGE key 0 -1这样的完整遍历操作。
应谨慎使用的List相关命令
- LINDEX:返回指定List指定index上的元素,如果index越界,返回nil。index数值是回环的,即-1代表List最后一个位置,-2代表List倒数第二个位置。时间复杂度O(N)
- LSET:将指定List指定index上的元素设置为value,如果index越界则返回错误,时间复杂度O(N),如果操作的是头/尾部的元素,则时间复杂度为O(1)
- LINSERT:向指定List中指定元素之前/之后插入一个新元素,并返回操作后的List长度。如果指定的元素不存在,返回-1。如果指定key不存在,不会进行任何操作,时间复杂度O(N)
Redis还提供了一系列阻塞式的操作命令,如BLPOP/BRPOP等,能够实现类似于BlockingQueue的能力,即在List为空时,阻塞该连接,直到List中有对象可以出队时再返回
hash (字典)
hash 结构也可以用来存储用户信息,不同于字符串一次性需要全部序列化整个对象,hash 可以对用户结构中的每个字段单独存储。
这样当我们需要获取用户信息时可以进行部分获取。而以整个字符串的形式去保存用户信息的话就只能一次性全部读取,这样就会比较浪费网络流量。
hash 也有缺点,hash 结构的存储消耗要高于单个字符串,到底该使用 hash 还是字符串,需要根据实际情况再三权衡。
Hash的优点包括:
-
可以实现二元查找,如”查找ID为1000的用户的年龄”
-
比起将整个对象序列化后作为String存储的方法,Hash能够有效地减少网络传输的消耗
-
当使用Hash维护一个集合时,提供了比List效率高得多的随机访问命令
增加元素
可以使用hset一次增加一个键值对,也可以使用hmset一次增加多个键值对
!redis-cli -h 127.0.0.1 hset website google google.com
!redis-cli -h 127.0.0.1 hmset website baidu baidu.com zhihu zhihu.com
(integer) 0
OK
获取元素
可以通过hget定位具体key对应的value
可以通过hmget获取多个key对应的value
可以使用hgetall获取所有的键值对
可以使用hkeys和hvals分别获取所有的key列表和value列表
!redis-cli -h 127.0.0.1 hget website google
"google.com"
!redis-cli -h 127.0.0.1 hmget website zhihu baidu
1) "zhihu.com"
2) "baidu.com"
!redis-cli -h 127.0.0.1 hgetall website
1) "google"
2) "google.com"
3) "baidu"
4) "baidu.com"
5) "zhihu"
6) "zhihu.com"
!redis-cli -h 127.0.0.1 hkeys website
1) "google"
2) "baidu"
3) "zhihu"
!redis-cli -h 127.0.0.1 hvals website
1) "google.com"
2) "baidu.com"
3) "zhihu.com"
删除元素
可以使用hdel删除指定key,hdel支持同时删除多个key
!redis-cli -h 127.0.0.1 hdel website google
!redis-cli -h 127.0.0.1 hdel website baidu zhihu
(integer) 2
判断元素是否存在
通常我们使用hget获得key对应的value是否为空就知道对应的元素是否存在了,不过如果value的字符串长度特别大,通过这种方式来判断元素存在与否就略显浪费,这时可以使用hexists指令。
!redis-cli -h 127.0.0.1 hexists website baidu
(integer) 0
计数器
hash结构还可以当成计数器来使用,对于内部的每一个key都可以作为独立的计数器。
如果value值不是整数,调用hincrby指令会出错。
!redis-cli -h 127.0.0.1 hget website googleAge
!redis-cli -h 127.0.0.1 hincrby website googleAge 20
!redis-cli -h 127.0.0.1 hget website googleAge
!redis-cli -h 127.0.0.1 hset website googleName google
!redis-cli -h 127.0.0.1 hincrby website googleName
"40"
(integer) 60
"60"
(integer) 1
(error) ERR wrong number of arguments for 'hincrby' command
归纳Hash相关的常用命令
-
HSET:将key对应的Hash中的field设置为value。如果该Hash不存在,会自动创建一个。时间复杂度O(1)
-
HGET:返回指定Hash中field字段的值,时间复杂度O(1)
-
HMSET/HMGET:同HSET和HGET,可以批量操作同一个key下的多个field,时间复杂度:O(N),N为一次操作的field数量
-
HSETNX:同HSET,但如field已经存在,HSETNX不会进行任何操作,时间复杂度O(1)
-
HEXISTS:判断指定Hash中field是否存在,存在返回1,不存在返回0,时间复杂度O(1)
-
HDEL:删除指定Hash中的field(1个或多个),时间复杂度:O(N),N为操作的field数量
-
HINCRBY:同INCRBY命令,对指定Hash中的一个field进行INCRBY,时间复杂度O(1)
应谨慎使用的Hash相关命令:
-
HGETALL:返回指定Hash中所有的field-value对。返回结果为数组,数组中field和value交替出现。时间复杂度O(N)
-
HKEYS/HVALS:返回指定Hash中所有的field/value,时间复杂度O(N)
上述三个命令都会对Hash进行完整遍历,Hash中的field数量与命令的耗时线性相关,对于尺寸不可预知的Hash, 应严格避免使用上面三个命令,而改为使用HSCAN命令进行游标式的遍历,
set (集合)
Redis的集合,它内部的键值对是无序的唯一的。
它的内部实现相当于一个特殊的字典,字典中所有的 value 都是一个值NULL。
当集合中最后一个元素移除之后,数据结构自动删除,内存被回收。
增加元素
可以一次增加多个元素
!redis-cli -h 127.0.0.1 SADD bbs "discuz.net"
!redis-cli -h 127.0.0.1 SADD bbs "discuz.net"
!redis-cli -h 127.0.0.1 SADD bbs "tianya.cn" "groups.google.com"
(integer) 1
(integer) 0
(integer) 2
读取元素
使用smembers列出所有元素
使用scard获取集合长度
使用srandmember获取随机count个元素,如果不提供count参数,默认为1
!redis-cli -h 127.0.0.1 SMEMBERS bbs
!redis-cli -h 127.0.0.1 SCARD bbs
!redis-cli -h 127.0.0.1 SRANDMEMBER bbs
1) "tianya.cn"
2) "discuz.net"
3) "groups.google.com"
(integer) 3
"groups.google.com"
删除元素
使用srem删除一到多个元素
使用spop删除随机一个元素
!redis-cli -h 127.0.0.1 SREM bbs
!redis-cli -h 127.0.0.1 SPOP bbs
判断元素是否存在
使用sismember指令,只能接收单个元素
!redis-cli -h 127.0.0.1 SISMEMBER bbs tianya.cn
归纳与Set相关的常用命令
-
SADD:向指定Set中添加1个或多个member,如果指定Set不存在,会自动创建一个。时间复杂度O(N),N为添加的member个数
-
SREM:从指定Set中移除1个或多个member,时间复杂度O(N),N为移除的member个数
-
SRANDMEMBER:从指定Set中随机返回1个或多个member,时间复杂度O(N),N为返回的member个数
-
SPOP:从指定Set中随机移除并返回count个member,时间复杂度O(N),N为移除的member个数
-
SCARD:返回指定Set中的member个数,时间复杂度O(1)
-
SISMEMBER:判断指定的value是否存在于指定Set中,时间复杂度O(1)
-
SMOVE:将指定member从一个Set移至另一个Set
慎用的Set相关命令:
-
SMEMBERS:返回指定Hash中所有的member,时间复杂度O(N)
-
SUNION/SUNIONSTORE:计算多个Set的并集并返回/存储至另一个Set中,时间复杂度O(N),N为参与计算的所有集合的总member数
-
SINTER/SINTERSTORE:计算多个Set的交集并返回/存储至另一个Set中,时间复杂度O(N),N为参与计算的所有集合的总member数
-
SDIFF/SDIFFSTORE:计算1个Set与1或多个Set的差集并返回/存储至另一个Set中,时间复杂度O(N),N为参与计算的所有集合的总member数
上述几个命令涉及的计算量大,应谨慎使用,特别是在参与计算的Set尺寸不可知的情况下,应严格避免使用。可以考虑通过SSCAN命令遍历获取相关Set的全部member
如果需要做并集/交集/差集计算,可以在客户端进行,或在不服务实时查询请求的Slave上进行。
zset(有序集合)
Redis Sorted Set是有序的、不可重复的String集合。
一方面它是一个 set,保证了内部 value 的唯一性,另一方面它可以给每个 value 赋予一个 score,代表这个 value 的排序权重。它的内部实现用的是一种叫做「跳跃列表」的数据结构。
Sorted Set中的每个元素都需要指派一个分数(score),Sorted Set会根据score对元素进行升序排序。如果多个member拥有相同的score,则以字典序进行升序排序。
zset 中最后一个 value 被移除后,数据结构自动删除,内存被回收。
Sorted Set非常适合用于实现排名。
增加元素
通过zadd指令可以增加一到多个value/score对,score放在前面
!redis-cli -h 127.0.0.1 ZADD page_rank 10 google.com
!redis-cli -h 127.0.0.1 ZADD page_rank 9 baidu.com 8 zhihu.com
!redis-cli -h 127.0.0.1 ZRANGE page_rank 0 -1 withscores
(integer) 1
(integer) 1
1) "zhihu.com"
2) "8"
3) "baidu.com"
4) "9"
5) "google.com"
6) "10"
长度
通过指令zcard可以得到zset的元素个数
!redis-cli -h 127.0.0.1 ZCARD page_rank
(integer) 3
删除元素
通过指令zrem可以删除zset中的元素,可以一次删除多个
!redis-cli -h 127.0.0.1 ZREM page_rank google.com baidu.com
!redis-cli -h 127.0.0.1 ZRANGE page_rank 0 -1 withscores
(integer) 2
1) "zhihu.com"
2) "8"
计数器
同hash结构一样,zset也可以作为计数器使用。
!redis-cli -h 127.0.0.1 ZINCRBY page_rank 1 zhihu.com
!redis-cli -h 127.0.0.1 ZRANGE page_rank 0 -1 withscores
"9"
1) "zhihu.com"
2) "9"
获取排名和分数
通过zscore指令获取指定元素的权重,
通过zrank指令获取指定元素的正向排名,
通过zrevrank指令获取指定元素的反向排名[倒数第一名]。正向是由小到大,负向是由大到小。
!redis-cli -h 127.0.0.1 zscore page_rank zhihu.com
!redis-cli -h 127.0.0.1 zrank page_rank zhihu.com
!redis-cli -h 127.0.0.1 zrank page_rank baidu.com
!redis-cli -h 127.0.0.1 zrank page_rank google.com
!redis-cli -h 127.0.0.1 zrevrank page_rank google.com
"8"
(integer) 0
(integer) 1
(integer) 2
(integer) 0
根据排名范围获取元素列表
通过zrange指令指定排名范围参数获取对应的元素列表,携带withscores参数可以一并获取元素的权重。
通过zrevrange指令按负向排名获取元素列表[倒数]。正向是由小到大,负向是由大到小。
!redis-cli -h 127.0.0.1 zrange page_rank 0 -1
1) "zhihu.com"
2) "baidu.com"
3) "google.com"
!redis-cli -h 127.0.0.1 zrange page_rank 0 -1 withscores
1) "zhihu.com"
2) "8"
3) "baidu.com"
4) "9"
5) "google.com"
6) "10"
!redis-cli -h 127.0.0.1 zrevrange page_rank 0 -1 withscores
1) "google.com"
2) "10"
3) "baidu.com"
4) "9"
5) "zhihu.com"
6) "8"
根据score范围获取列表
通过zrangebyscore指令指定score范围获取对应的元素列表。
通过zrevrangebyscore指令获取倒排元素列表。正向是由小到大,负向是由大到小。参数-inf表示负无穷,+inf表示正无穷。
!redis-cli -h 127.0.0.1 zrangebyscore page_rank 8 10
!redis-cli -h 127.0.0.1 zrangebyscore page_rank 8 10 withscores
1) "zhihu.com"
2) "baidu.com"
3) "google.com"
1) "zhihu.com"
2) "8"
3) "baidu.com"
4) "9"
5) "google.com"
6) "10"
!redis-cli -h 127.0.0.1 zrangebyscore page_rank -inf +inf withscores
1) "zhihu.com"
2) "8"
3) "baidu.com"
4) "9"
5) "google.com"
6) "10"
!redis-cli -h 127.0.0.1 zrangebyscore page_rank +inf -inf withscores
(empty list or set)
根据范围移除元素列表
可以通过排名范围,ZREMRANGEBYRANK key start stop
也可以通过score范围来一次性移除多个元素 ZREMRANGEBYSCORE key min max
!redis-cli -h 127.0.0.1 zadd salary 2000 jack 5000 tom 3500 peter
!redis-cli -h 127.0.0.1 ZREMRANGEBYRANK salary 0 1
!redis-cli -h 127.0.0.1 ZRANGE salary 0 -1 WITHSCORES
(integer) 3
(integer) 2
1) "tom"
2) "5000"
!redis-cli -h 127.0.0.1 zadd salary 2000 jack 5000 tom 3500 peter
!redis-cli -h 127.0.0.1 ZREMRANGEBYSCORE salary 1500 3500
!redis-cli -h 127.0.0.1 ZRANGE salary 0 -1 WITHSCORES
(integer) 2
(integer) 2
1) "tom"
2) "5000"
归纳Sorted Set的主要命令
- ZADD:向指定Sorted Set中添加1个或多个member,时间复杂度O(Mlog(N)),M为添加的member数量,N为Sorted Set中的member数量
- ZREM:从指定Sorted Set中删除1个或多个member,时间复杂度O(Mlog(N)),M为删除的member数量,N为Sorted Set中的member数量
- ZCOUNT:返回指定Sorted Set中指定score范围内的member数量,时间复杂度:O(log(N))
- ZCARD:返回指定Sorted Set中的member数量,时间复杂度O(1)
- ZSCORE:返回指定Sorted Set中指定member的score,时间复杂度O(1)
- ZRANK/ZREVRANK:返回指定member在Sorted Set中的排名,ZRANK返回按升序排序的排名,ZREVRANK则返回按降序排序的排名。时间复杂度O(log(N))
- ZINCRBY:同INCRBY,对指定Sorted Set中的指定member的score进行自增,时间复杂度O(log(N))
慎用的Sorted Set相关命令:
- ZRANGE/ZREVRANGE:返回指定Sorted Set中指定排名范围内的所有member,ZRANGE为按score升序排序,ZREVRANGE为按score降序排序,时间复杂度O(log(N)+M),M为本次返回的member数
- ZRANGEBYSCORE/ZREVRANGEBYSCORE:返回指定Sorted Set中指定score范围内的所有member,返回结果以升序/降序排序,min和max可以指定为-inf和+inf,代表返回所有的member。时间复杂度O(log(N)+M)
- ZREMRANGEBYRANK/ZREMRANGEBYSCORE:移除Sorted Set中指定排名范围/指定score范围内的所有member。时间复杂度O(log(N)+M)
容器型数据结构的通用规则
list/set/hash/zset 这四种数据结构是容器型数据结构,它们共享下面两条通用规则:
- create if not exists
如果容器不存在,那就创建一个,再进行操作。比如 rpush 操作刚开始是没有列表的,Redis 就会自动创建一个,然后再 rpush 进去新元素。
- drop if no elements
如果容器里元素没有了,那么立即删除元素,释放内存。这意味着 lpop 操作到最后一个元素,列表就消失了…
Bitmap (位图)
Bitmap在Redis中不是一种实际的数据类型,而是一种将String作为Bitmap使用的方法。
可以理解为将String转换为bit数组。使用Bitmap来存储true/false类型的简单数据极为节省空间。
HyperLogLog
HyperLogLogs是一种主要用于数量统计的数据结构,它和Set类似,维护一个不可重复的String集合,但是HyperLogLogs并不维护具体的member内容,只维护member的个数。
也就是说,HyperLogLogs只能用于计算一个集合中不重复的元素数量,所以它比Set要节省很多内存空间。
GeoHash
redis 在 3.2 版本以后增加了地理位置 GEO 模块,
意味着我们可以使用 Redis 来实现摩拜单车「附近的 Mobike」、美团和饿了么「附近的餐馆」的功能