深入学习Redis-常用数据结构详解


1. String类型

其基本的编码方式是RAW,是基于SDS实现的,存储上限为512MB

此时字符串的数据和存储redisObject的内存区域是一块区域,因此在这种情况下需要两次内存分配申请,那么同样的,回收内存也需要两次的内存回收,将数据和对象头连在一起,可以更好的利用CPU的cache

如果存储的SDS长度小于44个字节,那么就会采用EMBSTR编码,此时的object headSDS是一段连续的空间,申请内存的时候只需要调用一次内存分配函数,效率是更高的。

EMBSTR编码方式有什么缺点?

如果字符串的长度增加需要重新分配内存的时候,整个redisObjectsds都需要重新分配空间,所以embstr编码的字符串对象实际上是只读的,redis没有为embstr编码的字符串对象编写任何相关的修改程序,当我们对embstr编码的字符串对象执行任何修改命令时,程序会先将embstr转换为raw,然后在执行修改

如果存储的字符串是整数值,并且大小在LONG_MAX范围内,那么就会采用INT类型的编码,直接将数据保存在RedisObjectptr指针的位置刚好8个字节,不再需要SDS

分布式锁的实现

set命令有一个nx参数可以实现只有key不存在才可以插入,可以基于它来实现分布式锁

  • 如果key不存在,那么就显示插入成功,表示加锁成功
  • 如果key存在,那么就显示插入失败

2. List类型

List的底层数据结构是由双向链表或者ZipList实现的

  • 如果列表的元素个数小于512个,列表中的每个元素的值都小于64个字节,Redis会使用压缩列表作为List类型的底层数据结构
  • 如果列表的元素不满足上述的条件,Redis会使用双向链表作为List类型的底层数据结构

在3.2之后,List数据类型底层数据结构就只由quickList进行实现了,替代了双向链表和压缩列表

常用的命令有

# 将一个或多个值value插入到key列表的表头(最左边),最后的值在最前面
LPUSH key value [value....]
# 将一个或多个值value插入到key列表的表尾(最右边)
RPUSH key value [value....]
# 移除并返回key列表的头元素
LPOP key   
# 移除并返回key列表的尾元素
RPOP key 
# 返回列表key中指定区间内的元素,区间以偏移量start和stop指定,从0开始
LRANGE key start stop
# 从key列表表头弹出一个元素,没有就阻塞timeout秒,如果timeout=0则一直阻塞
BLPOP key [key...] timeout
# 从key列表表尾弹出一个元素,没有就阻塞timeout秒,如果timeout=0则一直阻塞
BRPOP key [key ...] timeout

3. Hash类型

Hash是一个键值对,value = [{f1,v1},{f2,v2},...,{fn,vn}]

Hash的底层实现是基于压缩列表或者是哈希表实现的

  • 如果Hash类型元素的个数是小于512个字节的(默认值),那么就可以由hash-max-ziplist-entries进行配置,所有值小于64字节的话,那么Redis会基于压缩列表来作为Hash类型的底层数据结构
  • 如果哈希类型元素不满足上述的条件,Redis就会使用哈希表来作为Hash类型的底层数据结构
# 存储一个哈希表key的键值
HSET key filed value
# 获取哈希表key对应的field的键值
HGET key filed

# 在一个哈希表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 n

4. SET类型

set类型是一个无序并且唯一的键值集合,它的存储顺序不会按照插入的先后顺序进行存储

一个集合最多可以存储2^32-1个元素,其内部的实现是基于哈希表或者整数集合实现的

  • 如果集合中的元素都是整数而且元素的个数是小于512个,那么Redis就会使用整数集合作为Set类型的底层数据结构
  • 如果集合中的元素不满足上述的条件,那么Redis就会使用哈希表作为Set类型的底层数据结构

Set的常用操作

# 往集合key中存入元素,元素存在则忽略,如果key不存在那么就新建
SADD key member [member...]
# 从集合key中删除元素
SREM key member [member...]
# 获取集合key中的所所有元素
SMEMBERS key
# 获取集合key中元素个数
SCARD key

# 判断member元素是否存在于集合key中
SISMEMBER key member
# 从集合key中随机选出count个元素,元素不删除,可以拿来做抽奖
SRANDMEMBER key [count]
# 从集合key中随机选出count个元素,元素从key中删除
SPOP key [count]
# 交集运算
SINTER key [key...]
# 将交集结果存入新集合destination中
SINTERSTORE destination key [key ...]
# 并集运算
SUNION key [key]
# 将并集运算结果存入到新集合中
SUNIONSTORE destination key [key ...]
# 差集运算
SDIFF key [key...]
# 将差集运算结果存入新集合中
SDIFFSTORE destination key [key ...]

5. ZSET类型

Zset 类型(有序集合类型)相比于 Set 类型多了一个排序属性 score(分值),对于有序集合 ZSet 来说,每个存储元素相当于有两个值组成的,一个是有序集合的元素值,一个是排序值。

有序集合保留了集合不能有重复成员的特性(分值可以重复),但不同的是,有序集合中的元素可以排序。

Zset 类型的底层数据结构是由压缩列表或跳表实现的:

  • 如果有序集合的元素个数小于 128 个,并且每个元素的值小于 64 字节时,Redis 会使用压缩列表作为 Zset 类型的底层数据结构;
  • 如果有序集合的元素不满足上面的条件,Redis 会使用跳表作为 Zset 类型的底层数据结构;

