Redis从入门到精通(二十)Redis最佳实践(一)优雅的Key结构、拒绝BigKey

news2025/1/10 3:23:43

文章目录

  • 第7章 Redis最佳实践
    • 7.1 Redis键值设计
      • 7.1.1 优雅的Key结构
      • 7.1.2 拒绝BigKey
        • 7.1.2.1 何为BigKey
        • 7.1.2.2 BigKey的危害
        • 7.1.2.3 如何发现BigKey
        • 7.1.2.4 如何删除BigKey
      • 7.1.3 恰当的数据类型
        • 7.1.3.1 存储Java对象
        • 7.1.3.2 存储hash数据
      • 7.1.4 小结

第7章 Redis最佳实践

7.1 Redis键值设计

7.1.1 优雅的Key结构

Redis的Key虽然可以自定义,但最好遵循下面的几个最佳实践约定:

  • 遵循基本格式:[业务名称]:[数据名]:[id]
  • 长度不超过44字节
  • 不包含特殊字符

例如,保存登录用户信息可以这样设计:login:user:1

这样设计的好处在于:可读性强;可避免Key冲突;方便管理;更节省空间。

Key是string类型的,其底层编码包含int、embstr和raw三种。其中,embstr在小于44字节使用,采用连续内存空间,内存占用更小。而当字节数大于44字节时,会转为raw模式存储,在raw模式下内存空间不是连续的,而是采用一个指针指向了另外一段内存空间,访问的时候性能也就会受到影响,还有可能产生内存碎片。如图:

7.1.2 拒绝BigKey

7.1.2.1 何为BigKey

BigKey通常以Key本身的大小和Key中成员的数量来综合判定,例如:

  • Key本身的数据量过大:一个string类型的Key,它的值为5MB;
  • Key中成员的数量过多:一个zset类型的Key,它的成员数量为10000个;
  • Key中成员的数据量过大:一个hash类型的Key,它的成员数量虽然只有1000个,但这些成员的Value值总大小为100MB。

Redis提供了 memory usage 命令来查看指定Key及其Value的大小:

127.0.0.1:6379> set name Jack
OK
127.0.0.1:6379> memory usage name
(integer) 56
127.0.0.1:6379> set name aaaaaaaaaaaaaa
OK
127.0.0.1:6379> memory usage name
(integer) 72

但一般不推荐使用memory命令,因为这个指令对CPU的使用率是比较高的。 在实际开发中,一般只需要衡量值的长度就可以了:

127.0.0.1:6379> strlen name
(integer) 14
127.0.0.1:6379> lpush l2 s1 s2
(integer) 2
127.0.0.1:6379> llen l2
(integer) 2

通常情况下,建议单个Key的Value值小于10KB;而对于集合类型的Key,建议元素数量小于1000个。

7.1.2.2 BigKey的危害
  • 网络阻塞:对BigKey执行读请求时,少量的QPS就可能导致带宽使用率被占满,导致Redis实例,乃至所在物理机变慢;

  • 数据倾斜:BigKey所在的Redis实例内存使用率远超其他实例,无法使数据分片的内存资源达到均衡;

  • Redis阻塞:对元素较多的hash、list、zset等做运算会耗时较久,使主线程被阻塞;

  • CPU压力:对BigKey的数据序列化和反序列化会导致CPU的使用率飙升,影响Redis实例和本机其它应用。

7.1.2.3 如何发现BigKey
  • 1)redis-cli --bigkeys

利用redis-cli --bigkeys命令,可以遍历分析所有Key,并返回Key的整体统计信息与每种数据类型的Top1的BigKey:

  • 2)scan扫描

自己编程,利用scan扫描Redis中的所有Key,利用strlen、hlen等命令判断Key的长度:

// 自定义string类型,超过400KB,则是BigKey
final static int STR_MAX_LEN = 400;
// 自定义hash类型,超过100KB,则是BigKey
final static int HASH_MAX_LEN = 100;

