PAYATHON 2026

StackExchange.Redis 异步调用为何出现 sync-ops?

数据小陈

结论

日志中的 sync-ops=37097 是该连接相关的同步操作累计计数,不能仅凭这个数字判断当前超时是由同步调用造成的。你贴出的 StringGetAsync 使用了异步 API,但这不能说明进程里的所有 Redis 操作都走异步路径。还需要检查其他调用方、RedLock 集成和连接初始化代码。

日志还显示 POOL: (Threads=28,QueuedItems=1715,...),工作线程最小值为 2,排队项有 1715 个。这些数据提示当时可能存在 ThreadPool 排队或线程饥饿,但单条日志不足以确认原因。如果有数据支持,可以把最小工作线程数设为 10 作为缓解措施,同时继续排查阻塞和排队来源。

sync-ops 表示什么

sync-ops 和 async-ops 是诊断计数,不代表当前正在执行的同步或异步操作数,也不会指出具体是哪一行代码发起了某个命令。sync-ops 不为零,说明该连接的统计范围内曾发生过同步 Redis 操作;日志没有记录这些操作的调用位置。

你给出的代码:

private AsyncPolicyWrap GetPolicy()
{
    return Policy.WrapAsync(redisConnectionRetryPolicy, customRetryPolicy ?? defaultRedisTimeoutPolicy);
}

public async Task<RedisValue> StringGetAsync(RedisKey key)
{
    return await GetPolicy().ExecuteAsync(() => redis.StringGetAsync(key));
}

这里调用的是异步的 StringGetAsync。异步重试不会把这个 API 调用变成同步调用。可以先检查进程中其他使用同一 ConnectionMultiplexer 的代码,包括其他 Redis 读写、锁操作、启动或健康检查逻辑,以及第三方库的具体版本和调用路径。仅凭使用了 RedLock,无法确定它是否增加了这些同步计数;还需要结合所用 RedLock 包的版本、配置和代码确认。

如何排查超时和线程池排队

日志显示请求等待了 6000ms,配置的 timeout 为 3000ms。这两个值不足以确定超时发生在哪个阶段,也无法据此判断是 Redis 服务端响应慢、客户端线程池拥塞,还是同步调用造成的。建议收集同一时间段的多条异常和应用指标,并重点对照以下情况:

  1. sync-ops、async-ops 是否持续增长,是否还有其他连接或调用方。
  2. QueuedItems 是否持续升高,工作线程是否长期处于忙碌状态。
  3. Redis 服务端延迟、网络状况、请求量和重试频率。
  4. 业务代码或依赖库中是否存在 .Result、.Wait() 或同步 Redis API 等同步等待。

重试会增加请求量。超时期间如果大量请求继续重试,也可能让排队加重。检查策略中的重试次数、间隔和超时设置,避免重试放大负载。

是否设置 ThreadPool.SetMinThreads

可以考虑把工作线程最小值从当前配置提高到 10。调整前先确认现有配置,并观测调整前后的队列长度、延迟和 CPU 使用率。示意代码如下:

ThreadPool.GetMinThreads(out var currentWorkerThreads, out var currentCompletionPortThreads);

var updated = ThreadPool.SetMinThreads(10, currentCompletionPortThreads);
if (!updated)
{
    // 记录配置未能应用,并检查参数及运行环境。
}

示例中保留了当前 completion port 线程最小值;不要把来源未说明的 minIOC 直接当作合适值。SetMinThreads 接收工作线程和 completion port 线程两个参数,修改前要确认两者各自的含义,并检查方法返回值。

你提供的 ECS 规格是每个任务 2 vCPU、16 GB RAM,共 4 个任务。仅凭这些规格无法推算出合适的最小线程数。可以通过负载测试或生产观测逐步调整,并检查排队、响应时间和 CPU 使用情况是否改善。如果排队主要由同步阻塞或下游过载造成,提高最小线程数可能只能暂时缓解问题,仍需处理阻塞点或控制并发与重试。

备注:内容仅供参考。