expireat 用于为 key 设置以 unix 时间戳(秒)为单位的绝对过期时间,到期后 redis 会自动删除该 key。与 expire 设置相对时间(从现在起多少秒后过期)不同,expireat 指定的是一个绝对时间点,适用于需要在特定日期或时刻让 key 失效的场景。
本文基于 redis 通用 key 操作介绍 expireat。redis 命令本身不区分大小写,因此 expireat、expireat 和 expireat 的效果相同;文档统一使用大写形式。expireat 自 redis 1.2 起可用,条件选项 nx、xx、gt、lt 从 redis 7.0 开始支持。
一、命令概览
1. 基本语法
expireat key timestamp [nx | xx | gt | lt]
参数说明:
| 参数 | 说明 |
|---|---|
| key | 要设置过期时间的 key |
| timestamp | unix 时间戳(秒),绝对过期时间点 |
| nx | 仅当 key 没有过期时间时才设置(not exists),redis 7.0+ |
| xx | 仅当 key 已有过期时间时才设置(exists),redis 7.0+ |
| gt | 仅当新过期时间戳大于当前过期时间戳时才设置(greater than),redis 7.0+ |
| lt | 仅当新过期时间戳小于当前过期时间戳时才设置(less than),redis 7.0+ |
返回值:
- 设置成功返回 (integer) 1。
- 设置失败返回 (integer) 0(如 key 不存在、条件不满足等)。
2. 最简单的示例
先写入一个字符串值,然后设置在指定 unix 时间戳过期:
set mykey "hello" expireat mykey 1735689600
返回:
(integer) 1
查看剩余生存时间:
ttl mykey
返回值为当前时间到 1735689600 的剩余秒数(正数)。
查看过期时间戳:
expiretime mykey
返回:
(integer) 1735689600
二、unix 时间戳基础
1. 什么是 unix 时间戳
unix 时间戳是从 utc 1970 年 1 月 1 日 00:00:00 起经过的秒数。expireat 接受的就是这种秒级 unix 时间戳。
常见时间点的 unix 时间戳(示例):
| 时间(utc) | unix 时间戳 |
|---|---|
| 2025-01-01 00:00:00 | 1735689600 |
| 2025-06-01 00:00:00 | 1748736000 |
| 2026-01-01 00:00:00 | 1767225600 |
| 2026-08-28 00:00:00 | 1787548800 |
2. 获取当前 unix 时间戳
在 redis-cli 中可以用 time 命令获取服务器当前时间:
time
返回类似:
1) "1724719123" 2) "456789"
第一个值为秒级时间戳,第二个值为微秒部分。
在 shell 中获取:
date +%s
3. 时间戳计算
可以结合 shell 命令计算未来的时间戳。例如设置 1 小时后过期:
redis-cli expireat mykey $(( $(date +%s) + 3600 ))
设置明天同一时间过期:
redis-cli expireat mykey $(( $(date +%s) + 86400 ))
三、expireat 的基本用法
1. 设置绝对过期时间
set promo:coupon:1001 "discount50" expireat promo:coupon:1001 1767225600
返回 (integer) 1,该 key 将在 utc 2026-01-01 00:00:00 过期。
2. 对不存在的 key 设置过期
expireat no-such-key 1735689600
返回:
(integer) 0
key 不存在时,expireat 返回 0,不会创建 key。
3. 时间戳在过去(立即过期)
传入的时间戳小于当前时间戳时,key 会立即过期(相当于被删除):
set temp "value" expireat temp 1 exists temp
exists temp 返回 (integer) 0。
利用这一特性可以实现"立即删除"的效果,但通常应使用 del 或 unlink。
4. 时间戳为 0
unix 时间戳 0 对应 1970-01-01 00:00:00 utc,显然在过去,key 会立即过期:
set temp "value" expireat temp 0 exists temp
exists temp 返回 (integer) 0。
5. 负值时间戳
传入负值时间戳时,key 也会立即过期:
set temp "value" expireat temp -1 exists temp
exists temp 返回 (integer) 0。
四、条件选项(nx / xx / gt / lt)
redis 7.0 及以上版本支持条件选项,用于更精细地控制过期时间的设置行为。
1. nx — 仅当没有过期时间时设置
nx(not exists):只有当 key 当前没有关联过期时间(即永久有效)时,才设置新的过期时间。
set permanent-key "forever" expireat permanent-key 1735689600 nx
返回 (integer) 1,因为该 key 原本没有过期时间。
如果 key 已有过期时间:
set temp-key "temp" ex 60 expireat temp-key 1735689600 nx
返回 (integer) 0,因为 nx 条件不满足。
2. xx — 仅当已有过期时间时设置
xx(exists):只有当 key 当前已有过期时间时,才更新为新的过期时间。
set temp-key "temp" ex 60 expireat temp-key 1735689600 xx
返回 (integer) 1,因为该 key 已有过期时间,成功更新。
如果 key 是永久有效的:
set permanent-key "forever" expireat permanent-key 1735689600 xx
返回 (integer) 0,因为 xx 条件不满足。
3. gt — 仅当新过期时间更晚时设置
gt(greater than):只有当新的过期时间戳大于当前剩余过期时间戳时,才更新过期时间。redis 7.0+ 支持。
set mykey "value" ex 100 expireat mykey 1735689600 gt
如果 1735689600 对应的过期时间在当前过期时间之后,返回 (integer) 1,过期时间被延长。
如果新过期时间更早:
set mykey "value" exat 1735689600 expireat mykey 1000 gt
时间戳 1000 对应 1970-01-01,远在当前过期时间之前,返回 (integer) 0。
4. lt — 仅当新过期时间更早时设置
lt(less than):只有当新的过期时间戳小于当前剩余过期时间戳时,才更新过期时间。redis 7.0+ 支持。
set mykey "value" exat 1735689600 expireat mykey 1735660000 lt
如果 1735660000 < 1735689600,返回 (integer) 1,过期时间被缩短。
如果新过期时间更晚:
set mykey "value" ex 100 expireat mykey 1735689600 lt
如果 1735689600 对应的过期时间在当前过期时间之后,返回 (integer) 0。
5. 选项约束
nx 与 xx 互斥,gt 与 lt 互斥。gt 和 lt 不能与 nx 同时使用(没有当前过期时间时无法比较大小)。
五、expireat 与相关命令的区别
1. expireat 与 expire
expireat 设置绝对过期时间(unix 时间戳),expire 设置相对过期时间(从现在起的秒数):
expire key 3600 # 3600 秒后过期 expireat key 1735689600 # 在指定 unix 时间戳过期
两者可以互相转换:expireat key timestamp 等价于 expire key (timestamp - 当前时间戳)。
需要在特定时间点过期时用 expireat,需要"多少秒后"过期时用 expire。
2. expireat 与 pexpireat
expireat 接受秒级时间戳,pexpireat 接受毫秒级时间戳:
expireat key 1735689600 # 秒级时间戳 pexpireat key 1735689600000 # 毫秒级时间戳
两者语义相同,精度不同。需要毫秒级精度时使用 pexpireat。
3. expireat 与 set … exat
set 命令(redis 6.2+)可以通过 exat 选项在设置值的同时指定绝对过期时间戳,是原子操作:
set cache:key "value" exat 1735689600
等效于(但更高效且原子):
set cache:key "value" expireat cache:key 1735689600
推荐优先使用 set ... exat 形式,避免 set 成功但 expireat 失败导致 key 永久有效的风险。
4. expireat 与 expiretime / pexpiretime
expiretime 返回 key 过期时间对应的 unix 时间戳(秒),redis 7.0+ 支持:
set mykey "value" exat 1735689600 expiretime mykey
返回:
(integer) 1735689600
| 状态 | expiretime 返回值 |
|---|---|
| key 存在且有过期时间 | 过期时间戳(正数) |
| key 存在但无过期时间 | -1 |
| key 不存在 | -2 |
5. expireat 与 persist
persist 用于移除 key 的过期时间,使其变为永久有效:
set temp-key "value" exat 1735689600 persist temp-key ttl temp-key
ttl temp-key 返回 -1(永久有效)。
6. 相关命令总览
| 命令 | 时间类型 | 精度 | 说明 |
|---|---|---|---|
| expire key seconds | 相对 | 秒 | 多少秒后过期 |
| pexpire key ms | 相对 | 毫秒 | 多少毫秒后过期 |
| expireat key timestamp | 绝对 | 秒 | 在指定时间戳过期 |
| pexpireat key timestamp | 绝对 | 毫秒 | 在指定毫秒时间戳过期 |
| ttl key | — | 秒 | 剩余秒数 |
| pttl key | — | 毫秒 | 剩余毫秒数 |
| expiretime key | — | 秒 | 过期时间戳(秒) |
| pexpiretime key | — | 毫秒 | 过期时间戳(毫秒) |
| persist key | — | — | 移除过期时间 |
六、过期时间的刷新与更新
1. 覆盖式更新
对已有过期时间的 key 再次执行 expireat,会覆盖原有的过期时间:
set mykey "value" exat 1735689600 expireat mykey 1767225600 expiretime mykey
expiretime mykey 返回 1767225600,过期时间被重置。
2. set 命令会清除过期时间
使用 set 命令覆盖 key 的值时(不带 exat/pxat/ex/px 选项),原有的过期时间会被清除,key 变为永久有效:
set mykey "old" exat 1735689600 set mykey "new" ttl mykey
ttl mykey 返回 -1。
3. 其他会影响过期时间的命令
| 命令 | 对过期时间的影响 |
|---|---|
| set、getset | 清除过期时间(key 变为永久有效) |
| set ... exat / pxat | 设置新的绝对过期时间 |
| set ... ex / px | 设置新的相对过期时间 |
| incr / decr / incrby | 保留原有过期时间 |
| lpush / rpush | 保留原有过期时间 |
| hset / hmset | 保留原有过期时间 |
| sadd / zadd | 保留原有过期时间 |
| rename | 源 key 的过期时间转移到目标 key |
| copy | 默认不复制过期时间 |
| persist | 移除过期时间 |
| getex | 可同时修改过期属性 |
七、redis 过期机制原理
1. 惰性过期(passive expiration)
当客户端访问某个 key 时,redis 会检查该 key 是否已过期,如果已过期则立即删除并返回不存在。
特点:
- 对 cpu 友好,只在访问时检查
- 对内存不友好,已过期但未被访问的 key 会一直占用内存
2. 定期过期(active expiration)
redis 会周期性地(默认每秒 10 次)随机抽取一部分设置了过期时间的 key 进行检查,删除已过期的 key。
特点:
- 限制每次检查的数量和耗时,避免阻塞
- 通过随机采样逐步清理过期 key
3. 过期 key 的复制与持久化
- 主从复制:主节点删除过期 key 后,会向所有从节点发送 del 命令。从节点不会主动过期 key。
- aof 持久化:key 过期被删除时,会向 aof 文件追加一条 del 命令。
- rdb 持久化:过期 key 不会被写入 rdb 文件。
八、expireat 与不同数据类型
expireat 作用于整个 key,所有数据类型(string、hash、list、set、sorted set、stream 等)的过期行为一致。
hset user:1001 name "alice" age 30 expireat user:1001 1735689600
rpush queue:jobs job-a job-b job-c expireat queue:jobs 1735689600
sadd tags redis python java expireat tags 1735689600
zadd leaderboard 100 "player1" 200 "player2" expireat leaderboard 1735689600
过期后整个 key 及其所有数据都会被删除。
九、事务、pipeline 与并发场景
1. 在事务中使用
expireat 可以放入事务队列:
multi set promo:banner "summer-sale" expireat promo:banner 1735689600 exec
exec 执行后返回各命令的结果。
2. 原子性与 set … exat
“先 set 再 expireat” 不是原子操作。如果 set 成功后客户端崩溃或网络中断,expireat 可能不会执行,导致 key 永久有效。
推荐使用原子的 set ... exat 形式(redis 6.2+):
set promo:banner "summer-sale" exat 1735689600
3. 分布式锁续期
使用 expireat 续期分布式锁时,应使用 xx 选项确保锁仍然存在:
expireat lock:resource 1735689600 xx
如果返回 0,说明锁已经不存在(可能已过期或被释放)。
4. pipeline 批量设置
需要为大量 key 设置同一过期时间点时,可以使用 pipeline 批量发送 expireat 命令,减少网络往返。
十、在常见客户端中的使用方式
1. redis-cli
redis-cli set promo:key "summer-sale" redis-cli expireat promo:key 1735689600 redis-cli expiretime promo.key
结合 shell 动态计算时间戳:
# 设置明天同一时间过期 redis-cli expireat mykey $(( $(date +%s) + 86400 )) # 设置 2025-06-01 00:00:00 utc 过期 redis-cli expireat mykey $(date -d "2025-06-01 00:00:00 utc" +%s)
2. python(redis-py)
import redis
import time
client = redis.redis(host="localhost", port=6379, decode_responses=true)
client.set("promo:key", "summer-sale")
# 设置在指定 unix 时间戳过期
expire_at = int(time.time()) + 3600 # 1 小时后
client.expireat("promo:key", expire_at)
# 使用 datetime 对象(redis-py 支持)
from datetime import datetime, timezone, timedelta
expire_dt = datetime(2025, 6, 1, 0, 0, 0, tzinfo=timezone.utc)
client.expireat("promo:key", expire_dt)
# 查看过期时间戳
expire_time = client.expiretime("promo:key")
print(expire_time)
3. node.js(node-redis)
import { createclient } from "redis";
const client = createclient();
await client.connect();
await client.set("promo:key", "summer-sale");
// 设置在指定 unix 时间戳过期
const expireat = math.floor(date.now() / 1000) + 3600; // 1 小时后
await client.expireat("promo:key", expireat);
// 查看过期时间戳
const expiretime = await client.expiretime("promo:key");
console.log(expiretime);
await client.quit();4. java(jedis)
import java.time.instant;
import redis.clients.jedis.jedis;
try (jedis jedis = new jedis("localhost", 6379)) {
jedis.set("promo:key", "summer-sale");
// 设置在指定 unix 时间戳过期
long expireat = instant.now().getepochsecond() + 3600; // 1 小时后
long result = jedis.expireat("promo:key", expireat);
system.out.println(result); // 1
// 使用 date 对象
import java.util.date;
jedis.expireat("promo:key", date.from(instant.parse("2025-06-01t00:00:00z")));
// 查看过期时间戳
long expiretime = jedis.expiretime("promo:key");
system.out.println(expiretime);
}
十一、典型使用场景
1. 限时促销活动
促销活动在指定日期结束,自动清理相关 key:
set promo:summer:2025 "active" expireat promo:summer:2025 1751328000 # 2025-07-01 00:00:00 utc
活动到期后 key 自动失效,无需手动清理。
2. 定时任务触发
利用 key 过期事件(keyspace notification)在指定时间触发任务:
set task:send-email:1001 "data" exat 1735689600
配合 notify-keyspace-events ex 配置,订阅过期事件来触发定时任务。
3. 限时验证码
验证码在特定时间点失效:
# 设置验证码在当天 23:59:59 过期 redis-cli set sms:code:13800138000 "123456" redis-cli expireat sms:code:13800138000 $(date -d "today 23:59:59" +%s)
4. 会话固定时间失效
用户会话在当天结束时间点自动失效:
redis-cli set session:abc123 "user:1001" redis-cli expireat session:abc123 $(date -d "tomorrow 00:00:00" +%s)
5. 倒计时活动
秒杀活动在特定时刻开始或结束:
set seckill:stock "100" expireat seckill:stock 1735689600 # 活动结束时间
6. 数据归档
日志或统计数据在指定日期后自动清理:
set log:2025-01:access "count_data" expireat log:2025-01:access 1767225600 # 2026-01-01 自动清理 2025 年 1 月数据
十二、性能与使用建议
- 时间复杂度:o(1)。
- expireat 是写命令(acl 类别为 @keyspace、@write、@fast),会修改 key 的元数据。
- 优先使用 set key value exat timestamp 的原子形式,避免 set 成功但 expireat 失败导致 key 永久有效。
- 注意时区问题:expireat 接受的是 utc unix 时间戳,不是本地时间。使用 date 命令时注意时区转换。
- 大量 key 在同一时间戳过期可能导致 redis 瞬间压力增大(缓存雪崩)。可以在过期时间上加入随机偏移,打散过期时间。
- 时间戳在过去时 key 会立即过期,等效于删除。如需删除 key,优先使用 del 或 unlink。
- redis 7.0+ 的条件选项(nx、xx、gt、lt)可以在很多场景下替代"先 ttl 判断再设置"的非原子流程,使用更安全。
- 集群模式下,expireat 作用于单个 key,不受哈希槽限制。
- 服务器时间偏差会影响过期判断。确保 redis 服务器系统时间准确,避免时间跳转导致 key 提前或延后过期。
十三、常见问题排查
问题 1:expireat 返回 1 但 key 立即过期
可能的原因:
- 传入的时间戳小于当前时间戳(在过去),key 会立即过期。
- 系统时间不准确,导致时间戳判断异常。
- 传入的是毫秒时间戳(而非秒级时间戳),导致值远大于预期,但如果误传小毫秒值则可能已过期。
time expiretime mykey ttl mykey
问题 2:expireat 返回 0
可能的原因:
- key 不存在(先 exists 确认)。
- 使用了条件选项(nx/xx/gt/lt)但条件不满足。
- 时间戳格式不正确(必须是整数)。
问题 3:key 没有在预期时间过期
可能的原因:
- 时区计算错误:expireat 使用 utc 时间戳,而用户可能误用了本地时间。
- 系统时间被修改或存在偏差。
- 在 expireat 之后又执行了 set 等会清除过期时间的命令。
- key 被 persist 移除了过期时间。
- 内存达到 maxmemory 上限,触发了内存淘汰策略,key 被提前淘汰。
问题 4:误用毫秒时间戳
expireat 接受秒级时间戳,pexpireat 接受毫秒级时间戳。如果误将毫秒时间戳传给 expireat,会导致过期时间远超预期(大约 1000 倍)。
# 错误:传入了毫秒时间戳 expireat key 1735689600000 # 实际过期时间约为 56670 年后 # 正确:秒级时间戳 expireat key 1735689600 # 如果需要毫秒精度,使用 pexpireat pexpireat key 1735689600000
问题 5:从节点数据不一致
从节点不会主动过期 key,而是等待主节点的 del 命令。如果从节点读取到"应该已过期"的 key,这是正常行为。确保读取策略与业务需求匹配。
十四、完整练习
下面的示例演示从设置绝对过期时间、验证 ttl 和 expiretime、条件选项、更新过期时间,到与 set ... exat、persist 等命令配合使用的完整流程:
del demo:key del demo:perm del demo:renew # 获取当前时间 time # 基本用法:设置绝对过期时间 set demo:key "hello" expireat demo:key 1767225600 expiretime demo:key ttl demo:key # 对不存在的 key 设置过期 expireat demo:noexist 1767225600 # set 会清除过期时间 set demo:key "new value" ttl demo:key # 使用 set ... exat 原子设置 set demo:key "with ttl" exat 1767225600 expiretime demo:key # persist 移除过期时间 persist demo:key ttl demo:key # 条件选项示例(需要 redis 7.0+) # nx:仅当无过期时间时设置 set demo:perm "permanent" expireat demo:perm 1767225600 nx expiretime demo:perm expireat demo:perm 1735689600 nx # 返回 0,因为已有过期时间 # xx:仅当有过期时间时更新 expireat demo:perm 1735689600 xx expiretime demo:perm # 续期场景模拟 set demo:renew "session" exat 1735689600 expiretime demo:renew expireat demo:renew 1767225600 xx # 续期到更晚的时间 expiretime demo:renew # hash 类型的过期 hset demo:hash name "alice" age 30 expireat demo:hash 1767225600 expiretime demo:hash # 验证过去时间戳立即过期 set demo:past "gone" expireat demo:past 1 exists demo:past
预期结果:
- expireat demo:key 1767225600 返回 (integer) 1,expiretime demo:key 返回 1767225600,ttl 返回正数。
- expireat demo:noexist 1767225600 返回 (integer) 0。
- set demo:key "new value" 后 ttl demo:key 返回 -1。
- set demo:key "with ttl" exat 1767225600 后 expiretime demo:key 返回 1767225600。
- persist demo:key 返回 1,ttl demo:key 返回 -1。
- expireat demo:perm 1767225600 nx 返回 1,第二次 nx 返回 0。
- expireat demo:perm 1735689600 xx 返回 1,expiretime 返回 1735689600。
- expireat demo:renew 1767225600 xx 返回 1,expiretime 返回 1767225600。
- expireat demo:hash 1767225600 返回 1,expiretime demo:hash 返回 1767225600。
- expireat demo:past 1 后 exists demo:past 返回 (integer) 0。
十五、命令速查表
| 需求 | 命令 | 时间类型 | 精度 |
|---|---|---|---|
| 在指定 unix 时间戳过期(秒) | expireat key timestamp | 绝对 | 秒 |
| 在指定 unix 时间戳过期(毫秒) | pexpireat key timestamp | 绝对 | 毫秒 |
| 多少秒后过期 | expire key seconds | 相对 | 秒 |
| 多少毫秒后过期 | pexpire key ms | 相对 | 毫秒 |
| 原子设置值和绝对过期时间 | set key value exat timestamp | 绝对 | 秒 |
| 原子设置值和绝对过期时间 | set key value pxat timestamp | 绝对 | 毫秒 |
| 获取剩余生存时间(秒) | ttl key | — | 秒 |
| 获取剩余生存时间(毫秒) | pttl key | — | 毫秒 |
| 获取过期时间戳(秒) | expiretime key | — | 秒 |
| 获取过期时间戳(毫秒) | pexpiretime key | — | 毫秒 |
| 移除过期时间 | persist key | — | — |
| 仅当无过期时间时设置 | expireat key timestamp nx | 绝对 | 秒 |
| 仅当有过期时间时更新 | expireat key timestamp xx | 绝对 | 秒 |
| 仅当新过期时间更晚时设置 | expireat key timestamp gt | 绝对 | 秒 |
| 仅当新过期时间更早时设置 | expireat key timestamp lt | 绝对 | 秒 |
总结
expireat 的核心作用是为 key 设置以秒级 unix 时间戳为单位的绝对过期时间,到期后 redis 自动删除该 key:
expireat key timestamp [nx | xx | gt | lt]
使用时重点注意五点:
- expireat 使用 utc 秒级 unix 时间戳,注意时区转换和精度(秒而非毫秒)。
- 时间戳在过去时 key 会立即过期,等效于删除。
- 优先使用 set key value exat timestamp 的原子形式,避免 set 成功但 expireat 失败。
- set 等覆盖 key 的命令会清除原有过期时间;incr、lpush、hset 等修改内部数据的命令则保留过期时间。
- redis 7.0+ 支持 nx、xx、gt、lt 条件选项,可以原子地实现"仅当无过期时设置"、"续期"等场景。
到此这篇关于redis中expireat命令实现的文章就介绍到这了,更多相关redis expireat命令内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论