redis - 使用 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。
到目前为止,这是我所知道的:
- 我们每小时会多次看到这些超时异常。通常在一分钟内超时 40-50 次,有时高达 80-90 次。然后,他们会离开几分钟。在过去的 24 小时内,大约有 5,000 起此类事件,并且它们在单个 API 客户端中突然发生。
- 这些超时仅发生在 Redis主节点上,从不发生在从节点上。但是,它们发生在各种 Redis 命令中,例如 GET 和 SET。
- 当这些超时爆发时,调用来自单个 API 服务器,但发生在与各种 Redis 节点通信。例如,API3 可能在尝试调用 Cache1、Cache2 和 Cache3 时出现大量超时。这有力地证明了该问题与 API 服务器有关,而不是与 Redis 服务器有关。
- Redis 主节点有 108 个连接的客户端。我记录了当前的连接,这个数字保持稳定。连接没有大的尖峰,看起来没有任何错误代码创建太多连接或不共享 ConnectionMultiplexer 实例(我有一个,它是静态的)
- Redis 从节点有 58 个连接的客户端,这看起来也完全稳定。
- 我们正在使用 StackExchange.Redis 版本 1.2.6
- Redis 使用 AOF 模式,磁盘大小约为 195MB
这是一个示例超时异常。大多数看起来和这个差不多:
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, ResultProcessor
1 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.RedisCacheProvider
1.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 的连接保持稳定。
解决方案
在global.asax
,在尝试添加:
protected void Application_Start(object sender, EventArgs e)
{
ThreadPool.SetMinThreads(200, 200);
}
对我们来说,这将错误从每天约 50-100 个减少到零。我相信对于设置哪些数字没有一般规则,因为它取决于系统(200 对我们有用),因此可能需要您进行一些试验。
我也相信这提高了网站的性能。
推荐阅读
- angular - 移动 Safari 下载问题:操作无法完成。(webkitblobresource 错误 1。)
- c# - 在启动中调用 AddTransient 中的异步方法 - Asp.Net Core
- perl - 使用 perl 提取最长的肽段
- python - Pandas:将 *multiple* 设置为列表列
- seedstack - 种子堆栈,如何从属性文件中读取基本 url
- r - 我的用户名和密码应该放在 SOAP api 的 POST 的什么位置
- python - 在响应中传递某些值或参数 - django
- xslt - 如何使用 XSLT 转换 XML 文档
- c# - C#泛型委托中的协变/逆变问题
- maven - 在同一浏览器上拆分并运行部分测试以更快地运行