返回介绍

Redis 数据类型

发布于 2024-04-11 12:48:47 字数 8136 浏览 0 评论 0 收藏 0

关于 Redis 5 种基础数据类型和 3 种特殊数据类型的详细介绍请看下面这两篇文章以及 Redis 官方文档

Redis 常用的数据类型有哪些?

Redis 中比较常见的数据类型有下面这些:

  • 5 种基础数据类型:String(字符串)、List(列表)、Set(集合)、Hash(散列)、Zset(有序集合)。
  • 3 种特殊数据类型:HyperLogLog(基数统计)、Bitmap (位图)、Geospatial (地理位置)。

除了上面提到的之外,还有一些其他的比如 Bloom filter(布隆过滤器) 、Bitfield(位域)。

String 的应用场景有哪些?

String 是 Redis 中最简单同时也是最常用的一个数据类型。它是一种二进制安全的数据类型,可以用来存储任何类型的数据比如字符串、整数、浮点数、图片(图片的 base64 编码或者解码或者图片的路径)、序列化后的对象。

String 的常见应用场景如下:

  • 常规数据(比如 Session、Token、序列化后的对象、图片的路径)的缓存;
  • 计数比如用户单位时间的请求数(简单限流可以用到)、页面单位时间的访问数;
  • 分布式锁(利用 SETNX key value 命令可以实现一个最简易的分布式锁);

关于 String 的详细介绍请看这篇文章: Redis 5 种基本数据类型详解

String 还是 Hash 存储对象数据更好呢?

  • String 存储的是序列化后的对象数据,存放的是整个对象。Hash 是对对象的每个字段单独存储,可以获取部分字段的信息,也可以修改或者添加部分字段,节省网络流量。如果对象中某些字段需要经常变动或者经常需要单独查询 对象中的个别字段信息,Hash 就非常适合。
  • String 存储相对来说更加节省内存,缓存相同数量的对象数据,String 消耗的内存约是 Hash 的一半。并且,存储具有多层嵌套的对象时也方便很多。如果系统对性能和资源消耗非常敏感的话,String 就非常适合。

在绝大部分情况,我们建议使用 String 来存储对象数据即可!

String 的底层实现是什么?

Redis 是基于 C 语言编写的,但 Redis 的 String 类型的底层实现并不是 C 语言中的字符串(即以空字符 \0 结尾的字符数组),而是自己编写了 SDS (Simple Dynamic String,简单动态字符串) 来作为底层实现。

SDS 最早是 Redis 作者为日常 C 语言开发而设计的 C 字符串,后来被应用到了 Redis 上,并经过了大量的修改完善以适合高性能操作。

