我会把多个应用程序实例的问题放在次要位置。并不是说没关系,但是如果您正在针对接口进行编程,那么在某些时候您可以用缓存的东西替换您的实现。
如果您希望平均请求时间超过五分钟,那么您将需要一个弹出过期条目的列表。这是一个尝试:
internal class TimestampedEntry<T>
{
internal DateTimeOffset Timestamp { get; private set; }
internal T Value { get; private set; }
internal TimestampedEntry(T value)
{
Timestamp = DateTimeOffset.Now;
Value = value;
}
}
public class ExpiringList<T>
{
private readonly List<TimestampedEntry<T>> _list = new List<TimestampedEntry<T>>();
private readonly TimeSpan _expiration;
public ExpiringList(TimeSpan expiration)
{
_expiration = expiration;
}
public void Add(T item)
{
lock (_list)
{
_list.Add(new TimestampedEntry<T>(item));
}
}
public IReadOnlyCollection<T> Read()
{
var cutoff = DateTimeOffset.Now - _expiration;
TimestampedEntry<T>[] result;
lock (_list)
{
result = _list.Where(item => item.Timestamp > cutoff).ToArray();
_list.Clear();
_list.AddRange(result);
}
return new ReadOnlyCollection<T>(result.Select(item => item.Value).ToList());
}
}
这确保当您从列表中读取时,它只返回存储在指定时间间隔内的项目,并删除其余项目。您可以创建一个ExpiringList<TimeSpan>
,添加每个调用的经过时间,然后根据需要检查平均值。
在哪里存放?我会把它放在一个只有一个实例的类中。那可能是一个单例或一个静态类。我更喜欢使用返回单个实例的依赖注入容器(如Windsor 的单例生活方式)。我不喜欢创建单例。我宁愿创建一个“普通”类,然后管理它以保留一个实例。像 Windsor 这样的 DI 容器让这一切变得简单。
我认为像这样的实现中的一个重要因素是将混乱的切换逻辑分开 - 隐藏在某种工厂中,而不是if/then
使用所有逻辑来检查平均响应时间并在一个大类中调用任一 API。
例如,如果您有一个表示获取数据调用的接口,例如IMyDataProvider
,那么您可以定义一个工厂,例如
interface IMyDataProviderFactory
{
IMyDataProvider Create();
}
您的类只依赖于该工厂接口。实现的类IMyDataProviderFactory
检查您的平均响应时间并返回IMyDataProvider
调用外部 API 的实现或使用您的计算的实现。
这样,该逻辑的复杂性就与依赖于 API 的任何类保持分离。
温莎也很擅长那些抽象工厂。其他 DI 容器也使它们变得简单,这种功能内置在 ASP.NET Core 中。您不是在询问依赖注入,但我建议您调查一下。它使管理这种复杂性并保持可维护性变得更加容易。
回到多个应用程序实例和分布式缓存 - 您可以看到工厂模式实现如何使这更易于管理。假设今天这是一个实例,但明天您想通过分布式缓存共享此数据。你在哪里做出改变?大多数依赖此 API 的代码根本不需要更改,因为它不“知道”任何这些实现细节。您将更改存储每个 API 调用时间的代码并更改工厂的实现。