【.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,过几十秒后继续访问,结果如下。

可以看出来,缓存已经生效,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进行测试,测试结果如下。

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

五、总结及预告

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

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

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

  1. 【.NET Core项目实战-统一认证平台】第二章网关篇-定制Ocelot来满足需求

    [.NET Core项目实战-统一认证平台]开篇及目录索引 这篇文章,我们将从Ocelot的中间件源码分析,目前Ocelot已经实现那些功能,还有那些功能在我们实际项目中暂时还未实现,如果我们要使用这 ...

  2. 【.NET Core项目实战-统一认证平台】第九章 授权篇-使用Dapper持久化IdentityServer4

    [.NET Core项目实战-统一认证平台]开篇及目录索引 上篇文章介绍了IdentityServer4的源码分析的内容,让我们知道了IdentityServer4的一些运行原理,这篇将介绍如何使用d ...

  3. 【.NET Core项目实战-统一认证平台】第十章 授权篇-客户端授权

    [.NET Core项目实战-统一认证平台]开篇及目录索引 上篇文章介绍了如何使用Dapper持久化IdentityServer4(以下简称ids4)的信息,并实现了sqlserver和mysql两种 ...

  4. 【.NET Core项目实战-统一认证平台】第八章 授权篇-IdentityServer4源码分析

    [.NET Core项目实战-统一认证平台]开篇及目录索引 上篇文章我介绍了如何在网关上实现客户端自定义限流功能,基本完成了关于网关的一些自定义扩展需求,后面几篇将介绍基于IdentityServer ...

  5. 【.NET Core项目实战-统一认证平台】第一章 功能及架构分析

    [.NET Core项目实战-统一认证平台]开篇及目录索引 从本文开始,我们正式进入项目研发阶段,首先我们分析下统一认证平台应该具备哪些功能性需求和非功能性需求,在梳理完这些需求后,设计好系统采用的架 ...

  6. 【.NET Core项目实战-统一认证平台】第三章 网关篇-数据库存储配置(1)

    [.NET Core项目实战-统一认证平台]开篇及目录索引 本篇将介绍如何扩展Ocelot中间件实现自定义网关,并使用2种不同数据库来演示Ocelot配置信息存储和动态更新功能,内容也是从实际设计出发 ...

  7. 【.NET Core项目实战-统一认证平台】第十六章 网关篇-Ocelot集成RPC服务

    [.NET Core项目实战-统一认证平台]开篇及目录索引 一.什么是RPC RPC是"远程调用(Remote Procedure Call)"的一个名称的缩写,并不是任何规范化的 ...

  8. 【.NET Core项目实战-统一认证平台】第十五章 网关篇-使用二级缓存提升性能

    [.NET Core项目实战-统一认证平台]开篇及目录索引 一.背景 首先说声抱歉,可能是因为假期综合症(其实就是因为懒哈)的原因,已经很长时间没更新博客了,现在也调整的差不多了,准备还是以每周1-2 ...

  9. 【.NET Core项目实战-统一认证平台】第十四章 授权篇-自定义授权方式

    [.NET Core项目实战-统一认证平台]开篇及目录索引 上篇文章我介绍了如何强制令牌过期的实现,相信大家对IdentityServer4的验证流程有了更深的了解,本篇我将介绍如何使用自定义的授权方 ...

随机推荐

  1. 100-days: twenty-eight

    Title: Lawrence Ferlinghetti's(劳伦斯·费林盖蒂) enduring San Francisco(旧金山) 劳伦斯·费林盖蒂心中的旧金山,历久弥新 费林盖蒂:美国垮掉的一 ...

  2. 解决time_wait过多

    需要真正找出问题或瓶颈,但调整单机参数能临时解决:(收集自:https://www.cnblogs.com/dadonggg/p/8778318.html) 编辑内核文件/etc/sysctl.con ...

  3. 轮播插件swiper

    使用步骤 1.引用js <script src="swiper/swiper.min.js" type="text/javascript" charset ...

  4. R语言如何读取.csv文件

    以下是我关于如何在R语言中读取.csv文件及一些需要注意的细节的总结,希望能帮助到大家~

  5. TZOJ 2519 Regetni(N个点求三角形面积为整数总数)

    描述 Background Hello Earthling. We're from the planet Regetni and need your help to make lots of mone ...

  6. python 修改的函数装饰器

    把好的代码记录下来 方便以后学习 修改的函数参数装饰器 from functools import wraps import time import logging def warn(timeout) ...

  7. Vue 获取元素样式 元素高度

    看到这个问题我第一时间想的竟然是JS 不知道你是怎么想的 不过昨天有一个小哥哥 问我一个Vue的 哈哈哈 get了 我当时问他为什么不用JS获取 他说 这个性能更高 那我们来看看这个高性能的获取元素高 ...

  8. zxlizsm

    好久没发表了,毕业之后就没回来过!

  9. ClassLoader的工作机制

    本文中主要介绍类加载器的工作机制 一:首先什么是类加载器? 类加载器就是用来加载java类到java虚拟机中.java源程序经过编译之后形成字节码文件,类加载器将字节码文件加载到内存中,并转换成jav ...

  10. windbg排查大内存

    现在都是用windbg preview,安装比较麻烦了,还要配置环境变量, 并且每次分析前要先执行 !analyze - v !eeheap -gc !DumpHeap -min 500 000002 ...