前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >.NET WebAPI 采用 IDistributedCache 实现分布式缓存过滤器 Redis 模式

.NET WebAPI 采用 IDistributedCache 实现分布式缓存过滤器 Redis 模式

作者头像
乌拉栋
发布2022-10-28 09:15:22
8280
发布2022-10-28 09:15:22
举报
文章被收录于专栏:编程宝典

分布式缓存是由多个应用服务器共享的缓存,通常作为访问它的应用服务器的外部服务进行维护。 分布式缓存可以提高 ASP.NET Core 应用的性能和可伸缩性,尤其是当应用由云服务或服务器场托管时。

与其他将缓存数据存储在单个应用服务器上的缓存方案相比,分布式缓存具有多个优势。

当分发缓存数据时,数据:

  • 在多个服务器的请求之间保持一致(一致性)。
  • 在进行服务器重启和应用部署后仍然有效。
  • 不使用本地内存。

实现方案采用 Redis 作为缓存的数据托管方案,接口使用微软官方的 IDistributedCache 接口实现。

首选安装 Microsoft.Extensions.Caching.StackExchangeRedis 组件包

然后注入 分布式缓存服务

代码语言:javascript
复制
//注册缓存服务 Redis模式
builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = builder.Configuration.GetConnectionString("redisConnection");
    options.InstanceName = "cache";
});

IDistributedCache 的扩展类,后面过滤器操作缓存需要用到这个扩展方法。

代码语言:javascript
复制
using Microsoft.Extensions.Caching.Distributed;

namespace Common
{

    /// <summary>
    /// 扩展分布式缓存接口,集成常用方法
    /// </summary>
    public static class IDistributedCacheExtension
    {

        /// <summary>
        /// 删除指定key
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public static bool Remove(this IDistributedCache distributedCache, string key)
        {
            try
            {
                distributedCache.Remove(key);
                return true;
            }
            catch
            {
                return false;
            }
        }


        /// <summary>
        /// 设置object类型的key
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public static bool SetObject(this IDistributedCache distributedCache, string key, object value)
        {
            try
            {
                var valueStr = JsonHelper.ObjectToJson(value);
                distributedCache.SetString(key, valueStr);
                return true;
            }
            catch
            {
                return false;
            }
        }


        /// <summary>
        /// 设置string类型key,包含有效时间
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="timeOut"></param>
        /// <returns></returns>
        public static bool SetString(this IDistributedCache distributedCache, string key, string value, TimeSpan timeOut)
        {
            try
            {
                distributedCache.SetString(key, value, new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = timeOut });
                return true;
            }
            catch
            {
                return false;
            }
        }


        /// <summary>
        /// 设置object类型key,包含有效时间
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="timeOut"></param>
        /// <returns></returns>
        public static bool SetObject(this IDistributedCache distributedCache, string key, object value, TimeSpan timeOut)
        {
            try
            {
                var valueStr = JsonHelper.ObjectToJson(value);
                distributedCache.SetString(key, valueStr, new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = timeOut });
                return true;
            }
            catch
            {
                return false;
            }
        }


        /// <summary>
        /// 读取 Object 类型的key
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <returns></returns>
        public static T GetObject<T>(this IDistributedCache distributedCache, string key)
        {
            try
            {
                var valueStr = distributedCache.GetString(key);
                var value = JsonHelper.JsonToObject<T>(valueStr);
                return value;
            }
            catch
            {
                return default!;
            }
        }


        /// <summary>
        /// 判断是否存在指定key
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public static bool IsContainKey(this IDistributedCache distributedCache, string key)
        {
            if (string.IsNullOrEmpty(distributedCache.GetString(key)))
            {
                return false;
            }
            else
            {
                return true;
            }
        }

    }
}

上面的扩展方法里面用到了我自己的 JsonHelper 所以这里也贴一下 JsonHelper 的代码

代码语言:javascript
复制
using Common.JsonConverter;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Nodes;

namespace Common
{
    public class JsonHelper
    {

        /// <summary>
        /// 通过 Key 获取 Value
        /// </summary>
        /// <returns></returns>
        public static string? GetValueByKey(string json, string key)
        {
            using JsonDocument doc = JsonDocument.Parse(json);
            var jsonElement = doc.RootElement.Clone();

            return jsonElement.GetProperty(key).GetString();
        }


        /// <summary> 
        /// 对象 转 Json
        /// </summary> 
        /// <param name="obj">对象</param> 
        /// <returns>JSON格式的字符串</returns> 
        public static string ObjectToJson(object obj)
        {
            JsonSerializerOptions options = new();

            //关闭默认转义
            options.Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping;

            //启用驼峰格式
            options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;

            //关闭缩进设置
            options.WriteIndented = false;

            return JsonSerializer.Serialize(obj, options);
        }


