1. 程式人生 > >【.NET Core專案實戰-統一認證平臺】第五章 閘道器篇-自定義快取Redis

【.NET Core專案實戰-統一認證平臺】第五章 閘道器篇-自定義快取Redis

原文: 【.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,過幾十秒後繼續訪問,結果如下。

可以看出來,快取已經生效,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裡,來解決叢集部署的問題,如果想清理快取資料,通過閘道器指定的授權介面即可完成,完全具備了閘道器的快取的相關模組的需求。

下一篇開始我們開始介紹針對不同客戶端設定不同的許可權來實現自定義認證,敬請期待,後面的課程會越來越精彩,也希望大家多多支援。