@Test
public void testScan() {
    int maxLen = 0;
    long len = 0;
    int cursor = 0;
    do {
        // 扫描并获取一部分Key
        ScanResult<String> result = jedis.scan(cursor);
        // 记录此时的cursor
        cursor = result.getCursor();
        List<String> list = result.getResult();
        if (list == null || list.isEmpty()) {
            break;
        }
        // 遍历这一部分Key
        for (String key : list) {
            // 判断key的类型
            String type = jedis.type(key);
            switch (type) {
                case "string":
                    len = jedis.strlen(key);
                    maxLen = STR_MAX_LEN;
                    break;
                case "hash":
                    len = jedis.hlen(key);
                    maxLen = HASH_MAX_LEN;
                    break;
                case "list":
                    len = jedis.llen(key);
                    maxLen = HASH_MAX_LEN;
                    break;
                case "set":
                    len = jedis.scard(key);
                    maxLen = HASH_MAX_LEN;
                    break;
                case "zset":
                    len = jedis.zcard(key);
                    maxLen = HASH_MAX_LEN;
                    break;
                default:
                    break;
            }
            // 如果查询出来的长度超过自定义的长度,则是BigKey
            if (len >= maxLen) {
                System.out.printf("Found big key : %s, type: %s, length or size: %d %n", key, type, len);
            }
        }
    } while (cursor != 0);
}

执行以上单元测试结果如下:

Found big key : item:id:1, type: string, length or size: 411 
Found big key : item:id:5, type: string, length or size: 406 
Found big key : item:id:4, type: string, length or size: 440 
Found big key : item:id:3, type: string, length or size: 432
  • 3)第三方工具

利用第三方工具,如Redis-Rdb-Tools分析RDB快照文件,全面分析内存使用情况。详见:https://github.com/sripathikrishnan/redis-rdb-tools

  • 4)网络监控

自定义工具,监控进出Redis的网络数据,超出预警值时主动告警。

7.1.2.4 如何删除BigKey

BigKey内存占用较多,删除这样的Key也需要耗费很长时间,从而导致Redis主线程阻塞,引发一系列问题。

为此,Redis在4.0后提供了异步删除的命令:UNLINK key [key ...]

7.1.3 恰当的数据类型

7.1.3.1 存储Java对象

比如要存储一个User对象,一般有三种存储方式:

  • 1)JSON字符串
login:user:1{"name":"Jack","age":21}

优点:实现简单粗暴
缺点:数据耦合,不够灵活

  • 2)字段打散
login:user:1:nameJack
login:user:1:name21

优点:可以灵活访问对象任意字段
缺点:占用空间大、没办法做统一控制

  • 3)hash(推荐)
login:user:1nameJack
age21

优点:底层使用ziplist,空间占用小,可以灵活访问对象的任意字段
缺点:代码相对复杂

7.1.3.2 存储hash数据

假设有一个hash类型的Key,其有10万对field和value,field是自增id,那这个Key存在什么问题?如何优化?

keyid:0value0
............
id:99999value99999
  • 1)存在的问题

hash的entry数量超过500时,会使用哈希表而不是ZipList,内存占用较多。Redis支持通过 hash-max-ziplist-entries 配置entry上限,但是如果entry过多就会导致BigKey问题。

这里可以编写一个单元测试,往Redis存入100000条数据:

@Test
public void testHashMemory() {
    for (int i = 0; i < 100000; i++) {
        jedis.hset("test:hash:memory", "key" + i, "value" + i);
    }
}

执行完毕后,查看这个Key的大小:

  • 2)方案一:拆分为string类型
id:0value0
............
id:99999value99999

但该方案也存在一些问题:string结构底层没有太多内存优化,内存占用较多;想要批量获取这些数据比较麻烦。

编写一个单元测试,往Redis存入100000条数据:

@Test
public void testHashMemory2() {
    for (int i = 0; i < 100000; i++) {
        jedis.set("test:hash:memory:" + i,  "value" + i);
    }
}

执行完毕后,查看这个Key的大小:

  • 3)方案二:拆分为小的hash
key:0id:00value0
............
id:99value99
key:1id:00value100
............
id:99value199
......
key:999id:00value99900
............
id:99value99999

编写一个单元测试,往Redis存入100000条数据:

@Test
public void testHashMemory3() {
    int hashSize = 100;
    Map<String, String> map = new HashMap<>(hashSize);
    for (int i = 1; i <= 100000; i++) {
        int k = (i - 1) / hashSize;
        int v = i % hashSize;
        map.put("key_" + v, "value_" + v);
        if (v == 0) {
            jedis.hmset("test:small:hash_" + k, map);
        }
    }
}

执行完毕后,查看这个Key的大小:

对比以上两种方案可以发现,拆分成小hash的方式最省空间。

7.1.4 小结

  • Key的最佳实践

    • 固定格式:[业务名]:[数据名]:[id]
    • 足够简短:不超过44字节
    • 不包含特殊字符
  • Value的最佳实践

    • 合理的拆分数据,拒绝BigKey
    • 选择合适数据结构
    • Hash结构的entry数量不要超过1000
    • 设置合理的超时时间

