Redis集群使用keys命令搜索匹配的Key

针对大量节点在线状态的实时更新及查询需求,介绍了一种利用Redis集群特性优化查询效率的方法。通过对集群节点进行分区搜索,有效解决了单线程环境下批量查询造成的性能瓶颈。

需求

今天在项目过程中碰到了一个需求:

1.所有节点(数量在1~10万)都会存储一个在线状态的Key在Redis集群,3分钟失效, 收到节点上报的任何数据后,刷新这个缓存。

2.需定时查询所有节点在Redis的状态,并将节点状态同步到数据库。

问题

因为Redis只有顶级的Key才可以设置超时时间,所以无法使用hset,hgetAll命令一次
性获取所有的节点信息,只有使用get(key)方法一个一个获取设备的在线状态,由于
Redis是单线程应用,这就造成了比较严重的性能问题。

经测试循环获取8万个节点的在线状态,耗时会达到50s左右,这是完全无法容忍的。

而项目中为保证可靠性,使用的是Redis集群,Redis操作都必须通过JedisCluster的
接口来实现,故而也无法使用Jedis的keys(pattern)方法来一次性搜索所有的在线节
点的Key。

解决思路

网络上搜索了一些解决方法,都感觉有些复杂,所以自己考虑是否可以用更简单的方
式解决这个问题。

思路是Redis集群有6个节点,其中有3个主节点,每个主节点各自有一个从节点,其
实只需要将集群视为3个单独的Redis,分别用keys(pattern)方法搜索各自的key,然
后把3次搜索的结果相加,就是集群内所有的Key了。

JedisCluster在网上的API很少,进去这个类看了一下,发现有一个方法:

Map<String,JedisPool> getClusterNodes();
复制代码

根据方法名和返回值判断,这应该就是我要找的获取Redis集群所有节点的方法了,
写了个方法测试了一下,果然可以拿到集群内所有的节点,那剩下的事情就好办了。

最终的搜索集群内匹配的Key方法如下:

/**
 * 搜索集群内匹配的Key
 *
 * @param pattern 匹配规则,该参数和Jedis keys(pattern)方法相同。
 *                eg:搜索Node_status_开头的所有key,该参数填Node_status_*
 * @return 集群内所有符合规则的Key列表
 */
public Set<String> clusterKeys(String pattern)throws DataLoadException{
    Set<String> result = new HashSet<>();
    try {
        // 获取Redis集群内所有节点
        Map<String, JedisPool> clusterNodes = jedisCluster.getClusterNodes();

        for (Map.Entry<String, JedisPool> entry : clusterNodes.entrySet()) {
            Jedis jedis = entry.getValue().getResource();
            // 判断非从节点(因为若主从复制,从节点会跟随主节点的变化而变化)
            if (!jedis.info("replication").contains("role:slave")) {
                // 搜索单个节点内匹配的Key
                Set<String> keys = jedis.keys(pattern);
                // 合并搜索结果
                result.addAll(keys);
            }
            jedis.close();
        }
    } catch (Exception e) {
        throw new DataLoadException(e);
    } finally {
        returnJedisCluster(jedisCluster);
    }
    return result;
}
复制代码

经测试,该方法一次性搜索8万个节点的在线状态,耗时在100ms左右。

Redis 集群环境中使用 `SCAN` 命令查找所有指定的,需要特别注意 Redis 集群的数据分片特性。由于数据分布在多个节点上,不能像单机 Redis 那样简单地执行一次 `SCAN` 就完成全部的检索。 ### 使用 `SCAN` 命令的基本方式 `SCAN` 命令是一种基于游标的迭代器,用于增量地遍历空间。基本语法如下: ```shell SCAN cursor [MATCH pattern] [COUNT count] ``` - `cursor` 是游标值,初始为 `0`。 - `MATCH` 是可选参数,用于指定匹配模式。 - `COUNT` 是建议每次迭代返回的元素数量,默认是 `10`。 例如,查找所有匹配 `user:*` 的: ```shell SCAN 0 MATCH user:* COUNT 100 ``` 在单个节点上,可以循环执行 `SCAN`,直到返回的游标值为 `0`,表示遍历完成。 ### 在 Redis 集群中遍历所有节点 由于 Redis 集群将数据分布在多个节点上,要查找所有指定的,必须对每个主节点分别执行 `SCAN` 操作。 #### 步骤: 1. 获取集群节点信息,确定所有主节点的地址和端口。 2. 对每个主节点建立连接。 3. 在每个节点上执行 `SCAN` 命令,遍历所有匹配。 4. 收集所有节点的扫描结果。 #### 示例(使用 Python 脚本): 以下是一个使用 `redis-py-cluster` 库实现的示例脚本,用于在 Redis 集群中查找所有匹配 `user:*` 的: ```python from rediscluster import RedisCluster # 初始化集群连接 startup_nodes = [ {"host": "127.0.0.1", "port": "6380"}, {"host": "127.0.0.1", "port": "6381"}, {"host": "127.0.0.1", "port": "6382"}, # 添加更多节点... ] # 创建 Redis 集群客户端 rc = RedisCluster(startup_nodes=startup_nodes, decode_responses=True) # 遍历所有节点 for node in rc.get_nodes(): if node.server_type == 'master': # 仅对主节点执行扫描 conn = rc.connection_pool.get_connection_by_node(node) cursor = 0 pattern = "user:*" count = 100 while True: result = conn.execute_command("SCAN", cursor, "MATCH", pattern, "COUNT", count) cursor = int(result[0]) keys = result[1] for key in keys: print(f"Found key: {key} on node {node}") if cursor == 0: break ``` ### 注意事项 - **性能影响**:大规模数据扫描会对 Redis 性能产生影响,建议在低峰期操作。 - **游标管理**:确保游标正确更新,避免遗漏或重复。 - **节点连接**:确保能够连接到所有主节点,并处理可能的连接异常。 - **模式匹配**:`MATCH` 模式应尽量具体,以减少不必要的扫描。 通过上述方法,可以在 Redis 集群环境中高效地查找所有指定的[^2]。
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值