Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >使用Redis的HSCAN命令遇到的一个问题

使用Redis的HSCAN命令遇到的一个问题

作者头像
Throwable
发布于 2020-06-23 08:08:56
发布于 2020-06-23 08:08:56
4.2K00
代码可运行
举报
文章被收录于专栏:Throwable's BlogThrowable's Blog
运行总次数:0
代码可运行

前提

笔者最近在做一个项目时候使用Redis存放客户端展示的订单列表,列表需要进行分页。由于笔者先前对Redis的各种数据类型的使用场景并不是十分熟悉,于是先入为主地看到Hash类型:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
USER_ID:1
   ORDER_ID:ORDER_XX: {"amount": "100","orderId":"ORDER_XX"}
   ORDER_ID:ORDER_YY: {"amount": "200","orderId":"ORDER_YY"}

感觉Hash类型完全满足需求实现的场景。然后想当然地考虑使用HSCAN命令进行分页,引发了后面遇到的问题。

SCAN和HSCAN命令

SCAN命令如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
SCAN cursor [MATCH pattern] [COUNT count] [TYPE type]
// 返回值如下:
// 1. cursor,数值类型,下一轮的起始游标值,0代表遍历结束
// 2. 遍历的结果集合,列表

SCAN命令在Redis2.8.0版本中新增,时间复杂度计算如下:每一轮遍历的时间复杂度为O(1),所有元素遍历完毕直到游标cursor返回0的时间复杂度为O(N),其中N为集合内元素的数量。SCAN是针对整个Database内的所有KEY进行渐进式的遍历,它不会阻塞Redis,也就是使用SCAN命令遍历KEY的性能会优于KEY *命令。对于Hash类型有一个衍生的命令HSCAN专门用于遍历Hash类型及其相关属性(Field)的字段:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
HSCAN key cursor [MATCH pattern] [COUNT count]
// 返回值如下:
// 1. cursor,数值类型,下一轮的起始游标值,0代表遍历结束
// 2. 遍历的结果集合,是一个映射

笔者当时没有详细查阅Redis的官方文档,想当然地认为Hash类型的分页简单如下(假设每页数据只有1条):

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 第一页
HSCAN USER_ID:1 0 COUNT 1    <= 这里认为返回的游标值为1
// 第二页
HSCAN USER_ID:1 1 COUNT 1    <= 这里认为返回的游标值为0,结束迭代

实际上,执行的结果如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
HSCAN USER_ID:1 0 COUNT 1

// 结果
0 
 ORDER_ID:ORDER_XX
 {"amount": "100","orderId":"ORDER_XX"}
 ORDER_ID:ORDER_YY
 {"amount": "200","orderId":"ORDER_YY"}

也就是在第一轮遍历的时候,KEY对应的所有Field-Value已经全量返回。笔者尝试增加哈希集合KEY = USER_ID:1里面的元素,但是数据量相对较大的时候,依然没有达到预期的分页效果;另一个方面,尝试修改命令中的COUNT值,发现无论如何修改COUNT值都不会对遍历的结果产生任何影响(也就是还是在第一轮迭代返回全部结果)。百思不得其解的情况下,只能仔细翻阅官方文档寻找解决方案。在SCAN命令的COUNT属性描述中找到了原因:

简单翻译理解一下:

SCAN命令以及其衍生命令并不保证每一轮迭代返回的元素数量,但是可以使用COUNT属性凭经验调整SCAN命令的行为。COUNT指定每次调用应该完成遍历的元素的数量,以便于遍历集合,本质只是一个提示值。

  1. COUNT默认值为10。
  2. 当遍历的目标SetHashSorted Set或者Key空间足够大可以使用一个哈希表表示并且不使用MATCH属性的前提下,Redis服务端会返回COUNT或者比COUNT大的遍历元素结果集合。
  3. 当遍历只包含Integer值的Set集合(也称为intsets),或者ziplists类型编码的Hash或者Sorted Set集合(说明这些集合里面的元素占用的空间足够小),那么SCAN命令会返回集合中的所有元素,直接忽略COUNT属性。

注意第3点,这个就是在Hash集合中使用HSCAN命令COUNT属性失效的根本原因。Redis配置中有两个和Hash类型ziplist编码的相关配置值:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
hash-max-ziplist-entries 512
hash-max-ziplist-value 64

在如下两个条件之一满足的时候,Hash集合的编码会由ziplist会转成dict

  • Hash集合中的数据项(即Field-Value对)的数目超过512的时候。
  • Hash集合中插入的任意一个Field-Value对中的Value长度超过64。