本节完。

更多内容请查阅分类专栏:Redis从入门到精通

本节所涉及的代码和资源可从git仓库下载:https://gitee.com/weidag/redis_learning.git

感兴趣的读者还可以查阅我的另外几个专栏:

  • SpringBoot源码解读与原理分析(已完结)
  • MyBatis3源码深度解析(已完结)
  • 再探Java为面试赋能(持续更新中…)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1604750.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SQL表连接详解:JOIN与逗号(,)的使用及其性能影响

省流版 在这个详细的解释中&#xff0c;我们将深入探讨SQL中表连接的概念&#xff0c;特别是JOIN和逗号&#xff08;,&#xff09;在连接表时的不同用法及其对查询性能的影响。通过实际示例和背后的逻辑分析&#xff0c;我们将揭示在不同场景下选择哪种连接方式更为合适。 1.…

RuoYi-Cloud部署实战(手动部署)

RuoYi-Cloud部署实战 语雀 1. 若依源码和架构 RuoYi-Cloud: &#x1f389; 基于Spring Boot、Spring Cloud & Alibaba的分布式微服务架构权限管理系统&#xff0c;同时提供了 Vue3 的版本 若依项目结构 带端口号的是需要启动的服务 com.ruoyi ├── ruoyi-ui …

(亲测有效)win7安装nodejs高版本(18.8.0)

现在学习vue3,vite,使用pnpm创建项目都需要高版本的nodejs了&#xff0c;win7最高只能安装13版本&#xff0c;好多已经不支持了。当然此篇只是以安装18.8.0为例&#xff0c;可以替换成更高的18或者20版本&#xff0c;只是太高的话可能出现冲突&#xff0c;够用就好。希望对各位…

管理能力学习笔记五:识别团队角色,因才施用

识别团队角色&#xff0c;因才施用&#xff0c;需要做到以下三点 扬长避短 管理者要学会问自己员工能把什么做好&#xff0c;而不是想方设法改造他们的短处 。 – 彼得德鲁克 人岗匹配 将合适的人放在合适的位置 人才多样化 团队需要各式各样的人才&#xff0c;才能高效配合…

【深度学习】Fine-Grained Face Swapping via Regional GAN Inversion高保真换脸范式

文章目录 代码介绍实践效果 帮助、问询 代码 https://github.com/e4s2022/e4s 介绍 Fine-Grained Face Swapping via Regional GAN Inversion 提出一种新的高保真换脸范式&#xff0c;能够保留期望的微妙几何和纹理细节。从微观面部编辑的角度重新思考换脸任务&#xff0c;基…

IO基础-传统I/O模型

关于IO数据流有两种形式&#xff0c;来源于网络和磁盘分别叫做网络IO、磁盘IO。 客户端通过TCP和UDP协议将数据流发往服务端&#xff0c;服务端接收数据这个过程称为网络IO。 服务端读取本地文件数据到服务中的过程称为磁盘IO。 基于 Linux 一切皆文件的理念&#xff0c;在内…

【Docker】Linux开放2735端口实现远程访问Docker

【Docker】Linux开放2735端口实现远程访问Docker 大家好 我是寸铁&#x1f44a; 总结了一篇【Docker】Linux开放2735端口实现远程访问Docker ✨ 喜欢的小伙伴可以点点关注 &#x1f49d; 前言 今天需要远程操作Linux服务器的docker&#xff0c;这时就需要开放出docker的端口给我…

TQ15EG开发板教程:在MPSOC上运行ADRV9371(vivado2018.3)

首先需要在github上下载两个文件&#xff0c;本例程用到的文件以及最终文件我都会放在网盘里面&#xff0c; 地址放在本文最后。首先在github搜索hdl选择第一个&#xff0c;如下图所示 GitHub网址&#xff1a;https://github.com/analogdevicesinc/hdl/releases 点击releases…

h2o-3机器学习平台连接mysql数据库

Getting Data into Your H2O Cluster — H2O 3.46.0.1 documentation 官方文档是这么说的&#xff01; 具体实操发现&#xff1a; java -jar build/h2o.jar 以上命令只能正常运行平台&#xff0c;无法连接数据库。 想要连接mysql数据集&#xff0c;首先需要自己下载mysql的…

【InternLM 实战营第二期-笔记3】茴香豆:搭建你的 RAG 智能助理

