【WCF】单例模式的线程安全缓存管理器实现,给你的WebApi加入缓存吧

发布于:2025-06-25 ⋅ 阅读:(20) ⋅ 点赞:(0)

WCF 系列文章目录

链接: 【WCF】基于WCF在WinForms搭建RESTful服务指南



前言

缓存是我们日常优化程序的老面孔。尤其是那种需要复杂计算的服务,又或是涉及和数据库交互更具大量数据生成报表内容的场景。我们都可以通过调用缓存,来使数据库查询次数减少,可降低 IO 压力。

本文将为大家介绍如何在WCF构建的RESTful服务里添加缓存的功能。通过一个泛型静态类实现缓存管理器,用于实现基于内存的键值对缓存功能。采用了单例模式,并提供了线程安全的缓存操作。该存管理器不依赖于WCF。

一、缓存项结构搭建

  • 内部类CacheItem,用于封装缓存数据及其时间戳。
	/// <summary>
	/// 缓存项结构,包含数据和时间戳
	/// </summary>
	private class CacheItem
	{
	    public T Data { get; set; }
	    public DateTime LastUpdateTime { get; set; }
	}
  • _cacheDict:线程安全的字典,ConcurrentDictionary类,存储所有缓存项。
  • _keyLocks:为每个缓存键提供独立的锁对象,实现细粒度锁控制。
  • _defaultCacheDuration:默认缓存过期时间(60 秒)。
	private static readonly ConcurrentDictionary<string, CacheItem> _cacheDict
	    = new ConcurrentDictionary<string, CacheItem>();
	
	private static readonly ConcurrentDictionary<string, object> _keyLocks
	    = new ConcurrentDictionary<string, object>();
	
	private static readonly TimeSpan _defaultCacheDuration = TimeSpan.FromSeconds(60);

二、核心方法

GetCache是该缓存管理器的核心方法,用于获取缓存项,如果不存在或已过期则重新加载。
它接收缓存键,数据获取委托两个必须参数,和自定义缓存时间,当数据获取失败时是否使用旧数据两个可选项。

执行步骤
执行GetCache方法,首先检查缓存键是否存在,无效则使用类型全名。然后通过TryGetValidCacheItem尝试获取有效缓存项。
如果缓存无效,获取键专用锁,并进行双重检查机制,避免多线程重复加载。最后调用数据获取委托dataRetriever,更新缓存并返回新数据。
如果缓存有效,则直接放回缓存数据。

	/// <summary>
	/// 获取或创建缓存
	/// </summary>
	/// <param name="cacheKey">缓存键(为空时使用类型全名)</param>
	/// <param name="dataRetriever">数据获取委托</param>
	/// <param name="cacheDuration">自定义缓存时间(默认60秒)</param>
	/// <param name="useStaleData">当数据获取失败时是否使用旧数据</param>
	public static T GetCache(
	    string cacheKey,
	    Func<T> dataRetriever,
	    TimeSpan? cacheDuration = null,
	    bool useStaleData = true)
	{
	    cacheKey = string.IsNullOrEmpty(cacheKey) ? typeof(T).FullName : cacheKey;
	    var duration = cacheDuration ?? _defaultCacheDuration;
	
	    //尝试获取有效缓存
	    if (TryGetValidCacheItem(cacheKey, duration, out var cachedItem))
	    {
	        return cachedItem.Data;
	    }
	
	    //获取键级专用锁
	    var keyLock = _keyLocks.GetOrAdd(cacheKey, (_) => new object());
	
	    lock (keyLock)
	    {
	        // 双重检查
	        if (TryGetValidCacheItem(cacheKey, duration, out cachedItem))
	        {
	            return cachedItem.Data;
	        }
	        try
	        {
	            //尝试获取新数据
	            var newData = dataRetriever();
	            UpdateCache(cacheKey, newData);
	            return newData;
	        }
	        catch
	        {
	            //降级策略:如果允许返回过期数据
	            if (useStaleData && _cacheDict.TryGetValue(cacheKey, out cachedItem))
	            {
	                return cachedItem.Data;
	            }
	            throw;
	        }
	    }
	}
	
	/// <summary>
	/// 更新缓存
	/// </summary>
	public static void UpdateCache(string cacheKey, T data)
	{
	    _cacheDict[cacheKey] = new CacheItem
	    {
	        Data = data,
	        LastUpdateTime = DateTime.Now
	    };
	}
	
	/// <summary>
	/// 清除指定缓存
	/// </summary>
	public static void ClearCache(string cacheKey)
	{
	    _cacheDict.TryRemove(cacheKey, out _);
	    _keyLocks.TryRemove(cacheKey, out _); // 同时清理锁
	}
	
	/// <summary>
	/// 清除所有缓存
	/// </summary>
	public static void ClearAll()
	{
	    _cacheDict.Clear();
	    _keyLocks.Clear();
	}
	
	/// <summary>
	/// 检查缓存项是否有效
	/// </summary>
	/// <param name="key"></param>
	/// <param name="duration"></param>
	/// <param name="item"></param>
	/// <returns></returns>
	private static bool TryGetValidCacheItem(string key, TimeSpan duration, out CacheItem item)
	{
	    //用于判断_cacheDict字典里是否有【key】对应的缓存项,并且赋值给item。返回值是bool类型
	    if (_cacheDict.TryGetValue(key, out item))
	    {
	        //进一步判断缓存项是否在指定时间段内
	        return (DateTime.Now - item.LastUpdateTime) <= duration;
	    }
	    return false;
	}