Zset的常用操作:

# 往有序集合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]
# 并集计算(相同元素分值相加),numberkeys一共多少个key,WEIGHTS每个key对应的分值乘积
ZUNIONSTORE destkey numberkeys key [key...] 
# 交集计算(相同元素分值相加),numberkeys一共多少个key,WEIGHTS每个key对应的分值乘积
ZINTERSTORE destkey numberkeys key [key...]

6. BITMAP类型

BitMap也就是位图,是一连串的二进制数组,可以通过偏移量offset来定位元素,BitMap通过最小的单位bit来进行0|1的设置,表示某个元素的值或者状态,时间复杂度为O(1)

特别适合一些二值统计的场景,BitMap本身使用String类型作为底层数据结构实现的一种统计二值状态的数据类型,String类型由于底层是SDS,其API在处理的时候会将字符以二进制的形式进行解析,因此完全就可以利用这些个特性来表示一个二值的状态

# 设置值,其中value只能是1或者0
SETBIT key offset value
# 获取值
GETBIT key offset
# 获取指定范围内值为1的个数
# start和end以字节为单位
BITCOUNT key start end

如何判断一个用户是否登录?

只需要一个 key = login_status 表示存储用户登陆状态集合数据, 将用户 ID 作为 offset,在线就设置为 1,下线设置 0。通过 GETBIT判断对应的用户是否在线。 5000 万用户只需要 6 MB 的空间。

假如我们要判断 ID = 10086 的用户的登陆情况:

第一步,执行以下指令,表示用户已登录。

SETBIT login_status 10086 1

第二步,检查该用户是否登陆,返回值 1 表示已登录。

GETBIT login_status 10086

第三步,登出,将 offset 对应的 value 设置成 0。

SETBIT login_status 10086 0

7. HyperLogLog类型

Redis HyperLogLog 是 Redis 2.8.9 版本新增的数据类型,是一种用于「统计基数」的数据集合类型,基数统计就是指统计一个集合中不重复的元素个数。但要注意,HyperLogLog 是统计规则是基于概率完成的,不是非常准确,标准误算率是 0.81%。

所以,简单来说 HyperLogLog 提供不精确的去重计数

HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的内存空间总是固定的、并且是很小的。

在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数,和元素越多就越耗费内存的 Set 和 Hash 类型相比,HyperLogLog 就非常节省空间。

这什么概念?举个例子给大家对比一下。

用 Java 语言来说,一般 long 类型占用 8 字节,而 1 字节有 8 位,即:1 byte = 8 bit,即 long 数据类型最大可以表示的数是:2^63-1。对应上面的2^64个数,假设此时有2^63-1这么多个数,从 0 ~ 2^63-1,按照long以及1k = 1024 字节的规则来计算内存总数,就是:((2^63-1) * 8/1024)K,这是很庞大的一个数,存储空间远远超过12K,而 HyperLogLog 却可以用 12K 就能统计完。

如何统计网页的UV数?

可以使用Redis中提供的HyperLogLog数据结构,具体实现流程如下:

将访问页面的每个用户都添加到HyperLogLog

PFADD ad:uv user2 user1 user3 user4 ...

如何就可以使用PFCOUNT直接获取UV值了

PFCOUNT ad:uv

8. GEO类型

GEO是基于ZSET进行实现的,它在使用的时候,将当前的经纬度通过GEOHASH()的一个计算,得到一个得分,然后加入到ZSET中,这其中的两个关键机制就是对二维地图做区间划分和对区间进行编码,一组经纬度落在某个区间后,就用区间的编码值来表示,并把编码值作为ZSET元素的权重分数

这样一来,就可以把经纬度保存到ZSET中,利用ZSET提供的按照权重进行有序范围查询的特性,实现LBS服务中频繁使用的搜索附近的需求

# 存储指定的地理空间位置,可以将一个或者多个经度、维度、位置名称添加到指定的ZSET
GEOADD key longitude latitude member [...]
# 从给定的key中返回所有指定名称的位置
GEOPOS key member
# 返回两个给定位置间的距离(单位)
GEODIST key member1 member2 [m|km|ft|mi]
# 根据用户给定的经纬度坐标来获取指定范围内的地理位置集合。
GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]

怎么实现一个滴滴打车?

假设车辆 ID 是 33,经纬度位置是(116.034579,39.030452),我们可以用一个 GEO 集合保存所有车辆的经纬度,集合 key 是 cars:locations。

执行下面的这个命令,就可以把 ID 号为 33 的车辆的当前经纬度位置存入 GEO 集合中:

GEOADD cars:locations 116.034579 39.030452 33

当用户想要寻找自己附近的网约车时,LBS 应用就可以使用 GEORADIUS 命令。

例如,LBS 应用执行下面的命令时,Redis 会根据输入的用户的经纬度信息(116.054579,39.030452 ),查找以这个经纬度为中心的 5 公里内的车辆信息,并返回给 LBS 应用。

GEORADIUS cars:locations 116.054579 39.030452 5 km ASC COUNT 10

文章作者: 穿山甲
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 穿山甲 !
  目录