【.NET Core项目实战-统一认证平台】第五章 网关篇-自定义缓存Redis

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 原文:【.NET Core项目实战-统一认证平台】第五章 网关篇-自定义缓存Redis【.NET Core项目实战-统一认证平台】开篇及目录索引 上篇文章我们介绍了2种网关配置信息更新的方法和扩展Mysql存储,本篇我们将介绍如何使用Redis来实现网关的所有缓存功能,用到的文档及源码将会在GitHub上开源,每篇的源代码我将用分支的方式管理,本篇使用的分支为course3。
原文: 【.NET Core项目实战-统一认证平台】第五章 网关篇-自定义缓存Redis

【.NET Core项目实战-统一认证平台】开篇及目录索引

上篇文章我们介绍了2种网关配置信息更新的方法和扩展Mysql存储,本篇我们将介绍如何使用Redis来实现网关的所有缓存功能,用到的文档及源码将会在GitHub上开源,每篇的源代码我将用分支的方式管理,本篇使用的分支为course3
附文档及源码下载地址:[https://github.com/jinyancao/CtrAuthPlatform/tree/course3]

一、缓存介绍及选型

网关的一个重要的功能就是缓存,可以对一些不常更新的数据进行缓存,减少后端服务开销,默认Ocelot实现的缓存为本地文件进行缓存,无法达到生产环境大型应用的需求,而且不支持分布式环境部署,所以我们需要一个满足大型应用和分布式环境部署的缓存方案。Redis应该是当前应用最广泛的缓存数据库,支持5种存储类型,满足不同应用的实现,且支持分布式部署等特性,所以缓存我们决定使用Redis作为缓存实现。

本文将介绍使用CSRedisCore来实现Redis相关操作,至于为什么选择CSRedisCore,可参考文章[.NET Core开发者的福音之玩转Redis的又一傻瓜式神器推荐],里面详细的介绍了各种Redis组件比较及高级应用,并列出了不同组件的压力测试对比,另外也附CSRedisCore作者交流QQ群:8578575,使用中有什么问题可以直接咨询作者本人。

二、缓存扩展实现

首先本地安装Redis和管理工具Redis Desktop Manager,本文不介绍安装过程,然后NuGet安装 CSRedisCore,现在开始我们重写IOcelotCache<T>的实现,新建InRedisCache.cs文件。

using Ctr.AhphOcelot.Configuration;
using Ocelot.Cache;
using System;
using System.Collections.Generic;
using System.Text;

namespace Ctr.AhphOcelot.Cache
{
    /// <summary>
    /// 金焰的世界
    /// 2018-11-14
    /// 使用Redis重写缓存
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class InRedisCache<T> : IOcelotCache<T>
    {
        private readonly AhphOcelotConfiguration _options;
        public InRedisCache(AhphOcelotConfiguration options)
        {
            _options = options;
            CSRedis.CSRedisClient csredis;
            if (options.RedisConnectionStrings.Count == 1)
            {
                //普通模式
                csredis = new CSRedis.CSRedisClient(options.RedisConnectionStrings[0]);
            }
            else
            {
                //集群模式
                //实现思路:根据key.GetHashCode() % 节点总数量,确定连向的节点
                //也可以自定义规则(第一个参数设置)
                csredis = new CSRedis.CSRedisClient(null, options.RedisConnectionStrings.ToArray());
            }
            //初始化 RedisHelper
            RedisHelper.Initialization(csredis);
        }

        /// <summary>
        /// 添加缓存信息
        /// </summary>
        /// <param name="key">缓存的key</param>
        /// <param name="value">缓存的实体</param>
        /// <param name="ttl">过期时间</param>
        /// <param name="region">缓存所属分类,可以指定分类缓存过期</param>
        public void Add(string key, T value, TimeSpan ttl, string region)
        {
            key = GetKey(region, key);
            if (ttl.TotalMilliseconds <= 0)
            {
                return;
            }
            RedisHelper.Set(key, value.ToJson(), (int)ttl.TotalSeconds); 
        }

        
        public void AddAndDelete(string key, T value, TimeSpan ttl, string region)
        {
            Add(key, value, ttl, region);
        }

        /// <summary>
        /// 批量移除regin开头的所有缓存记录
        /// </summary>
        /// <param name="region">缓存分类</param>
        public void ClearRegion(string region)
        {
            //获取所有满足条件的key
            var data= RedisHelper.Keys(_options.RedisKeyPrefix + "-" + region + "-*");
            //批量删除
            RedisHelper.Del(data);
        }

        /// <summary>
        /// 获取执行的缓存信息
        /// </summary>
        /// <param name="key">缓存key</param>
        /// <param name="region">缓存分类</param>
        /// <returns></returns>
        public T Get(string key, string region)
        {
            key= GetKey(region, key);
            var result = RedisHelper.Get(key);
            if (!String.IsNullOrEmpty(result))
            {
                return result.ToObject<T>();
            }
            return default(T);
        }

        /// <summary>
        /// 获取格式化后的key
        /// </summary>
        /// <param name="region">分类标识</param>
        /// <param name="key">key</param>
        /// <returns></returns>
        private string GetKey(string region,string key)
        {
            return _options.RedisKeyPrefix + "-" + region + "-" + key;
        }
    }
}

实现所有缓存相关接口,是不是很优雅呢?实现好缓存后,我们需要把我们现实的注入到网关里,在ServiceCollectionExtensions类中,修改注入方法。

/// <summary>
/// 添加默认的注入方式,所有需要传入的参数都是用默认值
/// </summary>
/// <param name="builder"></param>
/// <returns></returns>
public static IOcelotBuilder AddAhphOcelot(this IOcelotBuilder builder, Action<AhphOcelotConfiguration> option)
{
    builder.Services.Configure(option);
    //配置信息
    builder.Services.AddSingleton(
        resolver => resolver.GetRequiredService<IOptions<AhphOcelotConfiguration>>().Value);
    //配置文件仓储注入
    builder.Services.AddSingleton<IFileConfigurationRepository, SqlServerFileConfigurationRepository>();
    //注册后端服务
    builder.Services.AddHostedService<DbConfigurationPoller>();
    //使用Redis重写缓存
    builder.Services.AddSingleton<IOcelotCache<FileConfiguration>, InRedisCache<FileConfiguration>>();
            builder.Services.AddSingleton<IOcelotCache<CachedResponse>, InRedisCache<CachedResponse>>();
    return builder;
}

奈斯,我们使用Redis实现缓存已经全部完成,现在开始我们在网关配置信息增加缓存来测试下,看缓存是否生效,并查看是否存储在Redis里。

为了验证缓存是否生效,修改测试服务api/values/{id}代码,增加服务器时间输出。

[HttpGet("{id}")]
public ActionResult<string> Get(int id)
{
    return id+"-"+DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
}

增加新的测试路由脚本,然后增加缓存策略,缓存60秒,缓存分类test_ahphocelot

--插入路由测试信息 
insert into AhphReRoute values(1,'/ctr/values/{id}','[ "GET" ]','','http','/api/Values/{id}','[{"Host": "localhost","Port": 9000 }]',
'','','{ "TtlSeconds": 60, "Region": "test_ahphocelot" }','','','','',0,1);
--插入网关关联表
insert into dbo.AhphConfigReRoutes values(1,2);

现在我们测试访问网关地址http://localhost:7777/api/values/1,过几十秒后继续访问,结果如下。
img_6311416924c1e2b8a8d9a3aaeb708e92.png

可以看出来,缓存已经生效,1分钟内请求都不会路由到服务端,再查询下redis缓存数据,发现缓存信息已经存在,然后使用Redis Desktop Manager查看Redis缓存信息是否存在,奈斯,已经存在,说明已经达到我们预期目的。

三、解决网关集群配置信息变更问题

前面几篇已经介绍了网关的数据库存储,并介绍了网关的2种更新方式,但是如果网关集群部署时,采用接口更新方式,无法直接更新所有集群端配置数据,那如何实现集群配置信息一致呢?前面介绍了redis缓存,可以解决当前遇到的问题,我们需要重写内部配置文件提取仓储类,使用redis存储。

我们首先使用redis实现IInternalConfigurationRepository接口,每次请求配置信息时直接从redis存储,避免单机缓存出现数据无法更新的情况。RedisInternalConfigurationRepository代码如下。

using Ctr.AhphOcelot.Configuration;
using Ocelot.Configuration;
using Ocelot.Configuration.Repository;
using Ocelot.Responses;
using System;
using System.Collections.Generic;
using System.Text;

namespace Ctr.AhphOcelot.Cache
{
    /// <summary>
    /// 金焰的世界
    /// 2018-11-14
    /// 使用redis存储内部配置信息
    /// </summary>
    public class RedisInternalConfigurationRepository : IInternalConfigurationRepository
    {
        private readonly AhphOcelotConfiguration _options;
        private IInternalConfiguration _internalConfiguration;
        public RedisInternalConfigurationRepository(AhphOcelotConfiguration options)
        {
            _options = options;
            CSRedis.CSRedisClient csredis;
            if (options.RedisConnectionStrings.Count == 1)
            {
                //普通模式
                csredis = new CSRedis.CSRedisClient(options.RedisConnectionStrings[0]);
            }
            else
            {
                //集群模式
                //实现思路:根据key.GetHashCode() % 节点总数量,确定连向的节点
                //也可以自定义规则(第一个参数设置)
                csredis = new CSRedis.CSRedisClient(null, options.RedisConnectionStrings.ToArray());
            }
            //初始化 RedisHelper
            RedisHelper.Initialization(csredis);
        }

        /// <summary>
        /// 设置配置信息
        /// </summary>
        /// <param name="internalConfiguration">配置信息</param>
        /// <returns></returns>
        public Response AddOrReplace(IInternalConfiguration internalConfiguration)
        {
            var key = _options.RedisKeyPrefix + "-internalConfiguration";
            RedisHelper.Set(key, internalConfiguration.ToJson());
            return new OkResponse();
        }

        /// <summary>
        /// 从缓存中获取配置信息
        /// </summary>
        /// <returns></returns>
        public Response<IInternalConfiguration> Get()
        {
            var key = _options.RedisKeyPrefix + "-internalConfiguration";
            var result = RedisHelper.Get<InternalConfiguration>(key);
            if (result!=null)
            {
                return new OkResponse<IInternalConfiguration>(result);
            }
            return new OkResponse<IInternalConfiguration>(default(InternalConfiguration));
        }
    }
}

redis实现后,然后在ServiceCollectionExtensions里增加接口实现注入。

builder.Services.AddSingleton<IInternalConfigurationRepository, RedisInternalConfigurationRepository>();

然后启动网关测试,可以发现网关配置信息已经使用redis缓存了,可以解决集群部署后无法同步更新问题。

四、如何清除缓存记录

实际项目使用过程中,可能会遇到需要立即清除缓存数据,那如何实现从网关清除缓存数据呢?在上篇中我们介绍了接口更新网关配置的说明,缓存的更新也是使用接口的方式进行删除,详细代码如下。

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace Ocelot.Cache
{
    [Authorize]
    [Route("outputcache")]
    public class OutputCacheController : Controller
    {
        private readonly IOcelotCache<CachedResponse> _cache;

        public OutputCacheController(IOcelotCache<CachedResponse> cache)
        {
            _cache = cache;
        }

        [HttpDelete]
        [Route("{region}")]
        public IActionResult Delete(string region)
        {
            _cache.ClearRegion(region);
            return new NoContentResult();
        }
    }
}

我们可以先拉去授权,获取授权方式请参考上一篇,然后使用HTTP DELETE方式,请求删除地址,比如删除前面的测试缓存接口,可以请求http://localhost:7777/CtrOcelot/outputcache/test_ahphocelot地址进行删除,可以使用PostMan进行测试,测试结果如下。
img_5affee917c6a16607deadbbdf2e30776.png

执行成功后可以删除指定的缓存记录,且立即生效,完美的解决了我们问题。

五、总结及预告

本篇我们介绍了使用redis缓存来重写网关的所有缓存模块,并把网关配置信息也存储到redis里,来解决集群部署的问题,如果想清理缓存数据,通过网关指定的授权接口即可完成,完全具备了网关的缓存的相关模块的需求。

下一篇开始我们开始介绍针对不同客户端设置不同的权限来实现自定义认证,敬请期待,后面的课程会越来越精彩,也希望大家多多支持。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
2月前
|
开发框架 .NET 开发者
简化 ASP.NET Core 依赖注入(DI)注册-Scrutor
Scrutor 是一个简化 ASP.NET Core 应用程序中依赖注入(DI)注册过程的开源库,支持自动扫描和注册服务。通过简单的配置,开发者可以轻松地从指定程序集中筛选、注册服务,并设置其生命周期,同时支持服务装饰等高级功能。适用于大型项目,提高代码的可维护性和简洁性。仓库地址:&lt;https://github.com/khellang/Scrutor&gt;
60 5
|
4月前
|
存储 开发框架 JSON
ASP.NET Core OData 9 正式发布
【10月更文挑战第8天】Microsoft 在 2024 年 8 月 30 日宣布推出 ASP.NET Core OData 9,此版本与 .NET 8 的 OData 库保持一致,改进了数据编码以符合 OData 规范,并放弃了对旧版 .NET Framework 的支持,仅支持 .NET 8 及更高版本。新版本引入了更快的 JSON 编写器 `System.Text.UTF8JsonWriter`,优化了内存使用和序列化速度。
116 0
|
2月前
|
JSON 安全 API
.net 自定义日志类
在.NET中,创建自定义日志类有助于更好地管理日志信息。示例展示了如何创建、配置和使用日志记录功能,包括写入日志文件、设置日志级别、格式化消息等。注意事项涵盖时间戳、日志级别、JSON序列化、线程安全、日志格式、文件处理及示例使用。请根据需求调整代码。
51 13
|
2月前
|
开发框架 算法 中间件
ASP.NET Core 中的速率限制中间件
在ASP.NET Core中,速率限制中间件用于控制客户端请求速率,防止服务器过载并提高安全性。通过`AddRateLimiter`注册服务,并配置不同策略如固定窗口、滑动窗口、令牌桶和并发限制。这些策略可在全局、控制器或动作级别应用,支持自定义响应处理。使用中间件`UseRateLimiter`启用限流功能,并可通过属性禁用特定控制器或动作的限流。这有助于有效保护API免受滥用和过载。 欢迎关注我的公众号:Net分享 (239字符)
61 1
|
3月前
|
开发框架 .NET C#
在 ASP.NET Core 中创建 gRPC 客户端和服务器
本文介绍了如何使用 gRPC 框架搭建一个简单的“Hello World”示例。首先创建了一个名为 GrpcDemo 的解决方案,其中包含一个 gRPC 服务端项目 GrpcServer 和一个客户端项目 GrpcClient。服务端通过定义 `greeter.proto` 文件中的服务和消息类型,实现了一个简单的问候服务 `GreeterService`。客户端则通过 gRPC 客户端库连接到服务端并调用其 `SayHello` 方法,展示了 gRPC 在 C# 中的基本使用方法。
64 5
在 ASP.NET Core 中创建 gRPC 客户端和服务器
|
2月前
|
开发框架 缓存 .NET
GraphQL 与 ASP.NET Core 集成:从入门到精通
本文详细介绍了如何在ASP.NET Core中集成GraphQL,包括安装必要的NuGet包、创建GraphQL Schema、配置GraphQL服务等步骤。同时,文章还探讨了常见问题及其解决方法,如处理复杂查询、错误处理、性能优化和实现认证授权等,旨在帮助开发者构建灵活且高效的API。
57 3
|
4月前
|
存储 消息中间件 NoSQL
Redis 入门 - C#.NET Core客户端库六种选择
Redis 入门 - C#.NET Core客户端库六种选择
108 8
|
5月前
|
开发框架 监控 前端开发
在 ASP.NET Core Web API 中使用操作筛选器统一处理通用操作
【9月更文挑战第27天】操作筛选器是ASP.NET Core MVC和Web API中的一种过滤器,可在操作方法执行前后运行代码,适用于日志记录、性能监控和验证等场景。通过实现`IActionFilter`接口的`OnActionExecuting`和`OnActionExecuted`方法,可以统一处理日志、验证及异常。创建并注册自定义筛选器类,能提升代码的可维护性和复用性。
|
4月前
|
Windows
.NET 隐藏/自定义windows系统光标
【10月更文挑战第20天】在.NET中,可以使用`Cursor`类来控制光标。要隐藏光标,可将光标设置为`Cursors.None`。此外,还可以通过从文件或资源加载自定义光标来更改光标的样式。例如,在表单加载时设置`this.Cursor = Cursors.None`隐藏光标,或使用`Cursor.FromFile`方法加载自定义光标文件,也可以将光标文件添加到项目资源中并通过资源管理器加载。这些方法适用于整个表单或特定控件。
|
4月前
|
开发框架 JavaScript 前端开发
一个适用于 ASP.NET Core 的轻量级插件框架
一个适用于 ASP.NET Core 的轻量级插件框架