1. 预备知识
1.1 全局命令
keys * 查询所有的键,会遍历所有的键
dbsize 键总数,查询Redis内置键数量的变量
exists key 查询key是否存在
expire key seconds 设置键的存活时间
ttl key 查看键的剩余过期时间,返回值:大于0的整数,剩余过期时间;-1时,未设置过期时间;-2时,键不存在
type key 键的数据类型,键若不存在,返回null
1.2 数据结构和内部编码
Redis中每个数据类型都有多种内部编码实现如下图所示,
这样做有2个好处:
1)可以不断改进内部实现,而对外部的数据命令没有影响;
2)多种内部编码实现可以在不同场景下发挥各自的优势;
1.3单线程架构
Redis使用单线程架构,当多个客户端访问服务端时,将他们依次放入等待队列中,依次执行客户端的命令并返回结果。
Question:为什么单线程还这么快?
1)纯内存访问。数据都放在内存中;
2)非阻塞I/O。Redis使用epoll作为多路复用计数的实现,再加上Redis自身的事件处理
模型将 epoll中的链接,读写,关闭都转为事件,不在网络I/O上浪费太多时间;
3)单线程避免了线程切换和竞态产生的消耗。
2.字符串
字符串是Redis中最基本的数据类型,其他几种数据类型都是在string类型上构建的。字符串类型的值可以是字符串,数字,或者是二进制,但是大小不能超过512M。
2.1 命令
2.1.1 常用命令
1)设置值
set key value [ex seconds] [px milliseconds] [nx|xx]
例:set hello world ex 10 nx
ex: 为键设置秒级过期时间
px: 为键设置毫秒级过期时间
nx: 键必须不存在,才可以设置成功,用于添加
xx: 键必须存在,才可以设置成功,用于修改
类似的指令有:setnx和setex,用法如下:
setnx hello world 键必须不存在,才可以设置成功
setex hello world 键必须存在,才可以设置成功
对于setnx命令来说,只有一个客户端才能设置成功,所以可以用它实现分布式锁。
2)获取值
get key 获取值
例:get hello
如果获取的键不存在,返回 nil(空)
3)批量设置值
mset key value [key value ...]
例:mset a 1 b 2 c 3 d 4
4)批量获取值
mget key [...]
例:mget a b c d
如果获取的键不存在,返回 nil(空)
5)值自增
incr key
例:incr a
返回值有3中情况:
不是整数,返回错误;
是整数,返回自增后的结果;
键不存在,按照0自增,返回结果1;
类似的命令还有:decr(自减),incrby(自增指定数字),decrby(自减指定数字),
incrbyfloat(自增浮点数)
2.1.2 不常用命令
(1)追加值
append key value 向字符串尾部追加值
例:append hello java
(2)字符串长度
strlen key key占用的字节数
注意:每个汉字占3个字节
(3)设置并返回原值
getset key value
(4)设置指定位置的字符
setrange key offset value
例:setrange hello 0 t 将hello对应的值得第一个字符变为t
(5)获取部分字符串
getrange key start end 获取从start位置到end位置的字符
例:getrange hello 0 3
2.2 内部编码
字符串的内部编码有3种:
1. int: 8个字节的长整形
2. embstr: 小于等于39个字节的字符串
3. raw: 大于39个字节的字符串
Redis 会根据当前值得类型和长度决定使用哪种内部编码实现。
2.3 使用场景
(1)缓存功能
用户访问Web服务,先从缓存层(Redis)中请求目标数据,如果缓存层(Redis)中有目标数据,则直接返回数据,如果没有,则从存储层(MySQL)中获取数据,然后返回数据并将数据写入缓存层(Redis)
(2)计数。如视频的播放量等。
(3)共享session。
分布式web服务器将用户的信息(登录信息)保存在各自的服务器中, 而用户刷新后,由于负载均衡的考虑,负载均衡服务器有可能将用户请求映射到不同的服务器上,这样可能导致用户重新登录。这个问题是用户无法忍受的。
解决方法:将用户的信息(登录信息)集中保存在Redis中进行管理,用户每次更新或者查询信息直接从Redis中取集中获取。
(4)限速
为了限制短信接口不被频繁访问,比如限制用户一分钟内验证码最多可以输入5次。实际上是执行了下面命令:
set key value ex 60 nx
3.哈希
Redis中,哈希类型键值本身又是一个键值对结构。哈希类型中的映射关系叫做field-value,这里的value是指field对应的值,并不是键对应的值。
3.1 命令
(1)设置值
hset key field value
例:hset user:1 name tom
还有类似的 hsetnx命令
(2)获取值
hget key field
例:hget user:1 name
如果field不存在,返回nil
(3)删除field
hdel key field [field ...]
例:hdel user:1 name
(4)计算field个数
hlen key
例:hlen user:1
(5)批量设置或获取field-value
hmget key field [field ...]
例子:hmset user:1 name mike age 12 city tianjin
hmset key field value [field value ...]
例子:hmget user:1 name city
(6)判断field是否存在
hexists key field
例子:hexists user:1 name
(7)获取所有field
hkeys key
例子:hkeys user:1
(8)获取所有value
hvals key
例子:hvals user:1
(9)获取所有的field-value
hgetall key
例: hgetall user:1
(10)hincrby hincrbyfloat
hincrby key field
hincrbyfloat key field
(11)计算value的字符长度
hstrlen key field
例:hstrlen user:1 name
3.2 内部编码
ziplist(压缩列表)
当哈希类型元素个数小于hash-max-ziplist-entries配置(默认512个),同时所有值都小于hash-max-ziplist-value配置(默认64字节)时,Redis会使用ziplist作为哈希的内部实现,ziplist使用更加紧凑的结构实现多个元素的连续存储,所以比hashtable更节省内存。
hashtabe(哈希表)
当哈希类型无法满足ziplist条件时,Redis会使用hashtable作为内部实现。
3.3使用场景
使用hash类型存储用户登录数据。相对于字符串序列化缓存用户数据,hash类型更加直观,修改更加便捷。
hash类型是稀疏的,而关系型数据库是完全结构化的,例如hash类型每个键可以有不同的field,而关系型数据库添加的列,都要为他添加数据。
缓存用户数据的3种方式:
1)原生字符串类型:每个属性一个键。
set user:1 name Tom
set user:1 age 34
set user:1 sex male
优点:简单直观,每个属性都支持更新操作。
缺点:占用过多的键,占用内存较大,同时用户信息内聚性较差.
2)序列化字符串类型
set user:1 serialize(userInfo)
优点:简化编程,如果合理的使用序列化可以提高内存的使用效率。
缺点:序列化和反序列化有一定的开销;每次更新都要把所有数据取出来进行反 序列化,更新完成后再进行序列化到Redis中。
3)哈希类型:每个用户属性使用一对field-value,但是只用一个键保存。
hset user:1 name tom age 12 sex m
优点:简单直观,使用合理可以减少内存的使用。
缺点:要控制hash在ziplist和hashtable中切换,hashtable会消耗更多内存。
4.列表
列表是用来存储多个有序字符串,一个列表最多可以存储2^32-1个元素,可以在两端进行插入和删除,还可以获取指定范围内的元素,指定位置的元素,可以充当栈和队列的角色。
列表中的元素是有序的,且是可重复的。
4.1 命令
-
添加操作
(1)从右边插入元素rpush key value [value ...] 例: rpush MyList 1 2 3 可用 lrange 0 -1 来获取列表中的所有元素 例:lrange MyList 0 -1
(2)从左边插入元素 lpush key
value [value ...] 例:lpush MyList 1 2 3 可用 lrange 0 -1 来获取列表中的所有元素 例:lrange MyList 0 -1 (3)向某个元素前或者后插入元素 linsert key before|after pivot value 例:linsert MyList before b 100 在MyList中的b元素之前插入100
-
查找操作
(1)获取指定范围内的元素列表lrange key start end
(2)获取列表指定索引下标的元素
lindex key index 例:lindex MyList 2
(3)获取列表长度
llen key 例:llen MyList
-
删除操作
(1)从列表左侧弹出元素
lpop key 例:lpop MyList
(2)从列表右侧弹出元素
rpop key 例:rpop MyList
(3)删除指定元素
lrem key count value count > 0 从左到右,删除最多count个元素; count < 0 从右到左,删除最多count绝对值个元素; count = 0 删除所有元素。 例:lrem MyList 2 a
(4)按照索引范围修剪列表
ltrim key start end 例:ltrim MyList 1 3 只保留列表中第2到第4个元素
-
修改
lset key index newvalue 例:lset MyList 2 q
-
阻塞操作
阻塞式弹出操作:
blpop key [key …] timeout
brpop key [key …] timeout
1)列表为空例: blpop MyList: test 3 列表空,3秒后返回 blpop MyList: test 0 列表空,阻塞,直到添加了数据后返回
2)列表不为空
blpop MyList: test 3 客户端立即返回
注意:
如果有多个键,那么brpop从左到右遍历键,一旦 有一个键能弹出元素,返回客户端;
如果多个客户端对同一个键执行brpop,那么最先执行brpop命令的客户端可以获取到弹出的值。
4.2 内部编码
- ziplist
- linkedlist
4.3 使用场景
-
消息队列
Redis的 lpush + brpop可以实现阻塞队列,生产者从队列左边添加数据,消费者从右边阻塞式的取出数据。
-
文章列表
每篇文章使用哈希结构存储,如下:
hmset acticle:1 title xx timestamp 1476536196 content xxxx ... hmset acticle:n title yy timestamp 1476536198 content yyyy
向用户文章列表添加文章,user:{id}:articles作为文章的键:
lpush user :1:articles article:1 article3 ... lpush user: k:articles article: 5
分页获取文章列表:
articles = lrange user:1:articles 0 9 for article in {articles} hgetall{article}
缺点:
每次分页获取的文章个数较多,需要多次执行hgetall操作,可以考虑使用Pipeline批量获取;
分页获取文章列表时,lrange命令在列表两端性能较好,但是如果列表较大,获取列表中间范围的元素性能较差,可以考虑将表做二级拆分或者使用quicklist内部编码实现。
注意:
lpush + lpop = Stack(栈)
lpush + rpop = Queue(队列)
lpush + brpop = Message Queue(消息队列)
5.集合
集合也是用来保存多个字符串元素,他不允许元素重复,且元素无序,Redis支持集合的增删改查,还支持集合的交集,并集,差集。
5.1 命令
集合内操作
(1)添加元素
sadd key element [element ...]
返回结果为添加成功的元素个数
(2)删除 元素
srem key element[element ...]
(3)计算元素个数
scard key 直接访问内部元素个数变量,复杂度为O(1)
(4)判断元素是否在集合中
sismember key element 在集合中返回1,否则返回0
(5)随机从集合中返回指定个元素
srandmember key [count] 不写count默认为1,执行后元素还存储在集合
(6)从集合随机弹出元素
spop key 执行后元素从集合删除
(7)获取所有集合元素
smembers key
集合间操作
先设置两个集合:
sadd user:1:follow it music his sports
sadd user:2:follow it news ent sports
(1)集合的交集
sinter user:1:follow user:2:follow
(2)集合的并集
sunion user:1:follow user:2:follow
(3)集合的差集
sdiff user:1:follow user:2:follow
(4)保存并交差的结果
sinterstore user:1_2:inter user:1:follow user:2:follow
sunionstore user:1_2:union user:1:follow user:2:follow
5.2 内部编码
intset:元素个数较少且为整数时,用intset编码;
hashtable:元素个数超过512个,或者某个元素不是整数时,内部编码变为hashtable。
5.3 使用场景
典型的使用场景是标签。比如用户可能对音乐,运动等感兴趣,系统可以根据这些给用户推荐相关信息。
- 给用户添加标签
sadd user:1:tags tag1 tag2 tag3
sadd user:2:tags tag2 tag3 tag5
- 给标签添加用户
sadd tag1:users user:1
sadd tag2:users user:1 user:2
- 删除用户下的标签
srem user:1:tags tag1 tag2
- 删除标签下的用户
srem tag1:users:user:1
3和4尽量放在一个事务中进行
- 计算用户感兴趣的标签
sinter user:1:tags user:2:tags
6.有序集合
有序集合和集合类似,它可以根据元素的分数为元素排序,分数可以重复。
6.1 命令
集合内命令
(1)添加成员
zadd user:ranking 251 tom
zadd user:ranking 1 kris 91 mike 200 frank 220 tim 250 martin
(2)计算成员个数
zcard user:ranking
(3)计算某个成员的分数
zscore user:ranking tom
(4)计算某个成员的排名
zrank user:ranking tom 由低到高排名
zrevrank user:ranking tom 由高到低排名
(5)删除成员
zrem user:ranking tom
(6)增加成员的分数
zincrby user:ranking 9 tom
(7)返回指定排名范围的成员
zrange user:ranking 0 2 withscores 从低到高排名,并返回分数
zrevrange user:ranking 0 2 withscores 从高到低排名,并返回分数
(8)返回指定分数范围的成员
zrangebyscore user:ranking 200 thif withscores
zrevrangescore user:ranking 221 200 withscores
zrangebyscore user:ranking (200 +inf withscores
+inf和-inf 分别表示无限小和无限大
(9)返回指定分数范围的成员个数
zcount user:ranking 200 221
(10)删除指定排序内的升序元素
zremrangebyrank user:ranking 0 2
(11)删除指定分数范围的成员
zremrangebyrank user:ranking 0 2
集合间命令
(1)交集
zinterstore user:ranking:1_inter_2 2 user:ranking:1 user:ranking:2
zinterstore user:ranking: 1_inter_2 2 user:ranking:1 user:ranking:2 weights 1 0.5 aggregate max
(2)并集
zunionstore user:ranking:1_union_2 2 user:ranking:1 user:ranking:2
6.2 内部编码
ziplist(压缩列表):元素个数较少且每个元素较小时,内部编码为skiplist;
skiplist(跳跃表):元素个数超过128个,内部编码变为skiplist;
hashtable:当某个元素大于64字节时,内部编码变为hashtable。
6.3 使用场景
排行榜系统。如对用户上传的视频做排行榜。
添加用户的赞数
zadd user:ranking:2019_04_12 mike 3
zincrby user:ranking:2019_05_04 mike 2
取消用户的赞数
zrem user:ranking:2019_04_12 mike
展示获取赞数最多的10个用户
zrevrangebyrank user:ranking:2019_05_04 0 9
展示用户信息以及用户分数
hgetall user:info tom
zscore user:ranking:2020_03_21 tom
zrank user:ranking:2020_03_24 tom