Go语言中如何扫描Redis中大量的key

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 在Redis中,遍历大量键时直接使用`KEYS`命令会导致性能瓶颈,因为它会一次性返回所有匹配的键,可能阻塞Redis并影响服务稳定性。为解决此问题,Redis提供了`SCAN`命令来分批迭代键,避免一次性加载过多数据。本文通过两个Go语言示例演示如何使用`SCAN`命令:第一个示例展示了基本的手动迭代方式;第二个示例则利用`Iterator`简化迭代过程。这两种方法均有效地避免了`KEYS`命令的性能问题,并提高了遍历Redis键的效率。

在 Redis 中,当我们需要遍历大量的键时,直接使用 KEYS 命令会面临性能瓶颈,尤其是在键数量非常多的情况下。

KEYS 命令会一次性返回所有匹配的键,这可能导致 Redis 阻塞,严重影响线上服务的稳定性。为了解决这个问题,Redis 提供了 SCAN 命令,用于分批次迭代键,避免一次性返回所有数据。

今天,我们将通过两个示例代码,详细讲解如何在 Go 语言中使用 SCAN 命令遍历 Redis 键。

这里我们用到的是 github.com/go-redis/redis 包,先创建一个 redis 链接

package redis_demo

import (
    "github.com/go-redis/redis"
)

func RDBClient() (*redis.Client, error) {
   
    // 创建一个 Redis 客户端
    // 也可以使用数据源名称(DSN)来创建
    // redis://<user>:<pass>@localhost:6379/<db>
    opt, err := redis.ParseURL("redis://localhost:6379/0")
    if err != nil {
   
        return nil, err
    }
    client := redis.NewClient(opt)

    // 通过 cient.Ping() 来检查是否成功连接到了 redis 服务器
    _, err = client.Ping().Result()
    if err != nil {
   
        return nil, err
    }

    return client, nil
}

代码示例 1:使用 SCAN 命令的基本迭代方式

首先来看第一个示例代码,这段代码展示了如何通过 SCAN 命令遍历 Redis 数据库中的所有键。

package redis_demo

import (
    "fmt"
)

func scanKeysDemo1() {
   
    var cursor uint64
    rdb, err := RDBClient()
    if err != nil {
   
        panic(err)
    }

    for {
   
        var keys []string
        var err error
        // Scan 命令用于迭代数据库中的数据库键。
        keys, cursor, err = rdb.Scan(cursor, "*", 0).Result()
        if err != nil {
   
            panic(err)
        }

        // 处理 keys
        for _, key := range keys {
   
            fmt.Printf("key: %s\n", key)
        }

        // 如果 cursor 为 0,说明已经遍历完成,退出循环
        if cursor == 0 {
   
            break
        }
    }

}

代码详解:

  1. RDBClient() 函数: 这段代码假设 RDBClient() 是一个返回 Redis 客户端实例的函数,用于连接 Redis 数据库。如果连接失败,程序会直接 panic 终止。

  2. Scan 命令: rdb.Scan(cursor, "*", 0).Result()SCAN 命令的核心部分。这里的 cursor 用于记录当前扫描的游标位置,* 表示匹配所有键,0 表示每次扫描返回所有匹配键。在第一次调用时,cursor 必须为 0,之后 Redis 会返回新的 cursor,直到 cursor 再次为 0 表示迭代结束。

  3. 循环扫描: 使用 for 循环不断调用 SCAN 命令,每次返回一批键并更新 cursor。当 cursor0 时,退出循环。

  4. 键处理: for _, key := range keys 用于遍历当前批次的所有键,并对每个键进行处理(如打印出来)。

这个方法相对直观,但如果 Redis 中的键数量巨大,手动处理游标的方式可能显得繁琐。这时候,可以考虑使用更简便的 Iterator 方法。

代码示例 2:使用 Iterator 简化迭代过程

接下来是第二个示例代码,它展示了如何使用 Iterator 方法简化键的遍历过程。

package redis_demo

import (
    "fmt"
)

func scanKeysDemo2() {
   
    rdb, err := RDBClient()
    if err != nil {
   
        panic(err)
    }

    // 针对这种需要遍历大量 key 的场景,go-redis 提供了一个更简单的方法 Iterator
    iter := rdb.Scan(0, "*", 50).Iterator()
    for iter.Next() {
   
        fmt.Printf("key: %s\n", iter.Val())
    }
    if err := iter.Err(); err != nil {
   
        panic(err)
    }

    // 此外,对于 redis 中的 set、hash、zset 等类型,也可以使用 Iterator 进行遍历
    // 例如:
    // iter := rdb.SScan("set_key", 0, "*", 50).Iterator()
    // iter := rdb.HScan("hash_key", 0, "*", 50).Iterator()
    // iter := rdb.ZScan("zset_key", 0, "*", 50).Iterator()
}

