掘金 后端 ( ) • 2024-04-27 17:51

引言

你是否在工作中遇到过Redis的bigkey导致的内存占用严重、查询耗时大大增加?

同时bigKey还可能导致Redis实例的崩溃,因为内存不够用了,Redis就会不堪重负,像是被大象坐了一样,瞬间崩溃!

所以,大家在使用Redis的时候一定要小心,Redis内存是十分宝贵的资源,我们在使用时,要特别注意对内存资源的合理应用。

下面带大家了解下Redis极大节省内存空间的10个实用技巧。

正文

什么是bigkey

在面试过程中,发现很多人都对bigkey的概念搞错了,认为bigkey是一个key比较大的存储对象。

其实不是的,我们不能用英文直译的方式来理解它。所以这里还是给大家科普下:

bigkey,其实就是Redis中占用大量内存空间的键,具体来说,就是那些因为存储了大量数据,或者单个数据项太大,导致内存占用严重的键。

简单点来说:就是这个redis的某个键(key)存了太多的数据,占用了太多的内存

Redis节省内存的方式就是合理的运用Redis的数据结构,减少redis的bigkey的产生, 以及做好内存的清理和淘汰策略。

最近无意间获得一份阿里大佬写的刷题笔记,一下子打通了我的任督二脉,进大厂原来没那么难。 这是大佬写的, 7701页的BAT大佬写的刷题笔记,让我offer拿到手软

1、使用Hash数据结构

Hash是Redis提供的一种非常灵活的数据结构,可以存储键值对的集合。 如果我们存储对象类型的数据,建议使用Hsah

假设我们有一个电商网站,需要存储商品的信息,比如商品名称、类目、属性等,这时候就可以用Hash数据结构来存储,一个商品对应一个Hash。

// 使用Hash存储商品信息
String userId = "product:1001";
jedis.hset(userId, "productName", "xxxx");
jedis.hset(userId, "category", "手机");
jedis.hset(userId, "prop", "xxx");

2、使用压缩列表优化小规模列表

当你需要存储的列表元素数量不多时,Redis会自动使用压缩列表来存储,这样可以节省内存空间。

比如在一个论坛系统中,用户发布了一篇文章,我们可以用列表来存储文章的评论,而且这篇文章评论不多的话,就可以充分利用压缩列表的优势。

// 使用压缩列表存储文章评论
String articleId = "article:2001";
jedis.lpush(articleId + ":comments", "评论1", "评论2", "评论3");

3、使用Bitmaps存储布尔值信息

Bitmaps是一种非常紧凑的数据结构,适合存储布尔值信息。

在我们平时开发过程中,会有一些 bool 型数据需要存取,比如用户一年的签到记录,签了是 1,没签是 0,要记录 365 天。如果使用普通的 key/value,每个用户要记录 365 个,当用户上亿的时候,需要的存储空间是惊人的。

为了解决这个问题,Redis 提供了位图数据结构,这样每天的签到记录只占据一个位,365 天就是 365 个位,46 个字节 (一个稍长一点的字符串) 就可以完全容纳下,这就大大节约了存储空间。

// 使用Bitmaps存储用户签到情况
String userId = "user:1001";
int day = 10; // 签到的天数,从0开始计算
jedis.setbit("sign_in:" + day, Long.parseLong(userId), true);

4、使用ZSET存储有序集合

ZSET是Redis中的有序集合数据结构,可以按照指定的分数进行排序。

比如在一个新闻网站中,我们可以使用ZSET来存储新闻文章的阅读量排行榜,分数表示阅读量,文章ID作为成员。

// 使用ZSET存储文章阅读量排行榜
String articleId = "article:3001";
jedis.zadd("article:views", 1000, articleId);

5、使用SET存储唯一值

SET数据结构适合存储唯一值,比如用户的喜好标签、商品的标签等。

在一个电商平台中,我们可以使用SET来存储商品的标签,确保每个标签都是唯一的。

// 使用SET存储商品标签
String productId = "product:5001";
jedis.sadd(productId + ":tags", "电子产品", "数码设备", "智能家居");

6、使用HyperLogLog进行基数统计

HyperLogLog是一种用于统计不重复元素数量的算法,在统计网站的UV(独立访客)数量时非常有用。 比如:

如果你负责开发维护一个大型的网站,有一天老板找产品经理要网站每个网页每天的 UV 数据,然后让你来开发这个统计模块,你会如何实现?

如果统计 PV 那非常好办,给每个网页一个独立的 Redis 计数器就可以了,这个计数器的 key 后缀加上当天的日期。这样来一个请求,incrby 一次,最终就可以统计出所有的 PV 数据。

但是 UV 不一样,它要去重,同一个用户一天之内的多次访问请求只能计数一次。这就要求每一个网页请求都需要带上用户的 ID,无论是登陆用户还是未登陆用户都需要一个唯一 ID 来标识。

这时候就可以引入HyperLogLog, HyperLogLog 数据结构就是用来解决这种统计问题的。

HyperLogLog 提供不精确的去重计数方案,虽然不精确但是也不是非常不精确,标准误差是 0.81%,这样的精确度已经可以满足上面的 UV 统计需求了,又大量节省了内存

HyperLogLog 实现中用到的是 16384 个桶,也就是 2^14,每个桶的 maxbits 需要 6 个 bits 来存储,最大可以表示 maxbits=63,于是总共占用内存就是2^14 * 6 / 8 = 12k字节。

// 使用HyperLogLog统计网站UV
String today = "2024-04-23";
String userId = "user:1001";
jedis.pfadd("uv:" + today, userId);

7、使用String存储序列化的数据

虽然Redis提供了丰富的数据结构,但有时候我们仍然需要将复杂的数据结构序列化为字符串存储。

比如在一个微博系统中,我们可以将用户的微博对象序列化为JSON字符串,然后存储到Redis中。

// 使用String存储序列化后的用户微博对象
UserPost userPost = new UserPost("user:1001", "今天天气真好!", new Date());
String userPostJson = objectMapper.writeValueAsString(userPost);
jedis.set("user:1001:post:1", userPostJson);

8、使用Pipeline批量操作命令

Pipeline是一种批量操作命令的机制,可以减少网络通信开销,提高操作效率。

在需要进行大量操作时,使用Pipeline可以有效节省内存空间和提升性能。

//使用Pipeline批量设置键值对
Pipeline pipeline = jedis.pipelined();
for (int i = 0; i < 10000; i++) {
    pipeline.set("key:" + i, "value:" + i);
}
pipeline.sync();

9、定期清理过期数据

定期清理过期数据是保持Redis内存空间有效利用的重要方法之一。通过设置合适的过期时间,并定期清理过期数据,可以释放内存空间。

// 定期清理过期数据
jedis.setex("key:1001", 600, "value");

10、合理设置内存策略

根据实际需求和系统情况,合理设置Redis的内存策略,比如最大内存限制、淘汰策略等,可以更好地管理和利用内存空间。

本文总结

Redis内存是非常宝贵的资源,我们在日常开发中,要合理运用Redis的数据结构以达到节省内存的目的。

最后说一句(求关注,求赞,你的鼓励是我创作的最大动力)

最近无意间获得一份阿里大佬写的刷题笔记,一下子打通了我的任督二脉,进大厂原来没那么难。 这是大佬写的, 7701页的BAT大佬写的刷题笔记,让我offer拿到手软

本文,已收录于,我的技术网站 cxykk.com:程序员编程资料站,有大厂完整面经,工作技术,架构师成长之路,等经验分享

求一键三连:点赞、分享、收藏

点赞对我真的非常重要!在线求赞,加个关注我会非常感激!