🌟 后端 | Redis 数据类型



Redis 数据结构

String List Set SortedSet Hash

RedisObject

不管是任何一种数据结构,最终都会被封装为RedisObject 格式,它是一种结构体,可以理解为Java中的类

1
2
3
4
5
6
7
typeof struct redisObject {
unsigned type: 4; // 对象类型
unsigned encoding: 4; // 底层编码方式
unsigned lru: LRU_BITS; // 表示最后被访问的时间
int refcount; // 对象引用计数器,为0说明对象无人引用,可以被回收
void *ptr; // 指针, 指向存放实际数据的空间
}

在 RedisObject 结构体中存有 type、encoding、lru、refcount、*ptr, 并没有包含真实的数据,仅仅是对象头信息,内存占用的大小是 128bit,也就是16字节,其中指针 ptr 指向的才是真实数据存储的内存地址。 因此 RedisObject 的内存开销很大。

基础数据类型

String

最基本的数据结构,二进制安全。

应用场景

  • 存储常规数据/缓存对象:比如用户信息、商品详情,序列化后存储。
  • 计数器:点赞数、浏览数、库存数量等,可以配合 INCR/DECR 实现高并发下的原子递增递减。
  • 分布式锁:利用 SET key value NX EX 实现。

Hash

redis 中的 hash 是一个 string 类型的 field-value (键值对) 的映射表,特别适合用于存储对象,结构类似于 Map。后续操作的时候,你可以直接修改这个对象中的某些字段的值。

应用场景

  • 存储用户信息:user:1001 -> {name:Tom, age:20, balance:100}。
  • 这样可以只更新某个字段,而不是整个对象。
  • 配置管理:存储系统配置参数,字段更新灵活。

List

Redis 的 List 的实现为一个 双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销。

应用场景

  • 消息队列(简单场景):LPUSH + RPOP,实现异步任务队列(但更推荐 Kafka/RabbitMQ)。
  • 信息流、排行榜最新动态:保存最新的评论、操作日志,取最新 N 条。
  • 分页:存储有序的内容列表,支持 LRANGE。

Set

无序去重集合,支持交并差集运算。

应用场景

  • 用户标签:存储用户的兴趣标签,方便做交集推荐,文章点赞、动态点赞。共同好友(交集)、共同粉丝(交集)、共同关注(交集)、好友推荐(差集)、音乐推荐(差集)、订阅号推荐(差集+交集) 等场景。
  • 好友关系:SADD friend:1001 1002 1003,然后用 SINTER 求共同好友。
  • 去重统计:统计当天访问的唯一 IP。

SortedSet(ZSet)

SortedSet 是有序集合,底层的存储的每个数据都包含 element 和 score 两个值。score 是得分,element 则是字符串值。SortedSet 会根据每个 element 的 score 值排序,形成有序集合。

SortedSet 底层是基于 HashTable 、跳表实现的。SortedSet 底层需要用到两种数据结构,对内存占用比较高。因此 Redis 底层会对 SortedSet 中的元素大小做判断决定去使用 ListPack 还是 HashTable + SkipList。

应用场景

  • 排行榜:用户分数、积分、游戏排名,利用 ZADD + ZREVRANGE。
  • 延迟队列:score 存放执行时间,使用 ZRANGEBYSCORE 获取到期任务。
  • 热门榜单:根据点击量、热度排序。

底层采用 SkipList

SkipList 是链表,但相比传统链表有几点差异:

  • 元素按照升序排列存储
  • 节点可能包含多个指针,指针跨度不同。

传统链表只有指向前后元素的指针,因此只能顺序依次访问。如果查找的元素在链表中间,查询的效率会比较低。而 SkipList 则不同,它内部包含跨度不同的多级指针,可以让我们跳跃查找链表中间的元素,效率非常高。

这种多级指针的查询方式就避免了传统链表的逐个遍历导致的查询效率下降问题。在对有序数据做随机查询和排序时效率非常高。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
'''
SkipList 跳表的结构体
'''
typedef struct zskiplist {
// 头尾节点指针
struct zskiplistNode *header, *tail;
// 节点数量
unsigned long length;
// 最大的索引层级
int level;
} zskiplist;

'''
跳表中节点的结构体
'''
typedef struct zskiplistNode {
sds ele; // 节点存储的字符串
double score;// 节点分数,排序、查找用
struct zskiplistNode *backward; // 前一个节点指针
struct zskiplistLevel {
struct zskiplistNode *forward; // 下一个节点指针
unsigned long span; // 索引跨度
} level[]; // 多级索引数组
} zskiplistNode;

SkipList 主要属性是 header 和 tail,也就是头尾指针,因此它是支持双向遍历的。
每个节点中都包含 ele 和 score 两个属性,其中 score 是得分,也就是节点排序的依据。ele 则是节点存储的字符串数据指针。