代码详解:

  1. 使用 Iterator: 与前一个示例不同,这里使用了 Iterator 迭代器。rdb.Scan(0, "*", 50).Iterator() 创建了一个迭代器,每次返回 50 个匹配的键。这样无需手动处理 cursor,简化了遍历过程。

  2. 迭代与处理: for iter.Next() 是一个简洁的循环,用于遍历所有匹配的键。当 iter.Next() 返回 false 时,表示遍历结束。iter.Val() 返回当前键的值。

  3. 错误处理: 在循环结束后,检查 iter.Err() 是否为 nil,以确保遍历过程中没有出现错误。

  4. 扩展功能: 此外,Iterator 方法不仅适用于遍历键,也可用于遍历 Redis 中的集合(Set)、哈希(Hash)、有序集合(ZSet)等数据结构。通过将 Scan 换成 SScanHScanZScan,就能遍历对应的数据结构。

总结

这篇文章介绍了如何在 Go 语言中使用 SCAN 命令遍历 Redis 键,并比较了手动处理 cursor 和使用 Iterator 的两种方式。对于 Redis 新手来说,了解 SCAN 命令的用法非常重要,它不仅帮助你避免了使用 KEYS 命令可能带来的性能问题,还让你能够更高效地遍历 Redis 数据。

如果你觉得文章有帮助,欢迎点赞、转发,让更多人掌握 Redis 的这些实用技巧!😊

相关实践学习
基于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
相关文章
|
2月前
|
存储 监控 算法
员工上网行为监控中的Go语言算法:布隆过滤器的应用
在信息化高速发展的时代,企业上网行为监管至关重要。布隆过滤器作为一种高效、节省空间的概率性数据结构,适用于大规模URL查询与匹配,是实现精准上网行为管理的理想选择。本文探讨了布隆过滤器的原理及其优缺点,并展示了如何使用Go语言实现该算法,以提升企业网络管理效率和安全性。尽管存在误报等局限性,但合理配置下,布隆过滤器为企业提供了经济有效的解决方案。
92 8
员工上网行为监控中的Go语言算法:布隆过滤器的应用
|
2月前
|
存储 Go 索引
go语言中数组和切片
go语言中数组和切片
47 7
|
2月前
|
Go 开发工具
百炼-千问模型通过openai接口构建assistant 等 go语言
由于阿里百炼平台通义千问大模型没有完善的go语言兼容openapi示例,并且官方答复assistant是不兼容openapi sdk的。 实际使用中发现是能够支持的,所以自己写了一个demo test示例,给大家做一个参考。
|
1天前
|
Go C语言
Go语言入门:分支结构
本文介绍了Go语言中的条件语句,包括`if...else`、`if...else if`和`switch`结构,并通过多个练习详细解释了它们的用法。`if...else`用于简单的条件判断;`if...else if`处理多条件分支;`switch`则适用于基于不同值的选择逻辑。特别地,文章还介绍了`fallthrough`关键字,用于优化重复代码。通过实例如判断年龄、奇偶数、公交乘车及成绩等级等,帮助读者更好地理解和应用这些结构。
26 14
|
2月前
|
程序员 Go
go语言中结构体(Struct)
go语言中结构体(Struct)
119 71
|
2月前
|
存储 Go 索引
go语言中的数组(Array)
go语言中的数组(Array)
120 67
|
15天前
|
存储 监控 算法
内网监控系统之 Go 语言布隆过滤器算法深度剖析
在数字化时代,内网监控系统对企业和组织的信息安全至关重要。布隆过滤器(Bloom Filter)作为一种高效的数据结构,能够快速判断元素是否存在于集合中,适用于内网监控中的恶意IP和违规域名筛选。本文介绍其原理、优势及Go语言实现,提升系统性能与响应速度,保障信息安全。
25 5
|
25天前
|
算法 安全 Go
Go语言中的加密和解密是如何实现的?
Go语言通过标准库中的`crypto`包提供丰富的加密和解密功能,包括对称加密(如AES)、非对称加密(如RSA、ECDSA)及散列函数(如SHA256)。`encoding/base64`包则用于Base64编码与解码。开发者可根据需求选择合适的算法和密钥,使用这些包进行加密操作。示例代码展示了如何使用`crypto/aes`包实现对称加密。加密和解密操作涉及敏感数据处理,需格外注意安全性。
41 14
|
25天前
|
Go 数据库
Go语言中的包(package)是如何组织的?
在Go语言中,包是代码组织和管理的基本单元,用于集合相关函数、类型和变量,便于复用和维护。包通过目录结构、文件命名、初始化函数(`init`)及导出规则来管理命名空间和依赖关系。合理的包组织能提高代码的可读性、可维护性和可复用性,减少耦合度。例如,`stringutils`包提供字符串处理函数,主程序导入使用这些函数,使代码结构清晰易懂。
70 11
|
25天前
|
存储 安全 Go
Go语言中的map数据结构是如何实现的?
Go 语言中的 `map` 是基于哈希表实现的键值对数据结构,支持快速查找、插入和删除操作。其原理涉及哈希函数、桶(Bucket)、动态扩容和哈希冲突处理等关键机制,平均时间复杂度为 O(1)。为了确保线程安全,Go 提供了 `sync.Map` 类型,通过分段锁实现并发访问的安全性。示例代码展示了如何使用自定义结构体和切片模拟 `map` 功能,以及如何使用 `sync.Map` 进行线程安全的操作。