玩转Redis—如何高效访问Redis中的海量数据
mhr18 2024-10-26 10:48 30 浏览 0 评论
推荐阅读:
1、前言
??Redis以高性能著称,但性能再好,在面对海量数据时,若不正确的使用,也终将会有性能瓶颈,甚至造成服务宕机。
在实际项目中你是否会有以下疑问?
- 如何访问Redis中的海量数据,却不影响其他请求访问Redis?
- Redis中有百万/千万数据,如何高效访问?
- Redis中数据量太大,如何既保证快速访问,又不至于使服务宕机?
以上问题亦是Redis面试的高频问题。
2、思考
Q1:为什么Redis中的数据量很大时,某些数据操作会导致Redis卡顿,甚至宕机?
A1:Redis是单线程服务,所有指令都是顺序执行,当某一指令耗时很长时,就会阻塞后续的指令执行。当被积压的指令越来越多时,Redis服务占用CPU将不断升高,最终导致Redis实例崩溃甚至服务器宕机。
Q2:利用万能的keys命令查询任何想查的数据?
A2:自己电脑几万条数据玩玩就好了,线上使用keys命令,Excuse me?你想卷铺盖走人了吧。
++“某公司php工程师执行redis keys * 导致数据库宕机! 技术部发生2起本年度PO级特大事故,造成公司资金损失400万。”++ 这条新闻记忆犹新,警钟长鸣!
Q3:Redis中海量数据的正确操作方式
A3:利用SCAN系列命令(SCAN、SSCAN、HSCAN、ZSCAN)完成数据迭代。
??Redis的【SCAN系列命令】你了解多少呢?
3、SCAN系列命令详解
??SCAN系列命令,并不单纯指代SCAN命令,还包含SSCAN、HSCAN、ZSCAN,每种命令操作对象是有区别的,但用法及功能基本相同。
3.1、SCAN系列命令对比分析
- cursor:迭代游标;
- MATCH:数据匹配模式;
- COUNT:迭代返回数量;
3.2、SCAN系列命令注意事项
- SCAN的参数没有key,因为其迭代对象是DB内数据;
- 返回值都是数组,第一个值都是下一次迭代游标;
- 时间复杂度:每次请求都是O(1),完成所有迭代需要O(N),N是元素数量;
- 可用版本:version >= 2.8.0;
3.3、SCAN系列命令详解
3.3.1、 增量迭代,可用于生产环境
- 并不像KEYS、SMEMBERS一样是全量迭代,对大集合执行时可能阻塞服务很长时间;
3.3.2、不保证准确结果
- SMEMBERS可以返回整个set的元素,而SCAN这类增量迭代命令可能出现迭代过程中元素被改变,所以并不能保证准确的返回结果;
3.3.3、基于游标迭代
- SCAN基于游标迭代,每次请求将返回下一次需要使用的游标;
- 游标cursor可以比DB元素总量大,可以为负数;
- 错误游标:使用间断(不是迭代返回的)、负数、超出范围或其他非法游标,迭代不会报错,可能产生未定义行为(无法保证准确性);
3.3.4、迭代结束标记
- SCAN返回的游标不一定递增,某次迭代返回的元素数量可能为0;
- 返回元素列表为空,不代表迭代结束;
- 一个完整的迭代:SCAN游标从0开始,返回游标为0结束;
- 迭代状态由返回的游标控制。可以并发执行迭代;可随时终止迭代;
3.3.5、迭代完整性
- 遍历开始到遍历结束一直存在的数据,一定能被迭代返回;
- 同一个元素可能返回多次,数据去重应由应用程序完成;
- 在迭代过程中增删的元素,可能返回,可能不返回;
- 当数据类型是sets(由integer组成)、hashes、sorted sets且集合较小时,迭代将返回整个集合的数据,与count无关;
- 迭代结束保证:元素添加速率小于迭代速率。
3.3.6、why有时迭代直接返回整个集合
- 底层数据结构是hash时,如果数据量较小,Redis有内存优化策略,会使用紧凑的压缩编码。此时SCAN操作并不是返回有意义的游标,而是迭代整个集合;
- 数据量较小?参见官方memory-optimization(内存优化)说明。
3.3.7、参数count说明
- count默认值是10;
- 数据集较大时,如果没有使用match,返回元素为count或比count略大;
- 每次迭代的count参数值可以不同,只要使用上次迭代返回的游标即可;
3.3.8、参数match说明
- 和keys的pattern类似;
- MATCH操作是在检索出数据到返回元素前的期间执行,所以如果被匹配的元素较少,那么可能多次迭代返回的元素列表均为空;
4、SCAN系列命令示例
4.1、SCAN示例
??详见《5.2、部分问题解答》
4.2、SSCAN示例
// SSCAN示例 @zxiaofan 127.0.0.1:6378> SADD sscantest sscantest:1 1 sscantest:2 2 sscantest:3 3 sscantest:4 4 sscantest:1a 1a sscantest:2a 2a sscantest:1ab 1ab sscantest:a1 a1 sscantest:aa1 aa1 (integer) 0 // MATCH ?:无匹配数据 127.0.0.1:6378> SSCAN sscantest 0 MATCH ? COUNT 1 1) "24" 2) (empty list or set) 127.0.0.1:6378> SSCAN sscantest 24 MATCH ? COUNT 1 1) "20" 2) (empty list or set) 127.0.0.1:6378> SSCAN sscantest 0 MATCH * COUNT 1 1) "24" 2) 1) "sscantest:3" 2) "sscantest:2a" 127.0.0.1:6378> SSCAN sscantest 24 MATCH * COUNT 1 1) "20" 2) 1) "a1"
4.3、HSCAN示例
// HSCAN示例 @zxiaofan 127.0.0.1:6378> HMSET hscantest hscantest:1 1 hscantest:2 2 hscantest:3 3 hscantest:4 4 hscantest:1a 1a hscantest:2a 2a hscantest:1ab 1ab hscantest:a1 a1 hscantest:aa1 aa1 OK 127.0.0.1:6378> HSCAN hscantest 0 MATCH hscantest*a COUNT 20 1) "0" 2) 1) "hscantest:1a" 2) "1a" 3) "hscantest:2a" 4) "2a" 127.0.0.1:6378> HSCAN hscantest 0 MATCH hscantest*a COUNT 2 1) "0" 2) 1) "hscantest:1a" 2) "1a" 3) "hscantest:2a" 4) "2a" 127.0.0.1:6378>
??从HSCAN示例可以看出,即使count参数为2,也返回了所有匹配的结果。这就是先前提到的,数据量较小时,直接返回所有数据。
4.4、ZSCAN示例
// ZSCAN示例 @zxiaofan // 【移除】并弹出count个分数最大的元素,count默认为1 127.0.0.1:6378> ZPOPMAX zscantest 20 1) "sscantest:1ab" 2) "6" 3) "sscantest:2a" 4) "5" 5) "sscantest:1a" 6) "4" 7) "sscantest:3" 8) "3" 9) "zscantest:1" 10) "2" 11) "sscantest:2" 12) "2" 13) "test1" 14) "1" 15) "sscantest:1" 16) "1" 127.0.0.1:6378> ZPOPMAX zscantest 20 (empty list or set) 127.0.0.1:6378> ZADD zscantest 1 zscantest:1 2 zscantest:2 3 zscantest:3 4 zscantest:1a 5 zscantest:2a 6 zscantest:1ab 7 zscantest:a1 8 zscantest:aa1 (integer) 8 // NX:不存在才添加;CH:返回被改变(含新增)的元素个数 127.0.0.1:6378> ZADD zscantest NX CH 1 test1 2 zscantest:1 (integer) 1 127.0.0.1:6378> ZSCAN zscantest 0 MATCH *a COUNT 5 1) "0" 2) 1) "zscantest:1a" 2) "4" 3) "zscantest:2a" 4) "5" 127.0.0.1:6378>
5、总结
5.1、看看面试时你能答上几个问题
- SCAN迭代可以并发吗?
- SCAN返回数据为空就是迭代结束了吗?
- 如果首次迭代cursor参数不是0,能实现完整迭代吗?
- 可以严格控制每次迭代返回的数据量吗?
- 迭代返回的数据一定完整吗?
- 为什么迭代返回的元素列表可能为空?
5.2、部分问题解答
5.2.1、SCAN返回数据为空就是迭代结束了吗
// SCAN返回数据为空就是迭代结束了吗? @zxiaofan 127.0.0.1:6378> keys k? 1) "k1" 2) "k2" 127.0.0.1:6378> SCAN 0 MATCH k? 1) "88" 2) (empty list or set) 127.0.0.1:6378> SCAN 88 MATCH k? 1) "34" 2) 1) "k1" 127.0.0.1:6378> SCAN 34 MATCH k? 1) "122" 2) (empty list or set) 127.0.0.1:6378> SCAN 122 MATCH k? 1) "14" 2) (empty list or set) 127.0.0.1:6378> SCAN 14 MATCH k? 1) "33" 2) (empty list or set) 127.0.0.1:6378> SCAN 33 MATCH k? 1) "53" 2) (empty list or set) 127.0.0.1:6378> SCAN 53 MATCH k? 1) "93" 2) (empty list or set) 127.0.0.1:6378> SCAN 93 MATCH k? 1) "107" 2) 1) "k2" 127.0.0.1:6378> SCAN 107 MATCH k? 1) "79" 2) (empty list or set) 127.0.0.1:6378> SCAN 79 MATCH k? 1) "0" 2) (empty list or set) 127.0.0.1:6378>
??看上述示例,匹配“k?”的数据实际有2条“k1”、“k2”,在整个迭代过程中,多次返回数据为空,但是迭代未曾结束(因为“k1”、“k2”没有全部迭代返回)。
??所以,只有当游标返回为0时,才能说明迭代结束了。
5.2.2、如果首次迭代cursor参数不是0,能实现完整迭代吗?
// 如果首次迭代cursor参数不是0,能实现完整迭代吗? @zxiaofan 127.0.0.1:6378> keys k? 1) "k1" 2) "k2" 127.0.0.1:6378> SCAN 66 MATCH k? 1) "122" 2) (empty list or set) 127.0.0.1:6378> SCAN 122 MATCH k? 1) "14" 2) (empty list or set) 127.0.0.1:6378> SCAN 14 MATCH k? 1) "33" 2) (empty list or set) 127.0.0.1:6378> SCAN 33 MATCH k? 1) "53" 2) (empty list or set) 127.0.0.1:6378> SCAN 53 MATCH k? 1) "93" 2) (empty list or set) 127.0.0.1:6378> SCAN 93 MATCH k? 1) "107" 2) 1) "k2" 127.0.0.1:6378> SCAN 107 MATCH k? 1) "79" 2) (empty list or set) 127.0.0.1:6378> SCAN 79 MATCH k? 1) "0" 2) (empty list or set) 127.0.0.1:6378>
??看上述示例,匹配“k?”的数据实际有2条“k1”、“k2”,当第一次SCAN使用cursor为66,我们可以发现经过多次迭代,游标返回为0时,“k1”一直未曾被迭代返回。
??所以,如果首次迭代cursor参数不是0,不能实现完整迭代。
??完整迭代必须是游标从0开始,游标到0结束。
6、后记
??本文针对Redis的SCAN系列命令做了详细的对比分析以及实际使用示例,并整理了面试中的高频问题。建议阅读本文的同学实际动手练习下,效果更好。
作者:zxiaofan
链接:https://juejin.im/post/5dd10fde518825291f38e7a8
相关推荐
- 如何检查 Linux 服务器是物理服务器还是虚拟服务器?
-
在企业级运维、故障排查和性能调优过程中,准确了解服务器的运行环境至关重要。无论是物理机还是虚拟机,都存在各自的优势与限制。在很多场景下,尤其是当你继承一台服务器而不清楚底层硬件细节时,如何快速辨识它是...
- 第四节 Windows 系统 Docker 安装全指南
-
一、Docker在Windows上的运行原理(一)架构限制说明Docker本质上依赖Linux内核特性(如Namespaces、Cgroups等),因此在Windows系统上无法直...
- C++ std:shared_ptr自定义allocator引入内存池
-
当C++项目里做了大量的动态内存分配与释放,可能会导致内存碎片,使系统性能降低。当动态内存分配的开销变得不容忽视时,一种解决办法是一次从操作系统分配一块大的静态内存作为内存池进行手动管理,堆对象内存分...
- Activiti 8.0.0 发布,业务流程管理与工作流系统
-
Activiti8.0.0现已发布。Activiti是一个业务流程管理(BPM)和工作流系统,适用于开发人员和系统管理员。其核心是超快速、稳定的BPMN2流程引擎。Activiti可以...
- MyBatis动态SQL的5种高级玩法,90%的人只用过3种
-
MyBatis动态SQL在日常开发中频繁使用,但大多数开发者仅掌握基础标签。本文将介绍五种高阶技巧,助你解锁更灵活的SQL控制能力。一、智能修剪(Trim标签)应用场景:动态处理字段更新,替代<...
- Springboot数据访问(整合Mybatis Plus)
-
Springboot整合MybatisPlus1、创建数据表2、引入maven依赖mybatis-plus-boot-starter主要引入这个依赖,其他相关的依赖在这里就不写了。3、项目结构目录h...
- 盘点金州勇士在奥克兰13年的13大球星 满满的全是...
-
见证了两个月前勇士与猛龙那个史诗般的系列赛后,甲骨文球馆正式成为了历史。那个大大的红色标志被一个字母一个字母地移除,在周四,一切都成为了过去式。然而这座,别名为“Roaracle”(译注:Roar怒吼...
- Mybatis入门看这一篇就够了(mybatis快速入门)
-
什么是MyBatisMyBatis本是apache的一个开源项目iBatis,2010年这个项目由apachesoftwarefoundation迁移到了googlecode,并且改名为M...
- Springboot数据访问(整合druid数据源)
-
Springboot整合druid数据源基本概念SpringBoot默认的数据源是:2.0之前:org.apache.tomcat.jdbc.pool.DataSource2.0及之后:com.z...
- Linux 中的 "/etc/profile.d" 目录有什么作用 ?
-
什么是/etc/profile.d/目录?/etc/profile.d/目录是Linux系统不可或缺的一部分保留配置脚本。它与/etc/profile文件相关联,这是一个启动脚本,该脚...
- 企业数据库安全管理规范(企业数据库安全管理规范最新版)
-
1.目的为规范数据库系统安全使用活动,降低因使用不当而带来的安全风险,保障数据库系统及相关应用系统的安全,特制定本数据库安全管理规范。2.适用范围本规范中所定义的数据管理内容,特指存放在信息系统数据库...
- Oracle 伪列!这些隐藏用法你都知道吗?
-
在Oracle数据库中,有几位特殊的“成员”——伪列,它们虽然不是表中真实存在的物理列,但却能在数据查询、处理过程中发挥出意想不到的强大作用。今天给大家分享Oracle伪列的使用技巧,无论...
- Oracle 高效处理数据的隐藏神器:临时表妙用
-
各位数据库搬砖人,在Oracle的代码世界里闯荡,处理复杂业务时,是不是总被数据“搅得头大”?今天给大家安利一个超实用的隐藏神器——临时表!当你需要临时存储中间计算结果,又不想污染正式数据表...
- Oracle 数据库查询:多表查询(oracle多表关联查询)
-
一、多表查询基础1.JOIN操作-INNERJOIN:返回两个表中满足连接条件的匹配行,不保留未匹配数据。SELECTa.emp_id,b.dept_nameFROMempl...
- 一文掌握怎么利用Shell+Python实现多数据源的异地备份程序
-
简介:在信息化时代,数据安全和业务连续性已成为企业和个人用户关注的焦点。无论是网站数据、数据库、日志文件,还是用户上传的文档、图片等,数据一旦丢失,损失难以估量。尤其是当数据分布在多个不同的目录、服务...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- 如何检查 Linux 服务器是物理服务器还是虚拟服务器?
- 第四节 Windows 系统 Docker 安装全指南
- C++ std:shared_ptr自定义allocator引入内存池
- Activiti 8.0.0 发布,业务流程管理与工作流系统
- MyBatis动态SQL的5种高级玩法,90%的人只用过3种
- Springboot数据访问(整合Mybatis Plus)
- 盘点金州勇士在奥克兰13年的13大球星 满满的全是...
- Mybatis入门看这一篇就够了(mybatis快速入门)
- Springboot数据访问(整合druid数据源)
- Linux 中的 "/etc/profile.d" 目录有什么作用 ?
- 标签列表
-
- oracle位图索引 (74)
- oracle基目录 (50)
- oracle批量插入数据 (65)
- oracle事务隔离级别 (53)
- oracle主从同步 (55)
- oracle 乐观锁 (51)
- redis 命令 (78)
- php redis (88)
- redis 存储 (66)
- redis 锁 (69)
- 启动 redis (66)
- redis 时间 (56)
- redis 删除 (67)
- redis内存 (57)
- redis并发 (52)
- redis 主从 (69)
- redis 订阅 (51)
- redis 登录 (54)
- redis 面试 (58)
- 阿里 redis (59)
- redis 搭建 (53)
- redis的缓存 (55)
- lua redis (58)
- redis 连接池 (61)
- redis 限流 (51)