You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用StackExchange.Redis扫描Redis键时KeyAsync循环为何挂起?

Redis键扫描挂起问题排查与修复方案

1. 确保Redis连接完全建立

ConnectionMultiplexer.Connect在AbortOnConnectFail=false时会立即返回实例,但此时连接可能尚未成功建立,后续操作会因等待连接就绪而挂起。

修改连接初始化逻辑,等待连接稳定后再返回:

builder.Services.AddSingleton<IConnectionMultiplexer>(async serviceProvider =>
{
    var loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>();

    const string redisHost = ...
    const int redisPort = ...
    const string redisPassword = ...

    var redisConfiguration = new ConfigurationOptions
    {
        EndPoints = { $"{redisHost}:{redisPort}" },
        Password = redisPassword,
        Ssl = true,
        AbortOnConnectFail = false,
        ConnectTimeout = 15000,
        SyncTimeout = 15000,
        LoggerFactory = loggerFactory
    };

    var multiplexer = await ConnectionMultiplexer.ConnectAsync(redisConfiguration);
    // 等待连接成功建立
    await multiplexer.WaitUntilConnectedAsync();
    return multiplexer;
});

2. 修正主节点判断逻辑

server.IsReplica在部分Redis部署(如集群、哨兵模式)中可能判断不准确,导致跳过实际主节点。改用ServerType判断:

if (!server.IsConnected || server.ServerType != ServerType.Master)
{
    continue;
}

3. 优化KeysAsync调用参数

  • 增大pageSize提升扫描效率(默认值过小可能导致迭代缓慢)
  • 确保数据库编号与目标库一致
  • 确认模式匹配语法符合Redis SCAN规则(*为通配符,{hangfire}为哈希槽占位符,需与实际键格式匹配)

调整后的扫描代码:

await foreach (var key in server.KeysAsync(
    database: db.Database,
    pattern: pattern,
    pageSize: 1000).WithCancellation(cancellationToken))
{
    keys.Add(key);
}

4. 排查CancellationToken状态

StartAsync的cancellationToken会在宿主服务停止时触发,若程序启动阶段出现异常导致宿主终止,会直接取消扫描操作。添加日志辅助排查:

// 在DataMigration中注入ILogger<DataMigration>
logger.LogInformation("开始扫描Redis键,匹配模式:{Pattern}", pattern);

await foreach (var key in server.KeysAsync(...))
{
    logger.LogDebug("找到键:{Key}", key);
    keys.Add(key);
}

5. 升级StackExchange.Redis版本

旧版本的KeysAsync存在异步迭代挂起的已知bug,升级至最新稳定版(如2.7.x及以上)可解决此类问题。


完整修复后的DataMigration示例

public class DataMigration(IConnectionMultiplexer connection, ILogger<DataMigration> logger) : IHostedService
{
    public async Task StartAsync(CancellationToken cancellationToken)
    {
        const string pattern = "{hangfire}:recurring-job:EmailNotificationJob:*";
        List<RedisKey> keys = [];

        var db = connection.GetDatabase();
        logger.LogInformation("启动Redis键扫描,模式:{Pattern}", pattern);

        foreach (var endpoint in connection.GetEndPoints())
        {
            var server = connection.GetServer(endpoint);

            if (!server.IsConnected || server.ServerType != ServerType.Master)
            {
                logger.LogDebug("跳过端点 {Endpoint}:未连接或非主节点", endpoint);
                continue;
            }

            logger.LogDebug("扫描端点 {Endpoint} 的键", endpoint);
            await foreach (var key in server.KeysAsync(
                database: db.Database,
                pattern: pattern,
                pageSize: 1000).WithCancellation(cancellationToken))
            {
                logger.LogDebug("发现键:{Key}", key);
                keys.Add(key);
            }
        }

        logger.LogInformation("共找到 {KeyCount} 个待更新的键", keys.Count);
        Console.WriteLine($"Found {keys.Count} keys to update.");
    }

    public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}

内容的提问来源于stack exchange,提问作者nop

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 09:57:38