使用定时记忆化优化 API 响应速度:C# 实战经验

263 阅读4分钟

简介:

在现代软件开发中,优化 API 性能至关重要。一个有效的技术是缓存昂贵操作的结果。在这篇博客文章中,我们将探讨使用定时记忆化在 C# 中实现缓存的一种简单而强大的方法。我们将详细解释其实现,讨论其优点,并与 Redis 等其他流行的缓存解决方案进行比较。


第一部分:理解定时记忆化

什么是记忆化?

记忆化是一种通过存储昂贵函数调用的结果并在相同输入再次出现时返回缓存结果来提高函数性能的技术。

C# 中的定时记忆化:

在 C# 中,定时记忆化通过为缓存结果添加基于时间的过期机制扩展了这个概念。这确保了缓存保持新鲜并减少了内存占用。

代码实现:

方法 1:单函数记忆化

public static Func<R> Memoize<R>(Func<R> func, int clearSeconds)
{
    int GetKey() => 1;

    int clearMs = clearSeconds * 1000;
    ConcurrentDictionary<int, R> cache = new ConcurrentDictionary<int, R>();
    ConcurrentDictionary<int, Timer> timers = new ConcurrentDictionary<int, Timer>();

    TimerCallback clearCallback = new TimerCallback(key => {
        lock (cache)
        {
            cache.TryRemove((int)key, out R removedItem);
            timers.TryRemove((int)key, out Timer removedTimer);
        }
    });

    return () =>
    {
        int key = GetKey();

        lock (cache)
        {
            if (cache.TryGetValue(key, out R r))
            {
                return r;
            }
            else
            {
                r = func();
                cache.TryAdd(key, r);

                Timer timer = new Timer(clearCallback, key, clearMs, Timeout.Infinite);
                timers.TryAdd(key, timer);
                return r;
            }
        }
    };
}

方法 2:参数化函数记忆化

public static Func<T1, R> Memoize<T1, R>(Func<T1, R> func, int clearSeconds)
{
    int GetKey(T1 t1) => t1.GetHashCode();

    int clearMs = clearSeconds * 1000;
    ConcurrentDictionary<int, R> cache = new ConcurrentDictionary<int, R>();
    ConcurrentDictionary<int, Timer> timers = new ConcurrentDictionary<int, Timer>();

    TimerCallback clearCallback = new TimerCallback(key => {
        lock (cache)
        {
            cache.TryRemove((int)key, out R removedItem);
            timers.TryRemove((int)key, out Timer removedTimer);
        }
    });

    return t1 =>
    {
        lock (cache)
        {
            int key = GetKey(t1);

            if (cache.TryGetValue(key, out R r))
            {
                return r;
            }
            else
            {
                r = func(t1);
                cache.TryAdd(key, r);

                Timer timer = new Timer(clearCallback, key, clearMs, Timeout.Infinite);
                timers.TryAdd(key, timer);

                return r;
            }
        }
    };
}

解释:

方法 1:单函数记忆化

  • GetKey 方法:一个总是返回 1 的占位符方法。
  • 缓存和定时器初始化:使用 ConcurrentDictionary 存储缓存值和定时器。
  • 定时器回调:在指定持续时间后清除缓存和定时器条目。
  • Memoize 方法:检查结果是否已缓存,如果找到则返回,否则执行函数,缓存结果,并设置定时器清除缓存。

方法 2:参数化函数记忆化

  • GetKey 方法:基于输入参数的哈希码生成唯一键。这允许不同输入值的不同缓存条目。
  • 缓存和定时器初始化:与第一个方法类似,使用 ConcurrentDictionary 存储缓存值和定时器。
  • 定时器回调:与第一个方法相同,在指定持续时间后清除缓存和定时器条目。
  • Memoize 方法:使用动态键检查结果是否已缓存,如果找到则返回,否则执行带参数的函数,缓存结果,并设置定时器清除缓存。

第二部分:定时记忆化在实际场景中的应用

缓存 API 响应:

考虑一个获取仪表板中部件数据的函数。由于数据库查询或 API 调用,获取这些数据可能代价高昂。通过应用定时记忆化,我们可以显著减少负载并提高响应速度。

使用方法 1:单函数记忆化

Func<int> expensiveCalculation = () => {
    // 模拟昂贵的计算
    return new Random().Next();
};
var memoizedCalculation = TimedMemoization.Memoize(expensiveCalculation, 60);

使用方法 2:参数化函数记忆化

Func<int, int> squareCalculation = x => x * x;
var memoizedSquare = TimedMemoization.Memoize(squareCalculation, 60);

第三部分:定时记忆化的优点

提高响应速度:

缓存减少了获取重复请求数据所需的时间,提高了 API 的响应速度。

资源优化:

通过缓存结果,可以减少数据库或外部服务的负载,从而更好地利用资源。

自动缓存过期:

定时记忆化确保缓存数据在指定持续时间后自动清除,保持缓存的更新。


第四部分:与 Redis 的比较

Redis 缓存:

Redis 是一个流行的内存数据存储,支持高级缓存功能。它具有高度可扩展性,并提供精细的缓存管理控制。

主要区别:

  • 设置和复杂性:

    • 定时记忆化: 简单实现,不需要外部依赖。
    • Redis: 需要设置 Redis 服务器并将其集成到应用中。
  • 可扩展性:

    • 定时记忆化: 适用于缓存需求有限的应用。
    • Redis: 适用于具有复杂缓存需求的大规模应用。
  • 缓存管理:

    • 定时记忆化: 基本的基于时间的过期。
    • Redis: 支持各种驱逐策略、持久化和高级数据结构。

何时使用哪种方法:

  • 定时记忆化: 适用于简单的、具有直接过期策略的单函数缓存。
  • Redis: 适用于复杂的缓存需求,需要可扩展性、分布式缓存和高级功能时。

结论:

定时记忆化是一种通过缓存昂贵函数结果来提高 API 响应速度的简单而有效的技术。虽然它简单易实现,但对于更复杂和可扩展的解决方案,Redis 仍然是一个强大的替代方案。通过理解和利用这些缓存技术,您可以提升应用程序的性能和效率。

在您的项目中尝试定时记忆化,看看性能的差异。对于大型应用,探索 Redis 及其强大的缓存功能。

在评论区分享您的经验和见解吧!