Hash集合的编码会由ziplist会转成dictRedisHash类型的内存空间占用优化相当于失败了,降级为相对消耗更多内存的字典类型编码,这个时候,HSCAN命令COUNT属性才会起效。

案例验证

简单验证一下上一节得出的结论,写入一个测试数据如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 70个X
HSET USER_ID:2 ORDER_ID:ORDER_XXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX   
// 70个Y
HSET USER_ID:2 ORDER_ID:ORDER_YYY YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY

接着开始测试一下HSCAN命令:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 查看编码
object encoding USER_ID:2
// 编码结果
hashtable

// 第一轮迭代
HSCAN USER_ID:2 0 COUNT 1
// 第一轮迭代返回结果
2 
 ORDER_ID:ORDER_YYY
 YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY

// 第二轮迭代 
HSCAN USER_ID:2 2 COUNT 1
0 
 ORDER_ID:ORDER_XXX
 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

测试案例中故意让两个值的长度为70,大于64,也就是让Hash集合转变为dict(hashtable)类型,使得COUNT属性生效。但是,这种做法是放弃了RedisHash集合的内存优化。显然,HSCAN命令天然不是为了做数据分页而设计的,而是为了渐进式的迭代(也就是如果需要迭代的集合很大,也不会阻塞Redis服务)。所以笔者最后放弃了使用HSCAN命令,寻找更适合做数据分页查询的其他Redis命令。

小结

通过这简单的踩坑案例,笔者得到一些经验:

  • 切忌先入为主,使用中间件的时候要结合实际的场景。
  • 使用工具的之前要仔细阅读工具的使用手册。
  • 要通过一些案例验证自己的猜想或者推导的结果。

Redis提供的API十分丰富,后面应该还会遇到更多的踩坑经验。