三、引用示例

因为CacheManager是静态类,我们将泛型指定为需要的返回结果类型。然后通过一个委托参数执行获取数据的方法。如果想设置自定义缓存时间,当数据获取失败时是否使用旧数据这填入相应参数。
这里通过Thread.Sleep(5000)模拟耗时方法执行。

/// <summary>
/// 获取所有学生
/// </summary>
/// <returns></returns>
public JsonResult<List<Student>> GetStudents()
{
    try
    {
        var cacheData = CacheManager<List<Student>>.GetCache("GetStudents",() => {
            Thread.Sleep(5000);
            return new List<Student>
            {
                new Student { Id = 1, Name = "张三" },
                new Student { Id = 2, Name = "李四" }
            };
        });
        return new JsonResult<List<Student>>
        {
            code = "200",
            msg = "success",
            data = cacheData
        };
        
    }
    catch (Exception ex)
    {
        return new JsonResult<List<Student>>
        {
            code = "500",
            msg = $"获取学生列表失败: {ex.Message}",
            data = null
        };
    }
}

/// <summary>
/// 获取指定学生
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public JsonResult<Student> GetStudent(string id)
{
    try
    {
        if (!int.TryParse(id, out int studentId))
        {
            return new JsonResult<Student>
            {
                code = "400",
                msg = "无效的学生ID",
                data = null
            };
        }

        var cacheData = CacheManager<Student>.GetCache($"GetStudent_{studentId}", () => {
            Thread.Sleep(5000);
            return _students.FirstOrDefault(s => s.Id == studentId);
        });
        if (cacheData == null)
        {
            return new JsonResult<Student>
            {
                code = "404",
                msg = "未找到指定学生",
                data = null
            };
        }

        return new JsonResult<Student>
        {
            code = "200",
            msg = "success",
            data = cacheData
        };
    }
    catch (Exception ex)
    {
        return new JsonResult<Student>
        {
            code = "500",
            msg = $"获取学生失败: {ex.Message}",
            data = null
        };
    }
}

执行耗时方法
执行耗时方法
调用缓存

在这里插入图片描述

