Redis 8.10 引入了一个新的 Hash 数据导入命令:
HIMPORT。
如果应用需要批量向 Redis 写入大量结构相同的 Hash,传统的
HSET 会反复传输相同的字段名。
HIMPORT 的核心思路,就是提前声明这组字段结构,后续写入时只传输对应的值,从而减少网络传输数据量,提高批量导入效率。
本文通过 Ruby 客户端
redis-rb 演示
HIMPORT 的基本用法,以及它与
HSET 在批量写入场景下的性能差异。
本文使用 Ruby 主要有两个原因:
redis-rb 最近发布了新版本;HIMPORT 命令。在 Ruby 中连接 Redis 很简单,只需要安装
redis gem,并准备一个 Redis OSS 服务端即可。
redis = Redis.new( host: options[:host], port: options[:port], username: options[:user], password: options[:password], himport_auto_prepare: true )
这里的
himport_auto_prepare 默认值就是
true,之所以显式写出来,是因为它与后面的连接恢复机制有关。
假设需要向 Redis 写入一批玩家成绩数据,每条记录都包含三个字段:
_uid:玩家 IDscore:玩家取得的分数tag:玩家标签使用
HSET 时,代码可能类似这样:
redis.hset("scores:68430017", "_uid", "68430017", "score", 42, "tag", "vladvildanov")
...
redis.hset("scores:03DBA163", "_uid", "03DBA163", "score", 234, "tag", "nosqlgeek")
可以看到,每写入一条记录,不仅要发送实际数据,还需要重复发送
_uid、
score、
tag 这些字段名。
当所有 Hash 都采用相同结构时,这部分重复数据实际上是可以避免的。
这正是
HIMPORT 要解决的问题。
HIMPORT 提供了多个子命令,其中首先需要了解的是
HIMPORT PREPARE。
它允许客户端提前声明一个字段集合(field set)。例如,可以创建一个名为
scores 的字段集合:
_uid score tag
随后,批量导入数据时只需要按照同样的字段顺序提供值即可。
使用
redis-rb 可以这样实现:
redis.himport_prepare("scores", %w[_uid score tag])
(1..NUM_IMPORTS).each { |i|
uuid, score, tag = random_demo_data
redis.himport_set(uuid, "scores", [uuid, score, tag])
}
redis.himport_discard("scores")
这里首先通过:
redis.himport_prepare("scores", %w[_uid score tag])
注册字段结构。
之后每次执行
himport_set 时,只需要指定目标 Key、字段集合名称以及对应的值:
redis.himport_set(uuid, "scores", [uuid, score, tag])
字段名本身不再随着每条记录重复传输。
需要特别注意的是:通过
HIMPORT PREPARE 创建的字段集合只在执行该命令的当前 Redis 连接上下文中有效。
这时就需要重新看一下前面提到的:
himport_auto_prepare: true
redis-rb 客户端内部会维护一个已准备的 HIMPORT 字段集合注册表。
如果 Redis 连接断开后重新建立,客户端会自动重新注册此前准备好的字段集合。这样,应用就不需要在每次连接恢复之后自行重新执行
HIMPORT PREPARE。
当某个字段集合不再需要时,可以执行:
redis.himport_discard("scores")
将其删除。
执行 discard 后,该字段集合也会从客户端维护的注册表中移除。
示例项目中的 CLI 程序
himport_demo.rb 提供了一种 inline 模式:
ruby himport_demo.rb inline
这种方式模拟的是一种长期运行的应用场景:
应用启动时预先声明常用字段集合,之后在整个应用生命周期内都可以使用对应的
HIMPORT 写入数据。
借助
redis-rb 内部维护的字段集合注册表,
HIMPORT 可以在这类场景中作为
HSET 的一种替代方式。
如果采用这种模式,建议保持:
himport_auto_prepare=true
这样即使底层 Redis 连接发生重连,客户端也能够自动恢复此前注册的字段集合。
需要注意,
redis-rb 本身没有内置连接池。如果按照项目 README 中的方式额外使用连接池,那么必须保证连接池中的
每一条 Redis 连接都分别执行过字段集合 Prepare。
原因很直接:
HIMPORT PREPARE 的状态属于连接上下文,而不是整个客户端实例或 Redis Server 的全局状态。
另一种更典型的使用方式,是将
Prepare、批量写入和
Discard 放在同一个 Pipeline 中完成。
Redis Pipelining 可以一次发送多个命令,而不必在每个命令执行后等待服务器响应再发送下一个命令,因此非常适合批量数据导入。
同时,一个 Pipeline 始终运行在同一条 Redis 连接上,这正好满足
HIMPORT PREPARE 的连接级作用域要求。
这种情况下,可以将:
himport_auto_prepare
设置为
false。
示例程序中的 bulk 模式实现如下:
result = redis.pipelined do |pipeline|
pipeline.himport_prepare("scores", %w[_uid score tag])
(1..NUM_IMPORTS).each { |i|
uuid, score, tag = random_demo_data
pipeline.himport_set(uuid, "scores", [uuid, score, tag])
}
pipeline.himport_discard("scores")
end
整个过程非常明确:
HIMPORT SET;如果字段结构只是某次批量导入任务临时使用,这种方式通常更加合适。
示例 CLI 还包含一个简单的 Benchmark,用于比较
HSET 和
HIMPORT 的批量写入性能。
测试过程保持一致:
HSET;HIMPORT 重复同样的测试。测试在本地主机上进行,字段集合只有三个字段,总计写入 100 万条记录:
ruby himport_demo.rb benchmark Benchmarking 1000000 records ... HSET: 1000000 records in 12.029s HIMPORT: 1000000 records in 10.589s
结果显示:
HSET:12.029 秒HIMPORT:10.589 秒在这次测试中,
HIMPORT 的导入速度提高了约 11%。
需要注意,这里的字段集合只有三个字段。
HIMPORT 的主要收益来自减少网络中重复传输的字段名,因此字段数量越多、单批数据量越大,理论上可以节省的网络带宽也越明显,相应的性能收益也可能进一步增加。
换句话说,
HIMPORT 并不是通过改变 Hash 本身的数据模型来提升效率,而是在大量结构一致的 Hash 写入场景中,减少协议层面的重复数据传输。
从其工作方式来看,
HIMPORT 最适合这样的数据写入模式:
大量 Redis Hash 具有相同或高度固定的字段结构,并且需要进行持续或批量写入。
例如前面的玩家成绩记录:
_uid score tag
如果需要导入几十万甚至上百万条结构相同的数据,那么使用
HSET 时,每一条命令都会重复携带这些字段名。
而通过
HIMPORT,字段名只需要提前声明一次,后续主要传输真正变化的数据值。
对于字段较多、数据规模较大的批量导入任务,这种差异会更加明显。
使用时则需要重点理解一个限制: Prepared field set 是连接级状态。
如果应用采用多个 Redis 连接、连接池或连接频繁重建,就必须正确处理每条连接上的字段集合初始化。
redis-rb 提供的
himport_auto_prepare 可以简化重连场景,但连接池中的不同连接仍然需要分别完成 Prepare。
本文使用的完整代码和可执行示例可以在 GitHub 项目中找到:
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70026953/viewspace-3183112/ ,如需转载,请注明出处,否则将追究法律责任。