附件

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019年8月12日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
细探Redis scan命令
这周遇到的一个小需求是通过Go实现对Redis的hash field实时上限检查,而因为是线上的服务,所以这个上限检查不能对redis pod造成负担,跟组内导师交流学习后了解到可以通过redis的HScan命令来实现这个需求
Kevinello
2022/08/19
1.2K0
细探Redis scan命令
Redis中Scan命令的基本用法
SCAN 命令以及比较相近的 SSCAN、HSCAN 和 ZSCAN 命令都用于增量迭代数据集元素:
smartsi
2019/12/17
7.4K0
Redis 基本数据结构三:哈希
几乎所有的编程语言都提供了哈希(hash)类型,例如 Java 中的 Map,python 中的字典,在Redis中,哈希类型是指键的值本身又是一个键值对结构,如下图所示:
CoderJed
2019/07/12
5680
Redis 基本数据结构三:哈希
Redis SCAN的使用
有时候需要从 Redis 实例成千上万的 key 中找出特定前缀的 key 列表来手动处理数据,可能是修改它的值,也可能是删除 key。这里就有一个问题,如何从海量的 key 中找出满足特定前缀的 key 列表来?
小歪
2019/09/26
3.1K0
Redis之MoreKey问题及Scan命令解读
对于海量数据执行key *会造成严重服务卡顿、影响业务。在实际环境中最好不要使用。生产制造过程中keys * / flushdb/flushall等危险命令以防止误删误用。
一个风轻云淡
2023/09/24
2740
redis scan命令
redis是当前使用很广泛的一款内存key-value软件,有时需要查询下目前redis内存里面有哪些满足条件的键值,在线上系统一定不要使用keys命令,该命令会导致redis停止一切工作,如果redis键值要很多的话会导致redis长时间内都不会响应其他任何命令,这个时候scan命令就呼之欲出了,scan命令可以达到keys命令的效果同时又不会redis出现类似stop-the-world事件的发生,
johnhuster的分享
2022/03/29
6120
Redis 概览
KEYS h[ae]llo 匹配 hello 和 hallo ,但不匹配 hillo 。
三产
2021/01/12
5680
因为不会Redis的scan命令,我被开除了
那个深夜,我登上了公司的服务器,在Redis 命令行里敲入 keys* 后,线上开始报警,服务瞬间被卡死,我只能举起双手,焦急地等待几千万key被慢慢扫描,束手无策万念俱灰的时候,我收到了leader的短信:你明天不用来上班了。
帅地
2019/12/11
1.4K0
因为不会Redis的scan命令,我被开除了
Redis命令:scan实现模糊查询
从Redis v2.8开始,SCAN命令已经可用,它允许使用游标从keyspace中检索键。 对比KEYS命令,虽然SCAN无法一次性返回所有匹配结果,但是却规避了阻塞系统这个高风险,从而也让一些操作可以放在主节点上执行。
用户1205080
2018/12/10
6.3K0
高并发系统设计-redis技术梳理
架构师就是梳理技术,整理文档,落地技术方案,首先架构师需要梳理下redis能为我们解决什么问题,以及redis的技术门槛,redis的优势和缺点。
35岁程序员那些事
2020/02/24
1.1K0
Redis删除特定前缀key的优雅实现
Redis中没有批量删除特定前缀key的指令,但我们往往需要根据前缀来删除,那么究竟该怎么做呢?可能你一通搜索后会得到下边的答案
37丫37
2019/06/17
10.6K0
Redis中Scan命令的使用
Redis中有一个经典的问题,在巨大的数据量的情况下,做类似于查找符合某种规则的Key的信息,这里就有两种方式, 一是keys命令,简单粗暴,由于Redis单线程这一特性,keys命令是以阻塞的方式执行的,keys是以遍历的方式实现的复杂度是 O(n),Redis库中的key越多,查找实现代价越大,产生的阻塞时间越长。
星哥玩云
2022/08/18
1.2K0
Redis中Scan命令的使用
Redis:04---键的基本命令(上)
第二次执行“scan 10”,因为上一次返回10,所以从“cursor=10”开始扫描,结果如下所示,返回10个键
用户3479834
2021/02/03
4950
Redis:04---键的基本命令(上)
【实战】如何使用 Python 从 Redis 中删除 4000万 KEY
本文主要涉及 Redis 的以下两个操作和其 Python 实现,目录: SCAN 命令 DEL 命令 使用 Python SCAN 使用 Python DEL 成果展示 ---- SCAN 命令 SCAN 命令及相关的 SSCAN、HSCAN 和 ZSCAN 命令都用于增量迭代(incrementally iterate)一个集合的元素(a collection of elements): SCAN 用于迭代当前数据库中的数据库键 SSCAN 用于迭代集合键中的元素 HSCAN 用于迭代哈希键中的键值对
临书
2018/03/07
8.8K0
【实战】如何使用 Python 从 Redis 中删除 4000万 KEY
Redis Hash(Hash) 复习
哈希相当于一个二维数组,内部是无序字典。 哈希也是是一个 string 类型的 field(字段) 和 value(值) 的映射表,所以哈希特别适合用于存储对象。
陈大剩博客
2023/03/06
1.7K0
Redis Hash(Hash) 复习
Redis如何删除数量过万以上Key而不影响业务
有时候因为 Redis Key 没有设置过期时间或者因为业务需求或者Redis内存不足或者修改Redis Key值等需求,并且这些Key是有规律的,可以通过正则表达式来匹配。
YP小站
2021/04/07
4.7K0
Redis 哈希
{key:{field1:value1,field2:value2,...fieldN:valueN}}
三产
2021/01/12
3590
Redis系列(十二)scan Info Object等特殊命令集合
在上一篇文章中,介绍了 Redis 的所有命令的基本含义及其用法。但是 Redis 的命令太多,导致上一篇文章只能简单的进行总结,而有一些命令是那么简单的话语总结不了的,因此在这里单独的进行讲解。
呼延十
2020/09/19
1.1K0
redis keys和scan命令
1.自1.0.0起可用。 时间复杂度: O(N),其中N为数据库中密钥的数目,假设数据库中的密钥名称和给定模式的长度有限。 返回所有匹配的键pattern。
袁新栋-jeff.yuan
2020/08/26
9760
误用Redis命令导致服务器挂了,领导让我写事故报告
由于业务需要,会定时更新一批缓存的数据,但是一个个获取key效率低下,所以就想到了批量获取的思路,然后就使用了keys命令,在本地或者测试环境下,由于缓存中key的数量并不是那么多,所以没有出现缓存挂或者服务器宕机的情况,但是随着历史数据的增加和业务的增长,缓存中的key越来越多,达到了几百万甚至上千万,所以使用keys命令的时候,查询出来的符合查询规则的数据量也非常大,导致服务器阻塞,随后宕机!
码哥字节
2021/01/13
6470
相关推荐
细探Redis scan命令
更多 >
领券
💥开发者 MCP广场重磅上线!
精选全网热门MCP server,让你的AI更好用 🚀
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验