首页 > 解决方案 > 使用 StackExchange.Redis 的 RedisTimeoutException 突发

问题描述

我正在尝试使用 StackExchange Redis 库跟踪间歇性“突发”超时。下面是我们的设置:我们的 API 是用 C# 编写的,并且在 Windows 2008 和 IIS 上运行。我们有 4 台 API 服务器在生产中,我们有 4 台 Redis 机器(运行 Linux 最新的 LTS),每台机器都有 2 个 Redis 实例(一个主机在端口 7000,一个从机在端口 7001)。我已经研究了 Redis 服务器的几乎所有方面,它们看起来很棒。日志中没有错误,CPU 和网络都很棒,服务器端的一切看起来都很棒。我可以tail -f在发生这种情况时查看 Redis 日志,并且看不到任何异常(例如重写 AOF 文件或任何东西)。我认为问题不在于 Redis。

到目前为止,这是我所知道的:

这是一个示例超时异常。大多数看起来和这个差不多:

Type=StackExchange.Redis.RedisTimeoutException,Message=超时执行 GET Limeade:allActivities, inst: 1, mgr: ExecuteSelect, err: never, queue: 0, qu: 0, qs: 0, qc: 0, wr: 0, wq : 0, in: 0, ar: 0, clientName: LIMEADEAPI4, serverEndpoint: 10.xx.xx.11:7000, keyHashSlot: 1295, IOCP: (Busy=0,Free=1000,Min=2,Max=1000) , WORKER: (Busy=9,Free=32758,Min=2,Max=32767) (请看这篇文章,了解一些可能导致超时的常见客户端问题:http: //stackexchange.github.io/ StackExchange.Redis/Timeouts),StackTrace= at StackExchange.Redis.ConnectionMultiplexer.ExecuteSyncImpl[T](Message message, ResultProcessor 1 processor, ServerEndPoint server) at StackExchange.Redis.ConnectionMultiplexer.ExecuteSyncImpl[T](Message message, ResultProcessor1 processor, ServerEndPoint server) at StackExchange.Redis.RedisBase.ExecuteSync[T](Message message, ResultProcessor1 processor, ServerEndPoint server) at StackExchange.Redis.RedisDatabase.StringGet(RedisKey key, CommandFlags flags) at Limeade.Caching.Providers.RedisCacheProvider1.Get[T](K cacheKey, CacheItemVersion& cacheItemVersion) in ...

我对追踪这些超时异常进行了一些研究,但令人惊讶的是所有数字都为零。队列中没有任何内容,也没有等待处理的内容,我有大量线程空闲并且什么也没做。一切看起来都很棒。

有人对如何解决这个问题有任何想法吗?问题是这些缓存超时的爆发会导致我们的数据库受到更多的打击,在某些情况下这是一件坏事。我很高兴添加更多任何人都会觉得有帮助的信息。

更新:连接代码

连接到 Redis 的代码是一个相当复杂的系统的一部分,它支持各种缓存环境和配置,但我可以将其归结为基础。首先,有一个CacheFactory类:

public class CacheFactory : ICacheFactory
{
    private static readonly ILogger log = LoggerManager.GetLogger(typeof(CacheFactory));
    private static readonly ICacheProvider<CacheKey> cache;

    static CacheFactory()
    {
        ICacheFactory<CacheKey> configuredFactory = CacheFactorySection.Current?.CreateConfiguredFactory<CacheKey>();
        if (configuredFactory == null)
        {
           // Some error handling, not important
        }

        cache = configuredFactory.GetDefaultCache();
    }

    // ...
}

ICacheProvider是实现与某个可以配置的缓存系统对话的方法。在这种情况下,configuredFactory 是RedisCacheFactory这样的:

public class RedisCacheFactory<T> : ICacheFactory<T> where T : CacheKey, ICacheKeyRepository
{
    private RedisCacheProvider<T> provider;
    private readonly RedisConfiguration configuration;

    public RedisCacheFactory(RedisConfiguration config)
    {
        this.configuration = config;
    }

    public ICacheProvider<T> GetDefaultCache()
    {
        return provider ?? (provider = new RedisCacheProvider<T>(configuration));
    }
}

GetDefaultCache方法在静态构造函数中调用一次,并返回一个RedisCacheProvider. 这个类是真正连接到 Redis 的:

public class RedisCacheProvider<K> : ICacheProvider<K> where K : CacheKey, ICacheKeyRepository
{
    private readonly ConnectionMultiplexer redisConnection;
    private readonly IDatabase db;
    private readonly RedisCacheSerializer serializer;
    private static readonly ILog log = Logging.RedisCacheProviderLog<K>();
    private readonly CacheMonitor<K> cacheMonitor;
    private readonly TimeSpan defaultTTL;
    private int connectionErrors;

    public RedisCacheProvider(RedisConfiguration options)
    {
        redisConnection = ConnectionMultiplexer.Connect(options.EnvironmentOverride ?? options.Connection);
        db = redisConnection.GetDatabase();
        serializer = new RedisCacheSerializer(options.SerializationBinding);
        cacheMonitor = new CacheMonitor<K>();
        defaultTTL = options.DefaultTTL;

        IEnumerable<string> hosts = options.Connection.EndPoints.Select(e => (e as DnsEndPoint)?.Host);
        log.InfoFormat("Created Redis ConnectionMultiplexer connection.  Hosts=({0})", String.Join(",", hosts));
    }

    // ...
 }

构造函数根据配置的 Redis 端点(在某些配置文件中)创建一个 ConnectionMultiplexer。每次创建连接时,我也会记录。我们没有看到任何过多的这些日志语句,并且与 Redis 的连接保持稳定。

标签: redisstackexchange.redis

解决方案


global.asax,在尝试添加:

protected void Application_Start(object sender, EventArgs e)
{
    ThreadPool.SetMinThreads(200, 200);
}

对我们来说,这将错误从每天约 50-100 个减少到零。我相信对于设置哪些数字没有一般规则,因为它取决于系统(200 对我们有用),因此可能需要您进行一些试验。

我也相信这提高了网站的性能。


推荐阅读