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

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介: 笔者最近在做一个项目时候使用Redis存放客户端展示的订单列表,列表需要进行分页。由于笔者先前对Redis的各种数据类型的使用场景并不是十分熟悉,于是先入为主地看到Hash类型,感觉Hash类型完全满足需求实现的场景。然后想当然地考虑使用HSCAN命令进行分页,引发了后面遇到的问题。

前提



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


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命令如下:


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)的字段:


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


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


// 第一页
HSCAN USER_ID:1 0 COUNT 1    <= 这里认为返回的游标值为1
// 第二页
HSCAN USER_ID:1 1 COUNT 1    <= 这里认为返回的游标值为0,结束迭代
复制代码


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


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属性描述中找到了原因:


微信截图_20220512180731.png


简单翻译理解一下:

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编码的相关配置值:


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属性才会起效。


案例验证



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


// 70个X
HSET USER_ID:2 ORDER_ID:ORDER_XXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX   
// 70个Y
HSET USER_ID:2 ORDER_ID:ORDER_YYY YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY
复制代码


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


// 查看编码
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十分丰富,后面应该还会遇到更多的踩坑经验。


附件




相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
1月前
|
NoSQL Redis 数据库
10- 你们用过Redis的事务吗 ? 事务的命令有哪些 ?
```markdown Redis事务包括MULTI、EXEC、DISCARD、WATCH四个命令。虽具备事务功能,但在实际开发中使用较少。 ```
43 7
|
1月前
|
NoSQL Redis 数据库
Redis的全局命令及相关误区
Redis的全局命令及相关误区
24 0
|
1月前
|
存储 缓存 NoSQL
深入了解Redis键管理:探索Redis键命令及其功能与应用场景
深入了解Redis键管理:探索Redis键命令及其功能与应用场景
|
1月前
|
NoSQL Redis 数据库
Redis中的常用命令有哪些?
Redis常用命令包括:PING测试连接,ECHO回显,SELECT切换数据库,QUIT关闭连接;KEYS查找key,EXISTS检查存在,DEL删除key,EXPIRE设置过期时间,TTL查看剩余生存时间,TYPE检测数据类型;STRING操作如SET/GET/MSET/MGET,INCR/DECR增减计数;HASH命令如HSET/HGET/HMSET/HMGET/HGETALL管理字段;LIST操作如LPUSH/RPUSH/LPOP/RPOP/LRANGE;
13 0
|
2天前
|
存储 NoSQL Redis
Redis基础命令集详解
Redis基础命令集详解
9 1
|
3天前
|
存储 NoSQL Redis
Redis 常用命令
Redis 常用命令
10 0
|
4天前
|
存储 NoSQL Redis
深入浅出Redis(零):Redis常用命令的使用
深入浅出Redis(零):Redis常用命令的使用
|
4天前
|
存储 NoSQL 关系型数据库
深入浅出Redis(十二):Redis的排序命令Sort
深入浅出Redis(十二):Redis的排序命令Sort
|
4天前
|
NoSQL Linux Redis
Redis的介绍,以及Redis的安装(本机windows版,虚拟机Linux版)和Redis常用命令的介绍
Redis的介绍,以及Redis的安装(本机windows版,虚拟机Linux版)和Redis常用命令的介绍
17 0
|
8天前
|
存储 NoSQL 定位技术
Redis常用数据类型及常用命令
这些是Redis中常用的数据类型和命令。Redis还提供了许多其他命令和功能,用于数据存储、操作和查询。你可以根据需要选择适当的数据类型和命令来满足你的应用程序需求。
21 4