在上一篇 《Golang 操作 Redis:Set 集合数据类型操作用法 - go-redis 使用指南》 文章中,我们介绍了 Redis 的 Set 集合数据类型,并展示如何在 Golang 中使用 go-redis 进行相关操作。本文将介绍如何设置 Redis Hash 哈希数据类型的单个字段设置过期时间。
👉 点击查看《go-redis使用指南》系列文章目录
在《go-redis使用指南》系列文章中,我们将详细介绍如何在 Golang 项目中使用 redis/go-redis 库与 Redis 进行交互。以下是该系列文章的全部内容:
- Golang 操作 Redis:快速上手 - go-redis 使用指南
- Golang 操作 Redis:连接设置与参数详解 - go-redis 使用指南
- Golang 操作 Redis:基础的字符串键值操作 - go-redis 使用指南
- Golang 操作 Redis:如何设置 key 的过期时间 - go-redis 使用指南
- Golang 操作 Redis:Hash 哈希数据类型操作用法 - go-redis 使用指南
- Golang 操作 Redis:Set 集合数据类型操作用法 - go-redis 使用指南
- Golang 操作 Redis:为 Hash 中的字段设置过期时间 - go-redis 使用指南
- Golang 操作 Redis:List 列表数据类型操作用法 - go-redis 使用指南
- Golang 操作 Redis:SortedSet 有序集合数据类型操作用法 - go-redis 使用指南
- Golang 操作 Redis:bitmap 数据类型操作用法 - go-redis 使用指南
- Golang 操作 Redis:事务处理操作用法 - go-redis 使用指南
- Golang 操作 Redis:地理空间数据类型操作用法 - go-redis 使用指南
- Golang 操作 Redis:HyperLogLog 操作用法 - go-redis 使用指南
- Golang 操作 Redis:Pipeline 操作用法 - go-redis 使用指南
- Golang 操作 Redis:PubSub发布订阅用法 - go-redis 使用指南
- Golang 操作 Redis:布隆过滤器(Bloom Filter)操作用法 - go-redis 使用指南
- Golang 操作 Redis:Cuckoo Filter操作用法 - go-redis 使用指南
- Golang 操作 Redis:Stream操作用法 - go-redis 使用指南
Redis 7.4.0 直接支持单个 hash 字段设置过期时间
- HEXPIRE and HPEXPIRE:设置秒级和毫秒级生存时间
- HEXPIREAT and HPEXPIREAT:设置秒级和毫秒级 UNIX 过期时间戳
- HPERSIST:移除过期时间
- HEXPIRETIME and HPEXPIRETIME:获取秒级和毫秒级 UNIX 过期时间戳
- HTTL and HPTTL:获取秒级和毫秒级生存时间
相关 go-redis 使用示例请阅读:《Golang 操作 Redis:Hash 哈希数据类型操作用法 - go-redis 使用指南》
低版本 Redis 支持单个 hash 字段设置过期时间的替代方案
如果你的生产环境使用的 Redis 低于 7.4.0 且不便升级时,Hash 类型的字段 (field
) 是不支持单独设置过期时间的。Redis 的过期时间是针对整个 key 而不是 key 下的某个字段设置的。因此,你只能对整个 Hash key 设置过期时间,而不能直接单独对 Hash 中的某个字段设置过期时间,这意味着,Hash 中的所有 field 都会在 Hash 键的过期时间到达时一起被删除。
虽然低版本 Redis 不支持对 Hash 中的单个字段设置过期时间,但可以使用以下替代方案来实现类似的功能:
使用额外的 Redis key 来跟踪字段的过期时间: 你可以为每个需要过期时间的字段设置一个额外的 Redis key,用于记录该字段的过期时间。
自定义过期机制: 在访问字段时,检查该字段的过期时间是否已到期。如果到期,则删除该字段并进行相应处理。如果需要设置过期时间的字段数量过于庞大,可以定期扫描和清理这些字段。你可以使用 Redis 的扫描命令来找到过期的字段并将其删除。
go-redis 对 Hash 中的个别 field 设置过期时间
以下是一个示例,展示了如何使用额外的 Redis key 来跟踪 Hash 中字段的过期时间并实现定期清理过期的 Hash 字段:
package main
import (
"context"
"fmt"
"github.com/redis/go-redis/v9"
"time"
)
var ctx = context.Background()
// 设置 Hash 中的字段和字段的过期时间
func setFieldWithExpiration(rdb *redis.Client, hashKey, field, value string, expiration time.Duration) error {
// 设置 Hash 中的字段值
if err := rdb.HSet(ctx, hashKey, field, value).Err(); err != nil {
return err
}
// 设置字段过期时间的辅助 key
expirationKey := fmt.Sprintf("%s:%s:expiration", hashKey, field)
return rdb.Set(ctx, expirationKey, time.Now().Add(expiration).Unix(), expiration).Err()
}
// 获取 Hash 中的字段值
func getField(rdb *redis.Client, hashKey, field string) (string, error) {
expirationKey := fmt.Sprintf("%s:%s:expiration", hashKey, field)
// 检查字段是否过期
expirationTimeStr, err := rdb.Get(ctx, expirationKey).Result()
if err != nil && err != redis.Nil {
return "", err
}
if expirationTimeStr != "" {
expirationTime, err := time.ParseInt(expirationTimeStr, 10, 64)
if err != nil {
return "", err
}
if time.Now().Unix() > expirationTime {
// 如果字段已过期,删除字段和过期时间
rdb.HDel(ctx, hashKey, field)
rdb.Del(ctx, expirationKey)
return "", nil
}
}
// 返回 Hash 中的字段值
return rdb.HGet(ctx, hashKey, field).Result()
}
// 定期扫描并清理过期的 Hash 字段
func cleanExpiredFields(rdb *redis.Client, hashKey string) error {
cursor := uint64(0)
for {
// 扫描所有过期时间 key
keys, nextCursor, err := rdb.Scan(ctx, cursor, fmt.Sprintf("%s:*:expiration", hashKey), 0).Result()
if err != nil {
return err
}
cursor = nextCursor
if cursor == 0 {
break
}
// 使用 MGet 批量获取所有过期时间 key 的值
expirationValues, err := rdb.MGet(ctx, keys...).Result()
if err != nil {
return err
}
// 处理每个过期时间 key
for i, key := range keys {
expirationTimeStr := expirationValues[i]
if expirationTimeStr != nil {
expirationTime, err := time.ParseInt(expirationTimeStr.(string), 10, 64)
if err != nil {
return err
}
if time.Now().Unix() > expirationTime {
// 如果字段已过期,删除 Hash 中的字段和过期时间 key
field := key[len(hashKey)+1:len(key)-len(":expiration")]
rdb.HDel(ctx, hashKey, field)
rdb.Del(ctx, key)
}
}
}
}
return nil
}
func main() {
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "",
DB: 0,
})
// 设置字段 "field1" 的值和过期时间为 10 秒
if err := setFieldWithExpiration(rdb, "myhash", "field1", "value1", 10*time.Second); err != nil {
fmt.Println("Error setting field with expiration:", err)
return
}
// 启动清理过期字段的定时任务
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
go func() {
for range ticker.C {
if err := cleanExpiredFields(rdb, "myhash"); err != nil {
fmt.Println("Error cleaning expired fields:", err)
}
}
}()
// 测试获取字段值
value, err := getField(rdb, "myhash", "field1")
if err != nil {
fmt.Println("Error getting field:", err)
return
}
fmt.Println("Field value:", value)
// 等待 10 秒以超出字段的过期时间
time.Sleep(10 * time.Second)
// 再次获取字段值
value, err = getField(rdb, "myhash", "field1")
if err != nil {
fmt.Println("Error getting field:", err)
return
}
fmt.Println("Field value after expiration:", value)
// 保持主线程运行
select {}
}
代码说明
setFieldWithExpiration
:设置 Hash 中的字段值,并为该字段创建一个额外的过期时间 key。getField
:在获取字段值之前检查其过期时间。如果字段已过期,删除该字段和过期时间 key。cleanExpiredFields
:定期扫描所有过期时间 key,并删除已经过期的字段和过期时间 key。main
:- 设置一个字段的值和过期时间。
- 启动一个定时任务来定期清理过期字段。
- 测试获取字段值,并在字段过期后再次获取以验证功能。
总结
终于我们可以在 Redis 7.4.0 版本上直接对 Hash 的字段设置过期值了,但由于很多业务使用 Redis 低于该版本,生产环境去做升级迁移风险很大,所以还是很多的低版本 Redis 的使用场景,虽然低版本 Redis 不支持直接为 Hash 中的字段设置过期时间,但可以通过使用额外的 Redis key 来跟踪字段的过期时间,并在访问字段时检查其过期状态。这样,你可以实现类似的功能,并管理 Hash 中字段的生命周期。
希望这篇文章对你有所帮助!点击 go-redis 使用指南 可查看更多相关教程!