Redis HPEXPIREAT 命令详细教程
HPEXPIREAT 为 Hash 中一个或多个字段设置绝对到期时刻,时间以 Unix 毫秒级时间戳表示。它与 HEXPIREAT 语义相同,只是精度为毫秒。
资料合集:https://pan.quark.cn/s/10e98d308913、https://pan.quark.cn/s/f56bc69c5338
一、概览与语法
redis
HPEXPIREAT key unix-time-milliseconds [NX | XX | GT | LT] FIELDS numfields field [field ...]
| 项目 | 说明 |
|---|---|
| 数据类型 | Hash 的字段级过期时间 |
| 支持版本 | Redis 7.4.0 起 |
| key | 一个 Hash Key |
| unix-time-milliseconds | 绝对 Unix 时间戳,单位毫秒 |
| 条件选项 | NX、XX、GT、LT 四者最多选一,互斥 |
| FIELDS | 必填关键字 |
| numfields | 字段数量,必须与后续字段参数个数一致 |
| 时间复杂度 | O(N),N 为指定字段数量 |
| ACL | @write、@hash、@fast |
| 命令标记 | write、denyoom、fast |
官方明确说明:过去的时间戳会立即删除该字段。$TRAE_REF
二、逐字段返回值
| 数值 | 含义 |
|---|---|
| -2 | 该字段不存在,或整个 Key 不存在 |
| 0 | 指定的 NX、XX、GT、LT 条件未满足 |
| 1 | 过期时间已设置或更新 |
| 2 | 传入的是过去的 Unix 时间,字段被立即删除 |
官方对 2 的定义覆盖了四种命令:HEXPIRE 或 HPEXPIRE 以 0 秒/毫秒调用,HEXPIREAT 或 HPEXPIREAT 以过去的 Unix 时间调用。$TRAE_REF
三、基础示例
以下命令需要 Redis 7.4 或更新版本,在测试实例的 redis-cli 中执行。文中结果是预期说明,未实际连接 Redis 运行。官方示例使用的 1715704971000 是一个毫秒级时间戳。
redis
DEL tutorial:{hpexpireat}:profile
HSET tutorial:{hpexpireat}:profile field1 hello field2 world
HPEXPIREAT tutorial:{hpexpireat}:profile 1715704971000 FIELDS 2 field1 field2
HPTTL tutorial:{hpexpireat}:profile FIELDS 2 field1 field2
HPEXPIRETIME tutorial:{hpexpireat}:profile FIELDS 2 field1 field2
HPEXPIREAT tutorial:{hpexpireat}:profile 1 FIELDS 1 field1
HEXISTS tutorial:{hpexpireat}:profile field1
HEXISTS tutorial:{hpexpireat}:profile field2
预期结果:HPEXPIREAT 返回 [1, 1];HPTTL 返回两个相同的毫秒值(官方示例中为 303340 这类数值,取决于执行时刻与目标时间戳的差值);HPEXPIRETIME 返回两个相同的毫秒时间戳。传入过去的时间戳 1 后返回 [2],field1 被立即删除,HEXISTS 返回 0,而 field2 仍为 1。
如果示例中的时间戳已经过去,第一次调用就会返回 [2, 2] 并删除两个字段。实际使用时请替换为未来的时间戳。
四、如何生成毫秒时间戳
毫秒时间戳由客户端生成,Redis 不提供换算命令。
bash
date +%s%3N
python
import time
from datetime import datetime, timedelta, timezone
now_ms = int(time.time() * 1000)
print(now_ms) # 当前毫秒时间戳
print(now_ms + 60_000) # 一分钟后
print(int((datetime.now(timezone.utc) + timedelta(minutes=5)).timestamp() * 1000))
常见错误是把秒级时间戳当作毫秒传入:秒级数值比毫秒小三个数量级,作为毫秒时间戳几乎必然是"过去的时间",会导致字段被立即删除。反过来,把毫秒时间戳传给 HEXPIREAT 则会得到一个极其遥远的未来时刻。换算时务必确认单位。
五、条件选项与边界情况
| 选项 | 判断条件 |
|---|---|
| NX | 字段当前没有过期时间时才设置 |
| XX | 字段当前已有过期时间时才设置 |
| GT | 新到期时刻严格晚于当前到期时刻时才设置 |
| LT | 新到期时刻严格早于当前到期时刻时才设置 |
官方说明:对于 GT 与 LT,没有过期时间的字段被视为无限大的 TTL,因此有限时间的 GT 对无 TTL 字段通常不成立,LT 会成立。$TRAE_REF
| 场景 | 行为 |
|---|---|
| Key 不存在 | 按输入字段数返回多个 -2,不创建 Key |
| 字段不存在 | 该项返回 -2 |
| 时间戳已过去 | 该字段被立即删除,返回 2 |
| 时间戳不是整数 | 报参数类型错误 |
| Key 是 String、List 等非 Hash | 报 WRONGTYPE 错误 |
| numfields 与实际字段数不符 | 报语法错误 |
六、与相关命令的对照
| 命令 | 对象 | 时间表达 | 单位 |
|---|---|---|---|
| HPEXPIRE | Hash 字段 | 相对 | 毫秒 |
| HPEXPIREAT | Hash 字段 | 绝对 | 毫秒时间戳 |
| HEXPIREAT | Hash 字段 | 绝对 | 秒时间戳 |
| PEXPIREAT | 整个 Key | 绝对 | 毫秒时间戳 |
| HPEXPIRETIME | Hash 字段 | 查询绝对时刻 | 毫秒时间戳 |
字段级过期与 Key 级过期相互独立,谁先到期谁先限制数据可用性。查询字段绝对到期时刻使用 HPEXPIRETIME,查询剩余时间使用 HPTTL。
七、Python 客户端示例
前提为已安装 redis-py 且服务端为 Redis 7.4 或更新版本。
python
import time
import redis
r = redis.Redis(host="localhost", port=6379, decode_responses=True)
k = "tutorial:{hpexpireat}:python"
try:
r.delete(k)
r.hset(k, mapping={"a": "A", "b": "B"})
fields = ["a", "b", "missing"]
deadline_ms = int(time.time() * 1000) + 60_000
result = r.execute_command(
"HPEXPIREAT", k, deadline_ms, "FIELDS", len(fields), *fields
)
print(result) # [1, 1, -2]
print(r.execute_command("HPEXPIRETIME", k, "FIELDS", 2, "a", "b"))
# [deadline_ms, deadline_ms]
print(r.execute_command("HPEXPIREAT", k, 1, "FIELDS", 1, "a")) # [2]
print(r.hexists(k, "a")) # False
finally:
r.delete(k)
r.close()
八、并发、重试与适用场景
绝对时间戳的明显优势是重试语义稳定:把同一请求原样重发,到期时刻不会因为重试时刻变化而延长,这一点优于相对 TTL。但重试仍可能重复执行,条件选项只按各自规则判断,不是通用幂等保证;请求超时也不代表未执行,应先查询 HPEXPIRETIME 或 HEXISTS 再决定是否重发。
单条 HPEXPIREAT 对多个字段原子执行,但"先 HSET 写值,再 HPEXPIREAT 设期限"是两条独立调用,中间可能有其他写入。需要两者一起生效时应使用经过验证的脚本或事务设计。
典型场景:与外部系统对齐的固定过期时刻、活动截止时间、定时失效的权益字段。由于毫秒时间戳依赖客户端时钟,而判定在服务端进行,两者存在偏差时实际存活时长会与预期不符,跨机房场景应统一时间源并留出余量。
九、练习、排错与总结
练习:新建 tutorial:{hpexpireat}:exercise,写入 a=1、b=2;用 Python 计算当前时间加 10 分钟的毫秒时间戳,执行 HPEXPIREAT ... <ts> FIELDS 2 a b,预期返回 [1, 1];用 HPEXPIRETIME 确认返回该时间戳;再用 1 无条件调用,预期返回 [2] 且字段被删除。
排错要点:unknown command 时确认服务端版本不低于 7.4;字段被意外立即删除时,检查传入的是秒级还是毫秒级时间戳;返回 0 检查条件选项;返回 -2 检查字段是否缺失;HPEXPIRETIME 返回 -1 表示字段永久有效。清理使用 DEL tutorial:{hpexpireat}:profile tutorial:{hpexpireat}:exercise。速记:7.4 起支持、绝对毫秒时间戳、过去时间戳立即删除、FIELDS 必填、返回 -2/0/1/2、注意单位换算。