Redis学习[1] ——基本概念和数据类型
一、Redis基础概念
1.1 Redis是什么,有什么特点?
Redis是一个基于**内存的数据库,因此读写速度非常快**,常用作缓存、消息队列、分布式锁和键值存储数据库。支持多种数据结构:字符串、哈希表、列表、集合、有序集合、位图等。内置了事务、持久化、Lua脚本、多种集群方案(主从复制模式、哨兵模式、切片集群模式)、发布/订阅模式、内存淘汰机制、过期删除机制等。
具有以下特点:
- 基于内存:数据存储在内存中,具有快速的读写速度;
- 持久性:可以通过快照和日志文件的持久化来保证数据的持久性,防止数据丢失;
- 多数据结构:支持丰富的数据结构;
- 原子性操作:支持原子性操作,能够保证一个操作是原子的,要么执行成功,要么不执行;
- 分布式:提供了分布式特性,可以将数据分布在多个节点上。
1.2 为什么要使用Redis?
Redis 具备「高性能」和「高并发」两种特性。
-
Redis读写非常快速(高性能),对于需要频繁读写的数据,与一般MySQL数据库相比(从硬盘中读取),Redis性能优势非常明显。将Redis作为缓存,存储一些MySQL数据库中频繁被使用的数据,可以减轻对MySQL等持久性数据库的压力。
-
Redis可以很好地处理高并发请求(高并发),尤其是需要快速响应的场景。这也是它的高性能而决定的。
因此,Redis更适合处理高速、高并发的数据访问,以及需要复杂数据结构和功能的场景。在实际应用中,很多系统同时使用MySQL和Redis,其中Redis作为缓存大幅提高响应速度,减轻数据库的压力。
二、Redis 数据类型
2.1 Redis有哪些数据类型?
Redis提供了五种常见的数据类型:String(字符串)、Hash(哈希表)、List(列表)、Set(集合)、Zset(有序集合)
后面又增加了四种数据类型:BitMap(位图)、HyperLogLog(基数统计)、GEO(地理信息)、Stream(流)
2.2 String 类型
2.2.1 基本介绍
String
是最基本的key-value结构,value可以不仅仅是字符串,也可以是数字,value可以容纳的数据长度为512M
。
2.2.2 底层实现
String
类型的底层实现是int
和SDS
(简单动态字符串数据类型)。
SDS
和C语言字符串不同,它比C语言的字符串适用范围更广、效率更高、也更安全。具有以下特点:
SDS
不仅可以保存文本数据,还可以保存二进制数据。SDS
所有的API都会以处理二进制的方式来处理SDS存放在buf[]
数组里的数据,所以当然可以直接保存图片、音频、视频、压缩文件等二进制数据。SDS
获取字符串长度的时间复杂度是O(1)。SDS
维护了一个len
属性来记录自身长度,而不是通过尾部加’\0’的方式。(这点和std::string
一致)SDS
安全的,拼接字符串不会造成缓冲区溢出。SDS在拼接字符串会检查SDS空间是否满足,不满足则会扩容,不会导致缓冲区溢出。
知道了底层数据类型,来看看String类型是如何利用
int
和SDS
对数据进行构建对象的。
String
类型对象的内部编码(encoding)有三种方式:int、raw、embstr。
情况1:如果String
对象保存的是一个整数值,且这个整数值可以用long
类型表示。
字符串对象会将该整数值保存在ptr
属性中,并将字符串对象的编码类型(encoding)设置为int
。
情况2:如果String
对象保存的是一个字符串,且这个字符串的长度小于等于32字节。
String
对象会选择使用SDS
来保存这个字符串,并将编码类型设置为embstr
。
情况3:如果String
对象保存的是一个字符串,且这个字符串的长度大于32字节。(注意和上面的区别,SDS和redisObject不连续)
String
对象会选择使用SDS
来保存这个字符串,且用ptr指向这个SDS
,并将编码类型设置为embstr
。
embstr编码和raw编码的边界在redis不同版本是不一样的:
- redis 2.+ 是 32 字节
- redis 3.0-4.0 是 39 字节
- redis 5.0 是44字节
embstr
和raw
编码方式都会使用SDS
类型,它们之间的区别就在于字符串的长度。
1️⃣ 由于embstr
中字符串长度较短,只需要通过一次内存分配函数来分配一个**连续的内存空间**来保存redisObject
和SDS
。
2️⃣ 而raw编码会调用两次内存分配函数,分配两块空间分别保存redisObject
和SDS
。
为什么要单独设置一个
embstr
编码方式?
embstr
编码将创建字符串对象所需的内存分配次数从raw
编码的两次降低为一次;- 释放
embstr
编码的字符串对象同样只需要调用一次内存释放函数;- 因为
embstr
编码的字符串对象的所有数据都保存在一块连续的内存里面可以更好的利用 CPU 缓存提升性能。
embstr
编码方式缺点?如果字符串的长度增加需要重新分配内存时,整个redisObject和sds都需要重新分配空间,所以embstr编码的字符串对象实际上是只读的,redis没有为embstr编码的字符串对象编写任何相应的修改程序。当我们对embstr编码的字符串对象执行任何修改命令(例如append)时,程序会先将对象的编码从embstr转换成raw,然后再执行修改命令。
2.2.3 常用指令
普通字符串的基本操作:
# 设置key-value
> SET name magic
OK
# 根据key获得value
> GET name
"magic"
# 判断某个key是否存在
> EXISTS name
(integer) 1
# 返回key所存储的字符串值的长度
> STRLEN name
(integer) 5
# 删除某个key对应的值
> DEL name
(integer) 1
字符串批量操作 :
# 批量设置key-value
> MSET key1 value1 key2 value2
OK
# 批量获取多个key对应的value
> MGET key1 key2
1) "value1"
2) "value2"
当value为整数时,可以对其使用以下操作:
# 设置 key-value 类型的值
> SET number 0
OK
# 将key对应的value + 1
> INCR number
(integer) 1
# 将key对应的value + 10
> INCRBY number 10
(integer) 11
# 将key对应的value - 1
> DECR number
(integer) 10
# 将key对应的value - 10
> DECRBY number 10
(integer) 0
设置过期时间相关操作(默认为永不过期):
# 设置 key 在 60 秒后过期(为已经存在的key)
> EXPIRE name 60
(integer) 1
# 查看数据还有多久过期
> TTL name
(integer) 58
# 创建key-value时就指定过期时间
> SET key value EX 60
OK
> SETEX key 60 value
OK
不存在就插入:
# 不存在就插入(not exists),常用来实现分布式锁
> SET key value NX # 或者 SETNX key value
(integer) 1
2.2.4 应用场景
-
缓存对象
- 直接缓存这个对象的JSON,例如:
SET user:1 '{"name":"xiaolin", "age":18}'
。 - 利用MSET分离式存储,例如:
MSET user:1:name xiaolin user:1:age 18
。
- 直接缓存这个对象的JSON,例如:
-
常规计数
Redis处理命令是单线程,所以命令的执行是原子的。
String
数据类型适合计数场景,比如计算访问次数、点赞数、转发数、库存量灯。例如:# 初始化文章的阅读量 > SET article:readcount:1001 0 OK > INCR article:readcount:1001 # 阅读量+1
-
分布式锁
SET 命令有个 NX 参数可以实现**「key不存在才插入」**,可以用它来实现分布式锁:
- 如果 key 不存在,则显示插入成功,可以用来表示加锁成功;
- 如果 key 存在,则会显示插入失败,可以用来表示加锁失败。
一般而言,还需要对分布式锁加上过期时间,如下:
SET lock_key unique_value NX PX 10000 ## lock_key 就是 key 键; ## unique_value 是客户端生成的唯一的标识; ## NX 代表只在 lock_key 不存在时,才对 lock_key 进行设置操作;(没有被锁时才能加锁) ## PX 10000 表示设置 lock_key 的过期时间为 10s,这是为了避免客户端发生异常而无法释放锁。
解锁的过程就是将 lock_key 键删除,但不能乱删,要保证执行操作的客户端就是加锁的客户端,即先判断先判断锁的 unique_value 是否为加锁客户端。因此,解锁是两个步骤,无法保证原子性。因此Redis需要引入
Lua
脚本来实现原子操作,解锁的Lua
脚本如下:// 释放锁时,先比较 unique_value 是否相等,避免锁的误释放 if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end
用户通常需要编写适当的 Lua 脚本并通过 Redis 客户端发送到 Redis 服务器执行。这是因为分布式锁的解除需要原子操作,而 Lua 脚本可以保证在 Redis 中的操作是原子的。发送到Redis服务器方法如下:
## 后面的1是指有一个键 > EVAL "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end" 1 lock_key unique_value
不过,为了简化这一过程,一些 Redis 客户端库已经内置了处理分布式锁的功能。这些库通常会封装 Lua 脚本的编写和执行过程,使用户可以更方便地使用分布式锁。
-
共享Session信息
通常我们在开发后台管理系统时,会使用 Session 来保存用户的会话(登录)状态,这些 Session 信息会被保存在服务器端,但这只适用于单系统应用,如果是分布式系统此模式将不再适用:例如用户一的 Session 信息被存储在服务器一,但第二次访问时用户一被分配到服务器二,这个时候服务器并没有用户一的 Session 信息,就会出现需要重复登录的问题。
因此,我们需要借助 Redis 对这些 Session 信息进行统一的存储和管理,这样无论请求发送到那台服务器,服务器都会去同一个 Redis 获取相关的 Session 信息,这样就解决了分布式系统下 Session 存储的问题。
2.3 List 类型
2.3.1 基本介绍
List
列表是简单的字符串列表,按照插入的顺序排序,可以从头部或尾部向List列表添加元素。
列表的最大长度为 2^32 - 1
,也即每个列表支持超过 40 亿
个元素。
2.3.2 底层实现
List
类型的底层结构是由双向链表(linkedList)或压缩列表(zipList)实现的:
-
如果列表的元素小于512个(默认值,可由
list-max-ziplist-entries
配置),且列表中的值都小于64字节(默认值,可由list-max-ziplist-value
配置),Redis会使用压缩列表作为List的底层数据结构。 -
不满足上述条件,则会使用双向链表作为List的底层结构。
但是在 Redis 3.2 版本之后,List 数据类型底层数据结构就只由 quicklist 实现了,替代了双向链表和压缩列表。
什么是压缩列表(zipList)?
压缩列表**是一种特殊的双向链表,被设计成一种内存紧凑型的数据结构,占用一块连续的内存空间,不仅可以利用 CPU 缓存,而且会针对不同长度的数据,进行相应编码**,这种方法可以有效地节省内存开销。zipList整体的结构布局如下图:
zlbytes
: 32 位无符号整型,记录 ziplist 整个结构体的占用空间大小。当需要修改 ziplist 时候不需要遍历即可知道其本身的大小。 这个 SDS 中记录字符串的长度有相似之处。zltail
: 32 位无符号整型, 记录整个 ziplist 中最后一个 entry 的偏移量。所以在尾部进行 POP 操作时候也不需要先遍历一次。zllen
: 16 位无符号整型, 记录entry
的数量, 只能表示 2^16。但是 Redis 作了特殊的处理:当实体数超过 2^16 ,该值被固定为 2^16 - 1。 所以这种时候要知道所有实体的数量就必须要遍历整个结构了。entry
: 真正存数据的结构。包含了前一个entry
的长度prelen
(用于从后向前遍历),encoding
编码形式,和具体编码后的数据。zlend
: 8 位无符号整型, 固定为 255 (0xFF)。为 ziplist 的结束标识。
😀优点:
- 节省内存:存储在一个内存块中,剩下了两个指针的内存,减少内存碎片;
- 提高缓存命中率:连续空间,缓存可以高效访问;
- 快速序列化/反序列化:数据结构紧凑,序列化和反序列化速度较快。
☹缺点:
- 插入删除操作效率低:连续的内存块,插入或删除操作需要移动大量元素;
- 扩展性差:只适用于小规模数据集,规模越大,每次插入或删除性能越差;
- 不适合存储复杂结构:适合存储简单的数据结构。
什么是快速列表(quickList)?
快速列表实际上是双向链表和压缩列表的混合体,它将整体的数据分成多个段,每个段用一个压缩列表来表示,不同段之间通过双向指针串起来,最终形成一个双向链表的形式。
2.3.3 常用指令
常用的命令即增删改查
# LPUSH 将一个或多个值value插入到key列表的表头(最左边),最后的值在最前面,如果不存在key列表,则创建
LPUSH key value1 value2 (...)
# RPUSH 插入到表尾(最右边)
RPUSH key value1 value2 (...)
# LPOP 移除并返回key列表表头元素
LPOP key
# RPOP 移除并返回key列表表尾元素
RPOP key
# LRANGE 获取列表key中指定区间内的元素
LRANGE key start stop
# BLPOP 从key列表表头弹出一个元素,没有就阻塞timeout秒,如果timeout=0则一直阻塞
BLPOP key [key ...] timeout
# BRPOP 从key列表表尾弹出一个元素,没有就阻塞timeout秒,如果timeout=0则一直阻塞
BRPOP key [key ...] timeout
2.3.4 应用场景
-
消息队列
消息队列在存取消息时,必须要满足三个需求:消息顺序保序、处理重复的消息和保证消息可靠性。
Redis中
List
类型和Stream
类型可以满足。1️⃣ 如何满足消息顺序保存?
List 本身就是按先进先出的顺序对数据进行存取的,可以通过
LPUSH + BRPOP
(或RPUSH + BLPOP
)组合来实现消息的存取。使用
BRPOP
或者BLPOP
的原因是能够在当List中不存在消息时阻塞,直到有新的数据写入队列,再开始读取新数据。而不是一直循环读取,节省CPU开销。2️⃣ 如何处理重复的消息?
通过为每个消息添加一个全局ID,消费者(从消息队列中取消息的角色)程序可以对比收到的消息ID和记录的消息ID,判断是否为重复的消息。
3️⃣ 如何保证消息可靠性?
当消费者程序从 List 中读取一条消息后,List 就不会再留存这条消息了。所以,如果消费者程序在处理消息的过程出现了故障或宕机,就会导致消息没有处理完成,那么,消费者程序再次启动后,就没法再次从 List 中读取消息了。
为了留存消息,List 类型提供了
BRPOPLPUSH
命令,这个命令的作用是让消费者程序从一个 List 中读取消息,同时,Redis 会把这个消息再插入到另一个 List(可以叫作备份 List)留存。List 作为消息队列有什么缺陷?
List 不支持多个消费者消费同一条消息,因为一旦消费者拉取一条消息后,这条消息就从 List 中删除了,无法被其它消费者再次消费。
要实现一条消息可以被多个消费者消费,那么就要将多个消费者组成一个消费组,使得多个消费者可以消费同一条消息,但是 List 类型并不支持消费组的实现。
这就要说起 Redis 从 5.0 版本开始提供的
Stream
数据类型了,Stream
同样能够满足消息队列的三大需求,而且它还支持「消费组」形式的消息读取。
2.4 Hash 类型
2.4.1 基本介绍
Hash是一个键值对集合,即value=[{field1,value1},...{fieldN,valueN}]
,就是说k-v数据库的v是一个键值对的集合。Hash很适合用来存储对象。
Hash 与 String 对象的区别如下图所示:
2.4.2 内部实现
Hash类型的底层是由**压缩列表或哈希表**实现的:
- 如果哈希类型元素个数小于
512
个,所有值小于64
字节的话,Redis会使用压缩列表作为 Hash 类型的底层数据结构; - 如果哈希类型元素不满足上面条件,Redis 会使用哈希表作为 Hash 类型的 底层数据结构。
在 Redis 7.0 中,压缩列表(ziplist)数据结构已经废弃了,交由 listpack 数据结构来实现了。
2.4.3 常用指令
# 向Hash表key中添加filed-value对
> HSET key field value
(integer) 1
# 获取Hash表key中的field键对应的值
> HGET key field
"value"
# 在一个哈希表key中存储多个键值对
> HMSET key field value [field value...]
# 批量获取哈希表key中多个field键值
> HMGET key field [field ...]
# 删除哈希表key中的field键值
> HDEL key field [field ...]
# 返回哈希表key中field的数量
HLEN key
# 返回哈希表key中所有的键值
HGETALL key
# 为哈希表key中field键的值加上增量n
HINCRBY key field
2.4.4 应用场景
-
缓存对象
Hash 类型的 (key,field, value) 的结构与对象的(对象id, 属性, 值)的结构相似,也可以用来存储对象。如下图所示:
String + Json也是存储对象的一种方式,那么存储对象时,到底用 String + json 还是用 Hash 呢?
一般对象用 String + Json 存储,对象中某些频繁变化的属性可以考虑抽出来用 Hash 类型存储。
-
购物车
以用户 id 为 key,商品 id 为 field,商品数量为 value,恰好构成了购物车的3个要素,如下图所示。
2.5 Set 类型
2.5.1 基本介绍
Set类型是一个无序且唯一的键值集合,它的存储顺序不会按照插入的先后顺序进行存储。一个集合最多可以存储 2^32-1
个元素。概念和数学中个的集合基本类似,可以交集,并集,差集等等,所以 Set 类型**除了支持集合内的增删改查,同时还支持多个集合取交集、并集、差集。**
Set和List的区别:
- List可以存储重复的元素,Set只能存储非重复的元素;
- List是按照元素插入的先后顺序来存储元素的,Set是无序方式存储元素的。
2.5.2 内部实现
Set类型的底层数据结构是由**哈希表或整数集合**实现的:
- 如果集合中的元素都是整数且元素个数小于
512
(默认值,set-maxintset-entries
配置)个,Redis 会使用整数集合作为 Set 类型的底层数据结构; - 如果集合中的元素不满足上面条件,则 Redis 使用哈希表作为 Set 类型的底层数据结构。
2.5.3 常用指令
常规操作指令:
# 向集合key中加入元素number
SADD key number
# 从集合key中删除元素number
SREM key number
# 判断member元素是否存在于集合key中
SISMEMBER key member
# 从集合key中随机选出count个元素,元素不从key中删除
SRANDMEMBER key [count]
# 从集合key中随机选出count个元素,元素从key中删除
SPOP key [count]
运算操作指令:
# 交集运算
SINTER key [key ...]
# 将交集结果存入新集合destination中
SINTERSTORE destination key [key ...]
# 并集运算
SUNION key [key ...]
# 将并集结果存入新集合destination中
SUNIONSTORE destination key [key ...]
# 差集运算
SDIFF key [key ...]
# 将差集结果存入新集合destination中
SDIFFSTORE destination key [key ...]
2.5.4 应用场景
Set 类型比较适合用来数据去重和保障数据的唯一性,还可以用来统计多个集合的交集、错集和并集等.
存在一个风险:Set 的差集、并集和交集的计算复杂度较高,在数据量较大的情况下,如果直接执行这些计算,会导致 Redis 实例阻塞。
-
点赞、抽奖活动
Set 类型可以保证一个用户只能点一个赞,只能中奖一次。
-
共同关注
利用Set的取交集运算。
2.6 Zset 类型
2.6.1 基本介绍
Zset 类型(有序集合类型)相比于 Set 类型多了一个排序属性 score(分值),对于有序集合 ZSet 来说,每个存储元素相当于有两个值组成的,一个是有序集合的元素值,一个是排序值。
2.6.2 内部实现
Zset 类型的底层数据结构是由压缩列表或跳表实现的:
- 如果有序集合的元素个数小于
128
个,并且每个元素的值小于64
字节时,Redis 会使用压缩列表作为 Zset 类型的底层数据结构; - 如果有序集合的元素不满足上面的条件,Redis 会使用跳表作为 Zset 类型的底层数据结构;
在 Redis 7.0 中,压缩列表数据结构已经废弃了,交由 listpack 数据结构来实现了。
2.6.3 常用指令
常规操作指令:
# 往有序集合key中加入带分值元素
ZADD key score member [[score member]...] # 注意,每个元素需要指定一个分值
# 往有序集合key中删除元素
ZREM key member [member...]
# 返回有序集合key中元素member的分值
ZSCORE key member
# 返回有序集合key中元素个数
ZCARD key
# 为有序集合key中元素member的分值加上increment
ZINCRBY key increment member
# 正序获取有序集合key从start下标到stop下标的元素
ZRANGE key start stop [WITHSCORES]
# 倒序获取有序集合key从start下标到stop下标的元素
ZREVRANGE key start stop [WITHSCORES]
# 返回有序集合中指定分数区间内的成员,分数由低到高排序。
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
# 返回指定成员区间内的成员,按字典正序排列, 分数必须相同。
ZRANGEBYLEX key min max [LIMIT offset count]
# 返回指定成员区间内的成员,按字典倒序排列, 分数必须相同
ZREVRANGEBYLEX key max min [LIMIT offset count]
运算操作指令:(相比于 Set 类型,ZSet 类型没有支持差集运算)
# 并集计算(相同元素分值相加),numberkeys一共多少个key,WEIGHTS每个key对应的分值乘积
ZUNIONSTORE destkey numberkeys key [key...] [WEIGHTS weight [weight ...]]
# 交集计算(相同元素分值相加),numberkeys一共多少个key,WEIGHTS每个key对应的分值乘积
ZINTERSTORE destkey numberkeys key [key...] [WEIGHTS weight [weight ...]]
2.6.4 应用场景
Zset 类型(Sorted Set,有序集合) 可以根据元素的权重来排序,我们可以自己来决定每个元素的权重值。比如说,我们可以根据元素插入 Sorted Set 的时间确定权重值,先插入的元素权重小,后插入的元素权重大。
在面对需要展示最新列表、排行榜、电话排序、姓名排序等场景时,如果数据更新频繁或者需要分页显示,可以优先考虑使用 Sorted Set。
2.7 BitMap 类型
2.7.1 基本介绍
Bitmap,即位图,是一串连续的二进制数组(0和1),可以通过偏移量(offset)定位元素。由于 bit 是计算机中最小的单位,使用它进行储存将非常节省空间,特别适合一些数据量大且使用二值统计的场景(例如:使用0|1
表示状态)。
2.7.2 内部实现
Bitmap 本身是用 **String 类型**作为底层数据结构实现的一种统计二值状态的数据类型。
String 类型是会保存为二进制的字节数组,所以,Redis 就把字节数组的每个 bit 位利用起来,用来表示一个元素的二值状态,你可以把 Bitmap 看作是一个 bit 数组。
2.7.3 常用指令
常规操作指令:
# 设置值,其中offset是指位的偏移量,表示从字符串的开始位置(0)到目标位的距离,value只能是 0 和 1
SETBIT key offset value
#例如:
> SETBIT key 0 1
(integer) 0
# 获取值
GETBIT key offset
# 获取指定范围内值为 1 的个数
# start 和 end 以字节为单位,这两个参数是可选的,如果不指定,则计算整个字符串。
BITCOUNT key start end
位运算操作指令:
# 当 BITOP 处理不同长度的字符串时,较短的那个字符串所缺少的部分会被看作 0。返回值是保存到 result 的字符串的长度(以字节byte为单位),和输入 key 中最长的字符串长度相等。
BITOP [operations] [result] [key1] [keyn...]
# operations 位操作符
# AND 与运算 &
# OR 或运算 |
# XOR 异或 ^
# NOT 取反 ~
# 举例:
> BITOP & result key1 key2
# 返回指定key中的BitMap第一次出现指定value(0/1)的位置
BITPOS [key] [value]
2.7.4 应用场景
Bitmap 类型非常适合**二值状态统计的场景(例如:签到统计、登陆状态)**,这里的二值状态就是指集合元素的取值就只有 0 和 1 两种,在记录海量数据时,Bitmap 能够有效地节省内存空间。
2.8 HyperLogLog 类型
2.8.1 基本介绍
HyperLogLog是用于**「统计基数」的数据集合类型**。基数统计就是指统计一个集合中不重复的元素个数。但要注意,HyperLogLog 是统计规则是基于概率完成的,不是非常准确,标准误算率是 0.81%。
简单来说 HyperLogLog 提供不精确的去重计数。
HyperLogLog 的优点是:在输入元素的数量或者体积非常非常大时,计算基数所需的内存空间总是固定的、并且是很小的。
2.8.2 内部实现
请参考:HyperLogLog (opens new window)。
2.8.3 常用指令
HyperLogLog 命令很少,就三个。
# 添加指定元素到(HyperLogLog)key中
PFADD key element
# 返回给定的key中的基数估算值
PFCOUNT key
# 将多个HyperLogLog合并成一个
PFMERGE destkey sourcekey1 sourcekey2 ...
2.8.4 应用场景
Redis HyperLogLog 优势在于只需要花费 12 KB 内存,就可以计算接近 2^64 个元素的基数,和元素越多就越耗费内存的 Set 和 Hash 类型相比,HyperLogLog 就非常节省空间。所以,非常适合统计**百万级以上的网页不重复访客数量(UV) 的场景**。
2.9 GEO 类型
2.9.1 基本介绍
GEO类型主要用于存储地理位置信息,并对存储的信息进行操作。在日常生活中,我们越来越依赖搜索“附近的餐馆”、在打车软件上叫车,这些都离不开基于位置信息服务(Location-Based Service,LBS)的应用。GEO 就非常适合应用在 LBS 服务的场景中。
2.9.2 内部实现
GEO 本身并没有设计新的底层数据结构,而是直接使用了 Sorted Set 集合类型(Zset)。
GEO 类型使用 GeoHash 编码方法实现了经纬度到 Sorted Set 中元素权重分数的转换,「对二维地图做区间划分」和「对区间进行编码」,一组经纬度落在某个区间后,就用区间的编码值来表示,并把编码值作为 Sorted Set 元素的权重分数。
这样一来,我们就可以把经纬度保存到 Sorted Set 中,利用 Sorted Set 提供的“按权重进行有序范围查找”的特性,实现 LBS 服务中频繁使用的“搜索附近”的需求。
2.9.3 常用指令
# 存储指定的地理空间位置,将经度(longitude)、纬度(latitude)、位置名称(member)添加到指定的 key 中。
GEOADD key longitude latitude member
# 从给定的key里返回所有指定名称的经纬度
GETPOS key member
# 返回给定两个位置之间的距离,[指定单位]
GEODIST key member1 member2 [m|km|ft|mi]
# 根据用户给定的经纬度坐标来获取指定范围内的地理位置集合
GEORADIUS key longitude lattude radius m|km|ft|mi
2.9.4 应用场景
需要用到地理位置的,如:滴滴叫车。
2.10 Stream 类型
2.10.1 基本介绍
Redis 5.0 版本新增加的数据类型,Redis 专门为消息队列设计的数据类型。
在 Redis 5.0 Stream 没出来之前,消息队列的实现方式都有着各自的缺陷,例如:
- 发布订阅模式:不能持久化保存消息,离线重连的客户端不能读取历史消息;
- List实现消息队列:无法重复消费一条消息,即不支持消费组模式。
Redis 5.0 便推出了 Stream 类型也是此版本最重要的功能,用于完美地实现消息队列,它支持消息的持久化、支持自动生成全局唯一 ID、支持 ack 确认消息的模式、支持消费组模式等,让消息队列更加的稳定和可靠。
2.10.2 常用指令
# 向流中添加数据,自动生成全局唯一ID
XADD stream_name * field1 value1
# 从一个或多个流中读取数据,可以按照ID读取数据
XREAD STREAMS stream_name id
# 从流中读取范围数据
XRANGE stream_name start end
# XLEN :查询消息长度;
# XDEL : 根据消息 ID 删除消息;
# DEL :删除整个 Stream;
# XREADGROUP:按消费组形式读取消息;
# XPENDING 和 XACK:
# XPENDING 命令可以用来查询每个消费组内所有消费者「已读取、但尚未确认」的消息;
# XACK 命令用于向消息队列确认消息处理已完成;
2.10.3 应用场景
-
消息队列
Stream 可以以使用 XGROUP 创建消费组,创建消费组之后,Stream 可以使用 XREADGROUP 命令让消费组内的消费者读取消息。
2.11 数据类型总结
Redis 常见的五种数据类型:String(字符串),Hash(哈希),List(列表),Set(集合)及 Zset(sorted set:有序集合)。
这五种数据类型都由多种数据结构实现的,主要是出于时间和空间的考虑,当数据量小的时候使用更简单的数据结构,有利于节省内存,提高性能。
Redis 五种数据类型的应用场景:
- String 类型的应用场景:缓存对象、常规计数、分布式锁、共享session信息等。
- List 类型的应用场景:消息队列(有两个问题:1. 生产者需要自行实现全局唯一 ID;2. 不能以消费组形式消费数据)等。
- Hash 类型:缓存对象、购物车等。
- Set 类型:聚合计算(并集、交集、差集)场景,比如点赞、共同关注、抽奖活动等。
- Zset 类型:排序场景,比如排行榜、电话和姓名排序等。
Redis 后续版本又支持四种数据类型,它们的应用场景如下:
- BitMap(2.2 版新增):二值状态统计的场景,比如签到、判断用户登陆状态、连续签到用户总数等;
- HyperLogLog(2.8 版新增):海量数据基数统计的场景,比如百万级网页 UV 计数等;
- GEO(3.2 版新增):存储地理位置信息的场景,比如滴滴叫车;
- Stream(5.0 版新增):消息队列,相比于基于 List 类型实现的消息队列,有这两个特有的特性:自动生成全局唯一消息ID,支持以消费组形式消费数据。
针对 Redis 是否适合做消息队列,关键看你的业务场景:
- 如果你的业务场景足够简单,对于数据丢失不敏感,而且消息积压概率比较小的情况下,把 Redis 当作队列是完全可以的。
- 如果你的业务有海量消息,消息积压的概率比较大,并且不能接受数据丢失,那么还是用专业的消息队列中间件吧,如RabbitMQ 或 Kafka 。
资料参考
内容大多直接搬运自:图解Redis介绍 | 小林coding (xiaolincoding.com)