四、完整代码

 public static class CacheManager<T>
{
    /// <summary>
    /// 缓存项结构,包含数据和时间戳
    /// </summary>
    private class CacheItem
    {
        public T Data { get; set; }
        public DateTime LastUpdateTime { get; set; }
    }

    /// <summary>
    /// 缓存字典
    /// </summary>
    private static readonly ConcurrentDictionary<string, CacheItem> _cacheDict
        = new ConcurrentDictionary<string, CacheItem>();

    /// <summary>
    /// 缓存锁,只会根据键锁定指定的缓存项
    /// </summary>
    private static readonly ConcurrentDictionary<string, object> _keyLocks
        = new ConcurrentDictionary<string, object>();

    private static readonly TimeSpan _defaultCacheDuration = TimeSpan.FromSeconds(55);

    /// <summary>
    /// 获取或创建缓存
    /// </summary>
    /// <param name="cacheKey">缓存键(为空时使用类型全名)</param>
    /// <param name="dataRetriever">数据获取委托</param>
    /// <param name="cacheDuration">自定义缓存时间(默认55秒)</param>
    /// <param name="useStaleData">当数据获取失败时是否使用旧数据</param>
    public static T GetCache(
        string cacheKey,
        Func<T> dataRetriever,
        TimeSpan? cacheDuration = null,
        bool useStaleData = true)
    {
        cacheKey = string.IsNullOrEmpty(cacheKey) ? typeof(T).FullName : cacheKey;
        var duration = cacheDuration ?? _defaultCacheDuration;

        //尝试获取有效缓存
        if (TryGetValidCacheItem(cacheKey, duration, out var cachedItem))
        {
            return cachedItem.Data;
        }

        //获取键级专用锁
        var keyLock = _keyLocks.GetOrAdd(cacheKey, (_) => new object());

        lock (keyLock)
        {
            // 双重检查
            if (TryGetValidCacheItem(cacheKey, duration, out cachedItem))
            {
                return cachedItem.Data;
            }
            try
            {
                //尝试获取新数据
                var newData = dataRetriever();
                UpdateCache(cacheKey, newData);
                return newData;
            }
            catch
            {
                //降级策略:如果允许返回过期数据
                if (useStaleData && _cacheDict.TryGetValue(cacheKey, out cachedItem))
                {
                    return cachedItem.Data;
                }
                throw;
            }
        }
    }

    /// <summary>
    /// 更新缓存
    /// </summary>
    public static void UpdateCache(string cacheKey, T data)
    {
        _cacheDict[cacheKey] = new CacheItem
        {
            Data = data,
            LastUpdateTime = DateTime.Now
        };
    }

    /// <summary>
    /// 清除指定缓存
    /// </summary>
    public static void ClearCache(string cacheKey)
    {
        _cacheDict.TryRemove(cacheKey, out _);
        _keyLocks.TryRemove(cacheKey, out _); // 同时清理锁
    }

    /// <summary>
    /// 清除所有缓存
    /// </summary>
    public static void ClearAll()
    {
        _cacheDict.Clear();
        _keyLocks.Clear();
    }

    /// <summary>
    /// 检查缓存项是否有效
    /// </summary>
    /// <param name="key"></param>
    /// <param name="duration"></param>
    /// <param name="item"></param>
    /// <returns></returns>
    private static bool TryGetValidCacheItem(string key, TimeSpan duration, out CacheItem item)
    {
        //用于判断_cacheDict字典里是否有【key】对应的缓存项,并且赋值给item。返回值是bool类型
        if (_cacheDict.TryGetValue(key, out item))
        {
            //进一步判断缓存项是否在指定时间段内
            return (DateTime.Now - item.LastUpdateTime) <= duration;
        }
        return false;
    }
}

总结

这就是一个用于WCF RESTful服务的缓存管理器实现,它采用了泛型静态类和单例模式,提供了线程安全的缓存操作。希望能帮助到大家。


网站公告

今日签到

点亮在社区的每一天
去签到