文章目录
- 1. 前言
- 2. Redis的数据类型
- Redis中的键
- 2.1 String
- 2.2 Lists
- 常见用例
- Capped lists
- list的阻塞操作
- 自动创建和删除键
- 2.3 Hashes
- 2.4 Sets
- 常见用例
- 2.5 Sorted Sets
- 常见用例
- 2.6 Bitmaps
- 2.7 HyperLogLogs
- 常见用例
- 2.8 Streams
- 3. Redis过期
- 3.1 修改过期时间
- 3.2 Redis如何删除过期的键?
- 3.3 当键过期了,如何让主从保持一致
- 参考文献
1. 前言
Redis
不是简单的键值存储,它实际上是一个数据结构服务器,支持不同类型的值。在传统键值存储中,键和值都是字符串,而在Redis
中,值不仅限于简单的字符串,还可以容纳更复杂的数据结构。
2. Redis的数据类型
Redis中的键
Redis的键是二进制安全的,这意味着您可以使用任何二进制序列作为键,从“ foo”之类的字符串到JPEG文件的内容。 空字符串也是有效的键。
需要注意的是:
- 尽量不要使用过长的键。例如,一个1024字节的键会占用较大的内存,而且在比较键的过程中代价更高。更好的方法是,将该值hash之后再作为键。
- 尽量不要使用过短的键。尽管较短的键占用更少的内存,但是可读性更差。例如: “user:1000:followers” 比 “u1000flw” 作为键更合适。
- 键的模式最好保持一致。 例如,“ object-type:id”是一个好主意,例如“ user:1000”。 点或破折号通常用于多词字段,例如“ comment: 1234:reply.to”或“ comment: 1234:reply-to”。
-
键的大小不能超过512 MB
。
2.1 String
Redis
中最简单的数据类型就是 String
了,而它也是 Memcached
中唯一的数据类型。
由于Redis
中的键都是 String
类型的,当值也是 String
时,其实就是将一个 String
映射到另一个 String
。值的最大值不能超过512MB
。String
类型是二进制安全的,表示redis
的 string
可以包含任何数据。如数字,字符串,jpg图片或者序列化的对象。
# 如果原来的key已经存在了,那么再使用set会给key设置一个新的值。
set 键 值
get 键
# 该键的值加1(原子性递增),将原键的值解析为一个整数,然后加1,将加1后的结果SET为该键的新值
incr 键
# 在原来键的值得基础上,追加数据
append 键 "append value"
# 获取字符串的值得分片, [start, end]
getrange 键 start end
# 设置字符串的值的分片,从索引为start开始 用新值开始替换
setrange 键 start 新值
INCR
, DECR
, INCRBY
是原子性操作
,也就是说不会类似多线程的现象。例如,多线程中,A和B同时让共享变量x(值为10)加1,结果可能会是11,而不是12,而两个 redis-cli
同时使用incr
对某键加1,最终的结果一定是12。
1条命令 SET
或 GET
多个键的值。
mset key1 value1 key2 value2 key3 value3
mget key1 key2 key3
# EXISTS返回1或者0作为信号,表示该键是否存在
exists 键
# DEL删除指定键和其值
del 键
mget
的返回值是一个数组。
2.2 Lists
Redis
中的 lists
是通过双端链表实现的。这意味着不管 list
中的元素有多少,在列头和列尾添加一个新元素都能在常数时间,即O(1),内完成。lists
中的元素都是字符串。
1个 Redis list
的最大长度限制为 232 - 1
之所以使用链表实现,就是因为常常需要在较短时间内从很长很长的 list
中添加或删除元素(头、尾都行)。
还有一个优点是,Redis
中的 Lists
在常数时间,即O(1),内获取固定长度的list
。
但是缺点是获取元素比数组实现的 list
要慢,尤其是获取中间的元素,这是一个O(N)操作。
# 添加一个新元素或多个元素到列头
lpush list名 值
lpush list名 值1 值2 值3
# 添加一个新元素或多个元素到列尾
rpush list名 值
rpush list名 值1 值2 值3
# 从列表中获取一定范围的数据,[start, end]
lrange list名 start end
# 返回list中的所有元素
lrange list名 0 -1
# 弹出(获取+删除)list中的元素
# 从列头删除1个元素
lpop list名
# 从列尾删除1个元素
rpop list名
需要注意的是LRANGE
命令中,[start, end]
区间两边都是闭合的。
使用LPOP
或者RPOP
删除list中元素,若list已经为空,则返回值是NULL
。
常见用例
- 记录用户发布到社交网络上的最近更新。
-
生产者-消费者模式
。生产者生产消息并写入list
中,消费者消费消息。
举个例子,假设你的微博主页显示了你发布的一些最新照片,并且你希望主页的访问速度要尽可能的快。利用 Redis
中的 list
来实现:
- 每次用户发布新照片时,我们都会使用
LPUSH
将其照片id
添加到list
中。 - 当用户访问主页时,我们使用
LRANGE 0 9
来获取最新发布的10个照片。
Capped lists
在许多情况,我们只是想使用Redis
中的list
来存储最新的数据,不管这些数据是社交网络更新数据、日志数据还是其它什么数据。
只记录最新的N个数据,使用LTRIM
丢弃所有旧的数据。LTRIM
命令指定一个范围,这个范围之外的元素都会被删掉。
# 向某list中写入5个元素
rpush list名 1 2 3 4 5
# 设置最新值的范围是[0, 2],两边都是闭合的,所以有3个元素
ltrim list名 0 2
# 展示该list中的所有元素
lrange list名 0 -1
# Redis中的Sorted sets
在Redis中获取集合中间某部分的元素是很常见的,这就需要使用Sorted sets。
list的阻塞操作
用Redis
中的list
实现队列非常合适,这是因为list
有一个特性:阻塞操作,并且通常用作进程间通信系统时的阻塞。
- 生产者调用
LPUSH
命令推送一些数据到list中 - 消费者调用
RPOP
从该list
中抽取/处理这些数据
但是,有时list
可能为空(没有任何要处理的数据),因此RPOP
会返回NULL
。 在这种情况下,消费者被迫等待一段时间,然后使用RPOP
重试。这被称为轮询
,在这种情形有几个缺点:
-
Redis
和clients
会处理这些读命令,但这并有什么卵用,因为压根就没有数据了。 - 由于添加了数据处理的延迟,当1个消费者获取到一个
NULL
后,会缩短延迟的时间,这样会导致更多无用命令的调用。
Redis
使用BRPOP
和BLPOP
可以解决上述问题。BRPOP
和BLPOP
是RPOP
和LPOP
的阻塞版本,当list
是空的之后,会阻塞获取数据。仅当将新元素添加到list
中或达到用户指定的超时时间了,它们才会返回结果给调用方。
可以同时等待多个list
# 意思是等待该list中的元素,但如果5秒钟后没有可用元素,则返回
brpop list名 5
# 等待时间设置为0s,表示永远等待
brpop list名 0
-
clients
按照顺序享受服务。当该list
中有可用元素之后,会优先服务第一个阻塞的 client。 - 返回值与
RPOP
相比有所不同。BRPOP
的返回值是一个数组,由键名和值,因为BRPOP
和BLPOP
能够同时等待多个list,所以需要指明list
名。 - 如果超时了,则会返回
NULL
。
自动创建和删除键
我们不必去创建空list
,只需要向该list
中添加元素即可。我们也不用移除空list
,这些都是Redis
自己的工作。Streams
, Sets, Sorted Sets
和 Hashes
也是这样。
- 当我们添加元素到一个
聚合数据类型
(Streams
,Sets
,Sorted Sets
和Hashes
),如果该键并不存在,则Redis
会先建一个空的该聚合数据类型作为键,然后将元素添加进去。 - 当我们从一个聚合数据类型中删除了最后一个元素之后,
Redis
会自动删除该键。Stream
数据类型是个例外。 - 对一个空的
list
调用只读命令(如LLEN
,返回list
的长度)或者写命令移除元素时,效果如下。
2.3 Hashes
Redis
哈希是字符串字段和字符串值之间的映射,1个 hash
常用来表示1个对象(可以有许多 field
和 对应的 value
)下面的例子中,键是"user:1000",剩下是一对一对的字段和值。
每一个hash
可以存储 232 -1 个 field
/value
对。
# 为某hash键设置字段的值
hmset 键 field1 value1 field2 value2
# 获取该hash键的某一个字段的值
hget 键 field1
hget 键 field2
# 获取该hash键的多个字段的值,返回的是一个数组
hmget 键 field1 field3 field2
# 获取该hash键的所有字段和其值
hgetall 键
# 有些命令可以单独在hash键的某字段上进行操作,如HINCRBY(理解为h-incr-by)
hincrby 键 field1 10 # 让该hash键的field1字段的值加10
需要注意的是
,比较值比较小的hashes使用了特殊的编码方式,使得其在内存中的存储效率更高。
2.4 Sets
集合类型也是用来保存多个字符串的元素,可以理解成python中的set。具有以下特性:
- 没有重复的元素
- 集合中的元素是无序的,不能通过索引下标获取元素
- 支持集合间的操作,可以取多个集合取交集、并集、差集
Redis
中的 Sets
是字符串的无序集合。它支持集合间的操作(交集、并集、差集)和成员检查等操作。
1个 Redis set
的最大长度限制为 232 - 1
常见用例
- 跟踪指定博客的所有IP地址,会自动去重。
Redis sets
可以很好的表示某些关系。用set
来表示每一个tag
- 可以从 set 中获取随机元素。
# 向set中添加一个或多个新元素
sadd set名 value1 value2 value3
# 查看某set键中的所有元素
smembers set名
# 判断某元素是否在该set中
sismember set名 value
# 求交集
sinter set名1 set名2 set名3
# 求并集
sunion set名1 set名2 set名3
sunionstore 新set名 set名1 set名2 # 将几个set的并集存到一个新的set
# 求差集
# 弹出集合中随机一个元素
spop set名
# 获取集合中随机一个元素但是不删除
srandmember set名
# 集合的基数,即集合中元素数量
scard set名
例如,打标签。
# 表示给id为1000的新闻文章打了标签号为1,2,5,77的标签。
sadd news:1000:tags 1 2 5 77
# 也可以反过来,记录该标签标记了哪些新闻文章
sadd tag:1:news 1000
sadd tag:2:news 1000
sadd tag:5:news 1000
sadd tag:77news 1000
2.5 Sorted Sets
Sorted Sets
即有序的set
。这个有序是通过给每个元素关联一个名为score
的浮点数来实现的,score
是可以相同的,也就是score
可以重复。
排序规则如下:
- A和B是两个不同score的元素,如果A.score > B.score,则A > B
- 如果A和B的score相同,比较A和B的字符串大小,若A的字符串大于B,则A > B
Sorted set
的特点可以简单总结为:添加、删除、更新快,元素有序,成员检查快,获取中间元素快。
更新分数只需要zadd
为该元素设置新的score
即可,但是在更新score
之后,Sorted set
需要O(log(N)
的时间复杂度进行重新排序。因此,当有大量更新时,使用sorted set
是一个合适的选择。
# 给某Sorted set添加一个或多个元素
zadd zset名 score1 value1 score2 value2 score3 value3
# 查看某Sorted set中的所有元素,分数从小到大
zrange zset名 0 -1
# 查看某Sorted set中的所有元素,分数从大到小
zrevrange zset名 0 -1
# 使用withscores返回元素的同时也返回其分数
zrange zset名 0 -1 withscores
############# 范围操作 #############################
# 返回score在[负无穷,100] 左右区间都包括,这个范围的元素
zrangebyscore zset名 -inf 100
zrangebyscore zset名 100 +inf # [100, +inf]
# 移除某范围[start, end] 左右区间都包括,的元素,并返回
zremrangebyscore zset名 start end
# 获得值为value的元素在该sorted set中的排名
zrank zset名 value
zrevrank zset名 value # 逆序的排名
############# 按字典顺序(字符串大小顺序)获取范围 #############################
# 当分数都相同时,按照字符串的大小顺序有序。
zrangebylex zset名 min max
zrevrangebylex zset名 min max
zremrangebylex zset名 min max
zlexcount zset名 min max
常见用例
- 大型在线游戏的排行榜。使用
ZADD
可以很容易地更新用户的score
,还可以用ZRANGE
获取排名前几的用户,用ZRANK
获取某用户的排名。 - 索引数据。比如,我有很多代表用户的
hash
,那么可以将用户的ID作为Sorted set
的值,用户的年龄作为Sorted set
的score
。然后使用ZRANGEBYSCORE
就可以快速获取指定年龄范围的用户了。
2.6 Bitmaps
位图。并不是一种实际的数据类型,而是在String类型上定义的一组面向位的操作。
位操作分为两类:常数时间的单个位操作(如将一个位设置为1或0或获取其值),以及对一组位的操作,例如,计算给定位范围内位为1或者0的数量 (例如,人口计数)。
位图的最大优点之一是,它们在存储信息时通常可以节省大量空间。 例如,在以增量用户ID表示不同用户的系统中,仅使用512 MB内存就可以记住40亿用户的一位信息(例如,知道用户是否要接收新闻通讯)。
如果设置或者获取的位数超过了当前String
的长度,那么Redis
会自动扩大该String
的长度。
########### 单个位上的操作 #######################
setbit 某key 第几位 值 # 例如: setbit mykey 10 1. 意思第10位设为1
# 这是该key第10位的值为1
setbit 某key 10 1
# 获取第10位的值
getbit 某key 10
# 因为第11位没有设置,所以是0
getbit 某key 11
########### 多个位上的操作 #######################
# 在不同String上,实现按位运算,如AND OR XOR NOT操作
# 对一个或多个保存二进制位的字符串key进行位元操作,并将结果保存到destkey上。
bitop 操作 destkey key1 key2 key3
# 统计为1的位的数量
bitcount 某key
# 查找第一为0或者为1的位
bitpos 某key 0或者1
2.7 HyperLogLogs
HLL
是一种概率数据结构,旨在对唯一事物进行计数。
在Redis
中HLL
被编码为Redis
字符串,所以我们可以用GET
命令来序列化HLL
,可以用SET
命令来反序列化成HLL
。
从概念上讲,HLL API
有些类似Set
。 因为它跟 set
一样不会含有重复元素。
# 将新元素添加到该hll键中
pfadd hll键 value1 value2 value3
# 该hll键的值的数量
pfcount hll键
常见用例
- 统计每天用户在搜索表单中执行的唯一查询。
2.8 Streams
Stream
是Redis5.0
之后引入的新数据类型。提供抽象日志数据的append-only
数据结构。从概念上讲,因为Redis
是流式传输在内存中表示的抽象数据类型,所以它们实现了更强大的操作,以克服日志文件本身的限制。
consumer group
: 允许一组客户端合作使用同一消息流的不同部分。
Redis
中的键都有存活时间。这与Redis
中的数据结构无关。我们可以为键设置一个过期时间,当超过这个过期时间会自动删除这个键和其值,就像使用DEL
删除了这个键一样。
默认永不过期
。
- 过期时间的精度可以是s或者ms。
- 只要超过过期时间1ms则认为过期。
- 过期信息是复制并以时间戳格式持久化在磁盘中的,所以就算Redis挂了,只要重启了,仍然会在预计的时间过期。
设置过期时间,过期时间的默认单位是S
# 设置过期时间
set 键 值
expire 键 过期时间的s值
pexpire 键 过期时间的ms值
或者使用SET命令的ex选项
set 键 值 ex 过期时间的s值
# 查看某键还有多少时间过期
ttl 键 # s
pttl 键 # ms
# 让某键用不过期
persist 键
3.1 修改过期时间
使用DEL,SET,GETSET,PERSIST, EXPIRE命令可以修改过期时间。不使用DEL或者新值替换旧值,则过期时间不改变(除了这期间的时间消耗)。例如,使用INCR命令让一个键的值加1,或者向一个list中push一个元素,或者修改一个hash中某字段的值。
使用RENAME修改了某个键的名字,其过期时间仍然不改变(除了这期间的时间消耗)
设置一个负数的过期时间,则Redis会直接删除该key。
需要注意的是,如果将一个Redis实例的RDB文件复制给另一个Redis实例,必须要确保这两台计算机的本地时间一致,否则,可能存在某些键在新的这个Redis实例中直接就过期了。
3.2 Redis如何删除过期的键?
两种方式:
- 被动删除。当 client 获取已过期的键时,删除这个键。
- 主动删除。仅仅被动删除肯定不够,因为有的键可能一直都会再被访问了,就一直占用内存。因此,Redis每10秒执行如下操作1次:
- 从设置了过期时间的键中随机抽取20个键,判断是否过期。
- 删除所有发现的已过期的键。
- 如果超过25%的键已过期,请从步骤1重新开始。
3.3 当键过期了,如何让主从保持一致
当键过期时,会将对应的DEL操作写入AOF文件中,这样在该master 和 slave中不会出现不一致的情况。
参考文献[1] Redis 官方文档