        /// <summary> 
        /// Json 转 对象
        /// </summary> 
        /// <typeparam name="T">类型</typeparam> 
        /// <param name="jsonText">JSON文本</param> 
        /// <returns>指定类型的对象</returns> 
        public static T JsonToObject<T>(string json)
        {
            JsonSerializerOptions options = new();//启用大小写不敏感
            options.PropertyNameCaseInsensitive = true;

            return JsonSerializer.Deserialize<T>(json, options)!;
        }


        /// <summary>
        /// 没有 Key 的 Json 转 List<JToken>
        /// </summary>
        /// <param name="strJson"></param>
        /// <returns></returns>
        public static JsonNode? JsonToArrayList(string json)
        {
            var jsonNode = JsonNode.Parse(json);

            return jsonNode;
        }

    }
}

WebAPI 的缓存过滤器代码如下:

代码语言:javascript
复制
using Common;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Caching.Distributed;
using System.Text.Json;

namespace WebAPI.Filters
{

    /// <summary>
    /// 缓存过滤器
    /// </summary>
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
    public class CacheDataFilter : Attribute, IActionFilter
    {

        /// <summary>
        /// 缓存时效有效期,单位 秒
        /// </summary>
        public int TTL { get; set; }


        /// <summary>
        /// 是否使用 Token
        /// </summary>
        public bool IsUseToken { get; set; }


        void IActionFilter.OnActionExecuting(ActionExecutingContext context)
        {
            string key = "";

            if (IsUseToken)
            {
                var token = context.HttpContext.Request.Headers.Where(t => t.Key == "Authorization").Select(t => t.Value).FirstOrDefault();

                key = context.ActionDescriptor.DisplayName + "_" + context.HttpContext.Request.QueryString + "_" + token;
            }
            else
            {
                key = context.ActionDescriptor.DisplayName + "_" + context.HttpContext.Request.QueryString;
            }

            key = "CacheData_" + CryptoHelper.GetMD5(key);

            try
            {
                var distributedCache = context.HttpContext.RequestServices.GetRequiredService<IDistributedCache>();
                var cacheInfo = distributedCache.GetObject<object>(key);

                if (cacheInfo != null)
                {
                    if (((JsonElement)cacheInfo).ValueKind == JsonValueKind.String)
                    {
                        context.Result = new ObjectResult(cacheInfo.ToString());
                    }
                    else
                    {
                        context.Result = new ObjectResult(cacheInfo);
                    }
                }
            }
            catch (Exception ex)
            {
                var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<CacheDataFilter>>();
                logger.LogError(ex, "缓存模块异常-In");
            }
        }


        void IActionFilter.OnActionExecuted(ActionExecutedContext context)
        {
            try
            {
                if (context.Result is ObjectResult objectResult && objectResult.Value != null)
                {
                    string key = "";

                    if (IsUseToken)
                    {
                        var token = context.HttpContext.Request.Headers.Where(t => t.Key == "Authorization").Select(t => t.Value).FirstOrDefault();

                        key = context.ActionDescriptor.DisplayName + "_" + context.HttpContext.Request.QueryString + "_" + token;
                    }
                    else
                    {
                        key = context.ActionDescriptor.DisplayName + "_" + context.HttpContext.Request.QueryString;
                    }

                    key = "CacheData_" + CryptoHelper.GetMD5(key);

                    if (objectResult.Value != null)
                    {
                        var distributedCache = context.HttpContext.RequestServices.GetRequiredService<IDistributedCache>();
                        distributedCache.SetObject(key, objectResult.Value, TimeSpan.FromSeconds(TTL));
                    }

                }
            }
            catch (Exception ex)
            {
                var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<CacheDataFilter>>();
                logger.LogError(ex, "缓存模块异常-Out");
            }

        }
    }
}

缓存过滤器的入参只有两个

  • TTL 缓存有效期以秒为单位
  • IsUseToken 是否使用 Token 区分不同的用户身份,之所以加入这个参数,主要是因为有些接口虽然多个用户请求时的入参一样,但是不同的用户需要返回不同的信息,所以面对这种类型的接口需要将 IsUseToken 设定为 True。

过滤器的使用方法就很简单了,直接附在对应的接口 Action 方法上就可以,如下:

代码语言:javascript
复制
[CacheDataFilter(TTL = 60, IsUseToken = true)]
public DtoUser? GetUserInfo()
{
    ///省略业务逻辑
}

此处对于 GetUserInfo  接口添加了缓存过滤器,对数据缓存60秒钟,并且针对 不同的Token身份进行了区分,因为这边的逻辑是通过 Token 识别用户身份的,虽然请求没有特别的参数,但是需要为不同用户的请求返回对应的用户信息,并且分别缓存。

至此 .NET WebAPI 采用 IDistributedCache 实现分布式缓存过滤器 Redis 模式 就讲解完了,有任何不明白的,可以在文章下面评论或者私信我,欢迎大家积极的讨论交流

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-08-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云数据库 Redis®
腾讯云数据库 Redis®(TencentDB for Redis®)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档