书生浦语是上海人工智能实验室和商汤科技联合研发的一款大模型,很高兴能参与本次第二期训练营&#xff0c;我也将会通过笔记博客的方式记录学习的过程与遇到的问题&#xff0c;并为代码添加注释&#xff0c;希望可以帮助到你们。 记得点赞哟(๑ゝω╹๑) 茴香豆&#xff1a;搭建…

损失函数:Cross Entropy Loss (交叉熵损失函数)

损失函数&#xff1a;Cross Entropy Loss &#xff08;交叉熵损失函数&#xff09; 前言相关介绍Softmax函数代码实例 Cross Entropy Loss &#xff08;交叉熵损失函数&#xff09;Cross Entropy Loss与BCE loss区别代码实例 前言 由于本人水平有限&#xff0c;难免出现错漏&am…

安全开发实战(2)---域名反查IP

目录 安全开发专栏 前言 域名与ip的关系 域名反查ip的作用 1.2.1 One 1.2.2 Two 1.2.3 批量监测 ​总结 安全开发专栏 安全开发实战http://t.csdnimg.cn/25N7H 这步是比较关键的一步,一般进行cdn监测后,获取到真实ip地址后,或是域名时,然后进行域名反查IP地址,进行进…

【剪映专业版】10时间线工具:主轨磁吸、自动吸附、联动、预览轴、全局缩放预览

视频课程&#xff1a;B站有知公开课【剪映电脑版教程】 主轨&#xff1a;有封面标志的轨道才是主轨。 主轨磁吸&#xff1a;开启后&#xff0c;在主轨上移动素材&#xff0c;自动向前磁吸&#xff0c;在其他轨道上移动无此效果&#xff1b;关闭后&#xff0c;不自动向前磁吸&…

python教学入门:字典和集合

字典&#xff08;Dictionary&#xff09;&#xff1a; 定义&#xff1a; 字典是 Python 中的一种数据结构&#xff0c;用于存储键值对&#xff08;key-value pairs&#xff09;。字典使用花括号 {} 定义&#xff0c;键值对之间用冒号 : 分隔&#xff0c;每对键值对之间用逗号…

150个 HTML5 成体系的网站模版 量大慢选 持续更新中

目录 HTML5 网站模版 No.1HTML5 网站模版 No.2HTML5 网站模版 No.3HTML5 网站模版 No.4HTML5 网站模版 No.5 HTML5 网站模版 No.1 HTML5 网站模版 No.1 HTML5 网站模版 No.2 HTML5 网站模版 No.2 HTML5 网站模版 No.3 HTML5 成体系网站模版 No.3 HTML5 网站模版…

虚拟机磁盘剩余空间不足

VMware 弹出提示&#xff1a; 对文件“E:\Virtual Machine\CentOS 7 1810 的克隆 (2)\CentOS 7 1810-cl1.vmdk”的操作失败。 如果该文件位于远程文件系统上&#xff0c;请确保网络连接以及该磁盘所在的服务器正常工作。如果该文件位于可移动介质中&#xff0c;请重新连接该介…

C语言-内存操作函数

C语言有一类内存函数&#xff0c;他们可以以字节为单位进行数据的拷贝、追加&#xff0c;甚至可以替代部分字符串函数。于是让我们来狠狠地学习它一百万遍吧~ 1.memcpy函数的使用和模拟实现 void * memcpy ( void * destination, const void * source, size_t num ); 1.1mem…

单调队列(C/C++)

引言&#xff1a; 单调队列和单调栈都是一种数据结构&#xff0c;应用十分广泛&#xff0c;在蓝桥杯、ICPC、CCPC等著名编程赛事都是重点的算法&#xff0c;今天博主将自己对单调栈与单调队列的理解以及刷题的经验&#xff0c;用一篇博客分享给大家&#xff0c;希望对大家有所…

springboot中mongodb连接池配置-源码分析

yml下spring.data.mongodb 以前mysql等在spring.xxx下配置&#xff0c;现在springboot新版本&#xff08;小编3.2.3&#xff09;在spring.data.xxx下了&#xff0c;如下所示&#xff0c;mongodb的配置在spring.data.mongodb下&#xff1a; 连接池相关参数配置-源码分析 拼接在…

使用CCS软件查看PID曲线

在刚开始学习PID的时候&#xff0c;都需要借助PID的曲线来理解比例&#xff0c;积分&#xff0c;微分这三个参数的具体作用。但是这些曲线生成一般都需要借助上位机软件或者在网页上才能实现。如果是在单片机上调试程序的话&#xff0c;想要看曲线&#xff0c;一般就是通过串口…