【.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里,來解決集群部署的問題,如果想清理緩存數據,通過網關指定的授權接口即可完成,完全具備了網關的緩存的相關模塊的需求。

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM