【.NET Core項目實戰-統一認證平臺】第五章 網關篇-自定義緩存Redis

【.NET Core項目實戰-統一認證平臺】開篇及目錄索引

上篇文章咱們介紹了2種網關配置信息更新的方法和擴展Mysql存儲,本篇咱們將介紹如何使用Redis來實現網關的全部緩存功能,用到的文檔及源碼將會在GitHub上開源,每篇的源代碼我將用分支的方式管理,本篇使用的分支爲course3
附文檔及源碼下載地址:[https://github.com/jinyancao/CtrAuthPlatform/tree/course3]html

1、緩存介紹及選型

網關的一個重要的功能就是緩存,能夠對一些不常更新的數據進行緩存,減小後端服務開銷,默認Ocelot實現的緩存爲本地文件進行緩存,沒法達到生產環境大型應用的需求,並且不支持分佈式環境部署,因此咱們須要一個知足大型應用和分佈式環境部署的緩存方案。Redis應該是當前應用最普遍的緩存數據庫,支持5種存儲類型,知足不一樣應用的實現,且支持分佈式部署等特性,因此緩存咱們決定使用Redis做爲緩存實現。git

本文將介紹使用CSRedisCore來實現Redis相關操做,至於爲何選擇CSRedisCore,可參考文章[.NET Core開發者的福音之玩轉Redis的又一傻瓜式神器推薦],裏面詳細的介紹了各類Redis組件比較及高級應用,並列出了不一樣組件的壓力測試對比,另外也附CSRedisCore做者交流QQ羣:8578575,使用中有什麼問題能夠直接諮詢做者本人。github

2、緩存擴展實現

首先本地安裝Redis和管理工具Redis Desktop Manager,本文不介紹安裝過程,而後NuGet安裝 CSRedisCore,如今開始咱們重寫IOcelotCache<T>的實現,新建InRedisCache.cs文件。redis

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類中,修改注入方法。sql

/// <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}代碼,增長服務器時間輸出。c#

[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,過幾十秒後繼續訪問,結果以下。
api

能夠看出來,緩存已經生效,1分鐘內請求都不會路由到服務端,再查詢下redis緩存數據,發現緩存信息已經存在,而後使用Redis Desktop Manager查看Redis緩存信息是否存在,奈斯,已經存在,說明已經達到咱們預期目的。緩存

3、解決網關集羣配置信息變動問題

前面幾篇已經介紹了網關的數據庫存儲,並介紹了網關的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緩存了,能夠解決集羣部署後沒法同步更新問題。

4、如何清除緩存記錄

實際項目使用過程當中,可能會遇到須要當即清除緩存數據,那如何實現從網關清除緩存數據呢?在上篇中咱們介紹了接口更新網關配置的說明,緩存的更新也是使用接口的方式進行刪除,詳細代碼以下。

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進行測試,測試結果以下。

執行成功後能夠刪除指定的緩存記錄,且當即生效,完美的解決了咱們問題。

5、總結及預告

本篇咱們介紹了使用redis緩存來重寫網關的全部緩存模塊,並把網關配置信息也存儲到redis裏,來解決集羣部署的問題,若是想清理緩存數據,經過網關指定的受權接口便可完成,徹底具有了網關的緩存的相關模塊的需求。

下一篇開始咱們開始介紹針對不一樣客戶端設置不一樣的權限來實現自定義認證,敬請期待,後面的課程會愈來愈精彩,也但願你們多多支持。

相關文章
相關標籤/搜索