学习分享(第 2 期):从源码层面看 Redis 节省内存的设计

学习分享(第 2 期):从源码层面看 Redis 节省内存的设计

这里记录的是学习分享的内容,文章维护在 Github:studeyang/leanrning-share

回顾

在文章《Redis 的 String 类型,原来这么占内存》中,我们学习了 SDS 的底层结构,发现 SDS 存储了很多的元数据,再加上全局哈希表的实现,使得 Redis String 类型在内存占用方面并不理想。

然后在文章《学习分享(第1期)之Redis:巧用Hash类型节省内存》中,我们学习了另一种节省内存的方案,使用 ziplist 结构的 Hash 类型,内存占用减少了一半,效果显著。

虽然我们在使用 String 类型后,占用了较多内存,但其实 Redis 是对 SDS 做了节省内存设计的。除此之外,Redis 在其他方面也都考虑了内存开销,今天我们就从源码层面来看看都做了哪些节省内存的设计。

文中代码版本为 6.2.4。

一、redisObject 的位域定义法

我们知道,redisObject 是底层数据结构如 SDS, ziplist 的封装,因此,redisObject 如果能做优化,最终也能带来节省内存的用户体验。在源码 server.h 中定义了 redisObject 的结构体,如下面代码所示:

1
2
3
4
5
6
7
8
9
#define LRU_BITS 24

typedef struct redisObject {
unsigned type:4;//对象类型(4位=0.5字节)
unsigned encoding:4;//编码(4位=0.5字节)
unsigned lru:LRU_BITS;//记录对象最后一次被应用程序访问的时间(24位=3字节)
int refcount;//引用计数。等于0时表示可以被垃圾回收(32位=4字节)
void *ptr;//指向底层实际的数据存储结构,如:sds等(8字节)
} robj;

type, encoding, lru, refcount 都是 redisObject 的元数据,redisObject 的结构如下图所示。

阅读更多

学习分享(第 1 期)之 Redis:巧用 Hash 类型节省内存

开篇

之前的分享内容都是相对零散的知识点,不成体系。以后的每周分享,我会尽量将每篇文章串连起来,于是我决定做一个专栏,名字就叫《学习分享》。这是该系列的第一篇。

《学习分享》内容大多来自我平时学习过程中的笔记,笔记仓库在 Github:studeyang/technotes。其中我认为有深度、对工作有帮助的内容,就会以文章的形式发表在该专栏,内容会首发在我的公众号掘金今日头条,也会维护在 Github:studeyang/leanrning-share

回顾

上篇文章《Redis 的 String 类型,原来这么占内存》中,我们使用 String 类型存储了图片 ID 和图片存储对象 ID,结果发现两个 Long 类型的 ID 竟然占了 68 字节内存。具体验证过程,我还是贴一下方便你回顾。

1、查看 Redis 的初始内存使用情况。

1
2
3
127.0.0.1:6379> info memory
# Memory
used_memory:871840

2、接着插入 10 条数据。

1
2
3
4
5
6
7
8
9
10
10.118.32.170:0> set 1101000060 3302000080
10.118.32.170:0> set 1101000061 3302000081
10.118.32.170:0> set 1101000062 3302000082
10.118.32.170:0> set 1101000063 3302000083
10.118.32.170:0> set 1101000064 3302000084
10.118.32.170:0> set 1101000065 3302000085
10.118.32.170:0> set 1101000066 3302000086
10.118.32.170:0> set 1101000067 3302000087
10.118.32.170:0> set 1101000068 3302000088
10.118.32.170:0> set 1101000069 3302000089

3、再次查看内存。

1
2
3
127.0.0.1:6379> info memory
# Memory
used_memory:872528

可以看到,存储 10 个图片,内存使用了 688 个字节。一个图片 ID 和图片存储对象 ID 的记录平均用了 68 字节。

这是上次我们讲述的场景。

并且还留下了一道思考题:既然 String 类型这么占内存,那么你有好的方案来节省内存吗?

今天呢,我们就来具体谈一谈。

阅读更多

Redis String类型的内存开销都花在哪儿了?

1、场景介绍

假设现在我们要开发一个图片存储系统,要求这个系统能够根据图片 ID 快速查找到图片存储对象 ID。图片 ID 和图片存储对象 ID 的样例数据如下:

1
2
photo_id: 1101000060
photo_obj_id: 3302000080

在这种场景下,图片 ID 和图片存储对象 ID 刚好是一对一的关系,是典型的“键 - 单值”模式,Redis 的 String 类型提供了“一个键对应一个值的数据”的保存形式,在这种场景下刚好适用。

确定使用 String 类型后,接下来我们通过实战,来看看它的内存使用情况。首先通过下面命令连接上 Redis。

本文我使用的 Redis Server 及下文源码都是 6.2.4 版本。

1
redis-cli -h 127.0.0.1 -p 6379

然后执行下面的命令查看 Redis 的初始内存使用情况。

1
2
3
127.0.0.1:6379> info memory
# Memory
used_memory:871840

接着插入 10 条数据:

1
2
3
4
5
6
7
8
9
10
10.118.32.170:0> set 1101000060 3302000080
10.118.32.170:0> set 1101000061 3302000081
10.118.32.170:0> set 1101000062 3302000082
10.118.32.170:0> set 1101000063 3302000083
10.118.32.170:0> set 1101000064 3302000084
10.118.32.170:0> set 1101000065 3302000085
10.118.32.170:0> set 1101000066 3302000086
10.118.32.170:0> set 1101000067 3302000087
10.118.32.170:0> set 1101000068 3302000088
10.118.32.170:0> set 1101000069 3302000089

再次查看内存:

1
2
3
127.0.0.1:6379> info memory
# Memory
used_memory:872528

可以看到,存储 10 个图片,内存使用了 688 个字节。一个图片 ID 和图片存储对象 ID 的记录平均用了 68 字节。

但问题是,一组图片 ID 及其存储对象 ID 的记录,实际只需要 16 字节就可以了。图片 ID 和图片存储对象 ID 都是 10 位数,而 8 字节的 Long 类型最大可以表示 2 的 64 次方的数值,肯定可以表示 10 位数。这样算下来只需 16 字节就可以了,为什么 String 类型却用了 68 字节呢?

阅读更多
Redis高可用之 Sentinel 机制实现细节
Redis高可用全景一览

Redis高可用全景一览

对于一项技术的学习,我们要对这项技术有一个全局观,下面是一张 Redis 全景图,画得非常全面。

阅读更多

海量数据下,统计用户的签到信息

在 Web 和移动应用的业务场景中,我们经常需要保存这样一种信息:统计用户在手机 App 上的签到打卡信息。

在签到打卡的场景中,我们只用记录签到(1)或未签到(0),它就是非常典型的二值状态。在签到统计时,每个用户一天的签到用 1 个 bit 位就能表示,一个月(假设是 31 天)的签到情况用 31 个 bit 位就可以,而一年的签到也只需要用 365 个 bit 位。

阅读更多