Redis7.0 的 SDS 的部分源码如下( https://github.com/redis/redis/blob/7.0/src/sds.h ):

/* Note: sdshdr5 is never used, we just access the flags byte directly.
 * However is here to document the layout of type 5 SDS strings. */
struct __attribute__ ((__packed__)) sdshdr5 {
    unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; /* used */
    uint8_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
    uint16_t len; /* used */
    uint16_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
    uint32_t len; /* used */
    uint32_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
    uint64_t len; /* used */
    uint64_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};

通过源码可以看出,SDS 共有五种实现方式 SDS_TYPE_5(并未用到)、SDS_TYPE_8、SDS_TYPE_16、SDS_TYPE_32、SDS_TYPE_64,其中只有后四种实 际用到。Redis 会根据初始化的长度决定使用哪种类型,从而减少内存的使用。

类型字节
sdshdr5< 1<8
sdshdr818
sdshdr16216
sdshdr32432
sdshdr64864

对于后四种实现都包含了下面这 4 个属性:

  • len :字符串的长度也就是已经使用的字节数
  • alloc :总共可用的字符空间大小,alloc-len 就是 SDS 剩余的空间大小
  • buf[] :实际存储字符串的数组
  • flags :低三位保存类型标志

SDS 相比于 C 语言中的字符串有如下提升:

  1. 可以避免缓冲区溢出:C 语言中的字符串被修改(比如拼接)时,一旦没有分配足够长度的内存空间,就会造成缓冲区溢出。SDS 被修改时,会先根据 len 属性检查空间大小是否满足要求,如果不满足,则先扩展至所需大小再进行修改操作。
  2. 获取字符串长度的复杂度较低:C 语言中的字符串的长度通常是经过遍历计数来实现的,时间复杂度为 O(n)。SDS 的长度获取直接读取 len 属性即可,时间复杂度为 O(1)。
  3. 减少内存分配次数:为了避免修改(增加/减少)字符串时,每次都需要重新分配内存(C 语言的字符串是这样的),SDS 实现了空间预分配和惰性空间释放两种优化策略。当 SDS 需要增加字符串时,Redis 会为 SDS 分配好内存,并且根据特定的算法分配多余的内存,这样可以减少连续执行字符串增长操作所需的内存重分配次数。当 SDS 需要减少字符串时,这部分内存不会立即被回收,会被记录下来,等待后续使用(支持手动释放,有对应的 API)。
  4. 二进制安全:C 语言中的字符串以空字符 \0 作为字符串结束的标识,这存在一些问题,像一些二进制文件(比如图片、视频、音频)就可能包括空字符,C 字符串无法正确保存。SDS 使用 len 属性判断字符串是否结束,不存在这个问题。

多提一嘴,很多文章里 SDS 的定义是下面这样的:

struct sdshdr {
    unsigned int len;
    unsigned int free;
    char buf[];
};

这个也没错,Redis 3.2 之前就是这样定义的。后来,由于这种方式的定义存在问题, lenfree 的定义用了 4 个字节,造成了浪费。Redis 3.2 之后,Redis 改进了 SDS 的定义,将其划分为了现在的 5 种类型。

购物车信息用 String 还是 Hash 存储更好呢?

由于购物车中的商品频繁修改和变动,购物车信息建议使用 Hash 存储:

  • 用户 id 为 key
  • 商品 id 为 field,商品数量为 value

Hash 维护简单的购物车信息

那用户购物车信息的维护具体应该怎么操作呢?

  • 用户添加商品就是往 Hash 里面增加新的 field 与 value;
  • 查询购物车信息就是遍历对应的 Hash;
  • 更改商品数量直接修改对应的 value 值(直接 set 或者做运算皆可);
  • 删除商品就是删除 Hash 中对应的 field;
  • 清空购物车直接删除对应的 key 即可。

这里只是以业务比较简单的购物车场景举例,实际电商场景下,field 只保存一个商品 id 是没办法满足需求的。

使用 Redis 实现一个排行榜怎么做?

Redis 中有一个叫做 Sorted Set 的数据类型经常被用在各种排行榜的场景,比如直播间送礼物的排行榜、朋友圈的微信步数排行榜、王者荣耀中的段位排行榜、话题热度排行榜等等。

相关的一些 Redis 命令: ZRANGE (从小到大排序)、 ZREVRANGE (从大到小排序)、 ZREVRANK (指定元素排名)。

Set 的应用场景是什么?

Redis 中 Set 是一种无序集合,集合中的元素没有先后顺序但都唯一,有点类似于 Java 中的 HashSet

Set 的常见应用场景如下:

  • 存放的数据不能重复的场景:网站 UV 统计(数据量巨大的场景还是 HyperLogLog 更适合一些)、文章点赞、动态点赞等等。
  • 需要获取多个数据源交集、并集和差集的场景:共同好友(交集)、共同粉丝(交集)、共同关注(交集)、好友推荐(差集)、音乐推荐(差集)、订阅号推荐(差集+交集) 等等。
  • 需要随机获取数据源中的元素的场景:抽奖系统、随机点名等等。

使用 Set 实现抽奖系统怎么做?

如果想要使用 Set 实现一个简单的抽奖系统的话,直接使用下面这几个命令就可以了:

  • SADD key member1 member2 ... :向指定集合添加一个或多个元素。
  • SPOP key count :随机移除并获取指定集合中一个或多个元素,适合不允许重复中奖的场景。
  • SRANDMEMBER key count : 随机获取指定集合中指定数量的元素,适合允许重复中奖的场景。

使用 Bitmap 统计活跃用户怎么做?

Bitmap 存储的是连续的二进制数字(0 和 1),通过 Bitmap, 只需要一个 bit 位来表示某个元素对应的值或者状态,key 就是对应元素本身 。我们知道 8 个 bit 可以组成一个 byte,所以 Bitmap 本身会极大的节省储存空间。

你可以将 Bitmap 看作是一个存储二进制数字(0 和 1)的数组,数组中每个元素的下标叫做 offset(偏移量)。

img

如果想要使用 Bitmap 统计活跃用户的话,可以使用日期(精确到天)作为 key,然后用户 ID 为 offset,如果当日活跃过就设置为 1。

初始化数据:

> SETBIT 20210308 1 1
(integer) 0
> SETBIT 20210308 2 1
(integer) 0
> SETBIT 20210309 1 1
(integer) 0

统计 20210308~20210309 总活跃用户数:

> BITOP and desk1 20210308 20210309
(integer) 1
> BITCOUNT desk1
(integer) 1

统计 20210308~20210309 在线活跃用户数:

> BITOP or desk2 20210308 20210309
(integer) 1
> BITCOUNT desk2
(integer) 2

使用 HyperLogLog 统计页面 UV 怎么做?

使用 HyperLogLog 统计页面 UV 主要需要用到下面这两个命令:

  • PFADD key element1 element2 ... :添加一个或多个元素到 HyperLogLog 中。
  • PFCOUNT key1 key2 :获取一个或者多个 HyperLogLog 的唯一计数。

1、将访问指定页面的每个用户 ID 添加到 HyperLogLog 中。

PFADD PAGE_1:UV USER1 USER2 ...... USERn

2、统计指定页面的 UV。

PFCOUNT PAGE_1:UV

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文