首页 > 数据库 >从Redis读取.NET Core配置

从Redis读取.NET Core配置

时间:2023-12-25 12:33:05浏览次数:40  
标签:Core 配置 Redis private key new NET

在本文中,我们将创建一个自定义的.NET Core应用配置源和提供程序,用于从Redis中读取配置。在此之前,您需要稍微了解一些.NET Core配置提供程序的工作原理,相关的内容可以在Microsoft开发者官网搜索到。另外您可能还需要了解一些Redis的基础知识,比如Redis的基础数据类型,持久化等等。

一、配置的数据格式

.NET Core应用支持多种配置源(例如json、xml、ini文件,环境变量,内存字典,自定义源等),并且支持同时添加多个配置源,这也是本文的前提条件。应用程序会按照加入的先后顺序替换或补充配置。默认情况下,.NET Core应用的配置是存储在appsettings.json文件中的。在早期的.NET Core应用中,Program.cs的CreateHost方法里面还能看到AddJsonFile("appsettings.json").AddJsonFile($"appsetting.{env.Environment}.json")这样的代码,但是.NET 5以后,这段代码默认被隐藏了。

看过源码的朋友应该知道,.NET Core应用读取配置后,会将数据转换为一个Key和Value都是string的字典。Key的格式为Node1:Node2:abc。例如:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "ConnectionStrings": {
    "DefaultConnection": "Server=myserver;Database=mydb;User=myuser;Password=mypassword;"
  },
  "AppSettings": {
    "ApiBaseUrl": "https://api.example.com",
    "ApiKey": "your-api-key"
  },
  "AllowedHost":["foo1.com","foo2.com"]
}

转换后的数据为:

Logging:LogLevel:Default=Information
Logging:LogLevel:Microsoft=Warning
Logging:LogLevel:Microsoft.Hosting.Lifetime=Information
ConnectionStrings:DefaultConnection=Server=myserver;Database=mydb;User=myuser;Password=mypassword;
AppSettings:ApiBaseUrl=https://api.example.com
AppSettings:ApiKey=your-api-key
AllowedHost:0=foo1.com
AllowedHost:1=foo2.com

二、Redis的Hash类型

通过上面介绍,Redis的Hash数据结构刚好完美的切合了这一特点。先简单的介绍一下:

在Redis中,Hash是一种数据结构,用于存储键值对的集合,其中每个键都映射到一个值。Redis的Hash是一个键值对的无序集合,其中的每个键都是唯一的。Hash是一个类似于字典或关联数组的概念,在其他编程语言中也称为Map或Dictionary。

三、代码实现

创建好项目之后,我们需要安装一个NuGet包,就是大家熟知的StackExchange.Redis,到目前为止应该是.NET应用程序使用最多的Redis客户端。

PM> Install-Package StackExchange.Redis -v 2.7.10

您也可以通过Visual Studio、Rider自带的NuGet客户端安装,或者是直接在csproj文件中加入<PackageReference Include="StackExchange.Redis" Version="2.7.10" />

RedisConfigurationProvider.cs

public sealed class RedisConfigurationProvider : ConfigurationProvider, IAsyncDisposable
{
	private readonly ConnectionMultiplexer _connection;
    
    private readonly IDatabase _database;
    
    private readonly string _key;

	public RedisConfigurationProvider(RedisConfigurationSource source)
	{
        _key = source.Key;
		_connection = ConnectionMultiplexer.Connect(source.ConnectionString);
        _database = _connection.GetDatabase(source.Database);
	}

	/// <inheritdoc />
	public override void Load()
	{
		Data = _connection.HashGetAll(_key).ToDictionary(x => x.Name.ToString(), x => ReadRedisValue(x.Value);
	}

    private static string ReadRedisValue(RedisValue value)
	{
		if (value.IsNull)
		{
			return null;
		}

		return value.IsNullOrEmpty ? string.Empty : value.ToString();
	}
    
	/// <inheritdoc />
	public async ValueTask DisposeAsync()
	{
		await _connection.CloseAsync();
		await _connection.DisposeAsync();
	}
}

RedisConfigurationSource.cs

public sealed class RedisConfigurationSource : IConfigurationSource
{
	/// <summary>
	/// The Redis connection string.
	/// </summary>
	[DisallowNull]
	public string ConnectionString { get; set; }

	/// <summary>
	/// Gets or sets the Redis database ID.
	/// </summary>
	public int Database { get; set; } = -1;

	/// <summary>
	/// Gets or sets the Redis key this source will read from.
	/// </summary>
	/// <remarks>
	/// The key is expected to be a hash.
	/// </remarks>
	public string Key { get; set; } = "appsettings";
    
	/// <inheritdoc />
	public IConfigurationProvider Build(IConfigurationBuilder builder)
	{
		return new RedisConfigurationProvider(this);
	}
}

关键代码就这些,看上去似乎很简单……事实上确实很简单。

添加配置源

添加配置源的方法也很简单

// Program.cs

var builder = WebApplication.CreateBuilder(args);

builder.Configuration.Add(new RedisConfigurationSource
{
	ConnectionString = "localhost:6379",
	Key = "appsettings.dev"
});

RedisConfigurationSource里面总共只有三个属性,ConnectionString用于配置Redis连接字符串,Database用于指定从哪个数据库读取数据,也可以在连接字符串里面指定。Key用于指定要读取的键名称。

通过编写一些简单的代码,我们实现了一个能满足基本需求的分布式.NET Core配置提供程序。

Starfish.Redis

不想动手的朋友可以直接用我已经制作好的包

https://www.nuget.org/packages/Starfish.Redis

安装

Visual Studio包管理器搜索Starfish.Redis,或者执行dotnet add package Starfish.Redis

配置

// Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddRedis("127.0.0.1:6379,defaultDatabase=0,connectTimeout=5000,connectRetry=3", "appsettings");

启用Redis Keyspace Notifications

Starfish.Redis有两种机制用于实现ReloadOnChanged(配置修改后重新加载数据),一种是定时查询指定的Key,时效性稍微差一些。另一种是利用Redis的Keyspace Event和Pub/Sub模式来实现,当订阅的Key发生变化(删除、修改、过期等)时会主动发送通知给订阅者,使用这种模式需要配置Redis服务的notify-keyspace-events。

关于notify-keyspace-events配置,可参考下面的描述:

  • K:Keyspace事件,将会以__keyspace@__作为事件的前缀
  • E:Keyevent事件,将会以__keyevent@__作为事件的前缀
  • g:非特定类型的通用命令,例如DEL、EXPIRE、RENAME等
  • $:字符串命令,例如SET、INCR等
  • l:列表命令,例如LPUSH、LPOP等
  • s:集合命令,例如SADD、SREM等
  • h:哈希表命令,例如HSET、HINCRBY等
  • z:有序集合命令,例如ZSET、ZREM等
  • t:流命令,例如XADD、XDEL等
  • x:过期事件(在每个发生键过期的时侯产生)
  • e:淘汰事件(在每个发生键被淘汰的时候产生)
  • m:未命中事件(在访问某个不存在的键使产生)
  • A:配置g$lshztxe的别名,但不包括未命中事件m

简单起见,我们直接配置为AKE(启用所有事件的通知)。

方法一:redis-cli

redis-cli config set notify-keyspace-events AKE

方法二:docker参数

docker run -d --name redisname -p 6379:6379 redis --notify-keyspace-events AKE

方法三:配置文件

找到并打开打开redis.conf,在末尾加上

notify-keyspace-events AKE

注意事项

  1. Redis本身自带持久化策略,但是有的企业/团队没有开启或者是特意关闭了持久化,因此需要谨慎使用此方案。
  2. 强烈建议将存储配置数据的key设置为永不过期(TTL设置为-1),避免key过期带来一些不必要的麻烦。

导入appsettings.json到Redis

微软.NET库提供了一个内部类JsonConfigurationFileParser用于将json格式的配置转换为Dictionary<string, string>

namespace Microsoft.Extensions.Configuration.Json
{
    internal sealed class JsonConfigurationFileParser
    {
        private JsonConfigurationFileParser() { }

        private readonly Dictionary<string, string?> _data = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
        private readonly Stack<string> _paths = new Stack<string>();

        public static IDictionary<string, string?> Parse(Stream input)
            => new JsonConfigurationFileParser().ParseStream(input);

        private Dictionary<string, string?> ParseStream(Stream input)
        {
            var jsonDocumentOptions = new JsonDocumentOptions
            {
                CommentHandling = JsonCommentHandling.Skip,
                AllowTrailingCommas = true,
            };

            using (var reader = new StreamReader(input))
            using (JsonDocument doc = JsonDocument.Parse(reader.ReadToEnd(), jsonDocumentOptions))
            {
                if (doc.RootElement.ValueKind != JsonValueKind.Object)
                {
                    throw new FormatException(SR.Format(SR.Error_InvalidTopLevelJSONElement, doc.RootElement.ValueKind));
                }
                VisitObjectElement(doc.RootElement);
            }

            return _data;
        }

        private void VisitObjectElement(JsonElement element)
        {
            var isEmpty = true;

            foreach (JsonProperty property in element.EnumerateObject())
            {
                isEmpty = false;
                EnterContext(property.Name);
                VisitValue(property.Value);
                ExitContext();
            }

            SetNullIfElementIsEmpty(isEmpty);
        }

        private void VisitArrayElement(JsonElement element)
        {
            int index = 0;

            foreach (JsonElement arrayElement in element.EnumerateArray())
            {
                EnterContext(index.ToString());
                VisitValue(arrayElement);
                ExitContext();
                index++;
            }

            SetNullIfElementIsEmpty(isEmpty: index == 0);
        }

        private void SetNullIfElementIsEmpty(bool isEmpty)
        {
            if (isEmpty && _paths.Count > 0)
            {
                _data[_paths.Peek()] = null;
            }
        }

        private void VisitValue(JsonElement value)
        {
            Debug.Assert(_paths.Count > 0);

            switch (value.ValueKind)
            {
                case JsonValueKind.Object:
                    VisitObjectElement(value);
                    break;

                case JsonValueKind.Array:
                    VisitArrayElement(value);
                    break;

                case JsonValueKind.Number:
                case JsonValueKind.String:
                case JsonValueKind.True:
                case JsonValueKind.False:
                case JsonValueKind.Null:
                    string key = _paths.Peek();
                    if (_data.ContainsKey(key))
                    {
                        throw new FormatException(SR.Format(SR.Error_KeyIsDuplicated, key));
                    }
                    _data[key] = value.ToString();
                    break;

                default:
                    throw new FormatException(SR.Format(SR.Error_UnsupportedJSONToken, value.ValueKind));
            }
        }

        private void EnterContext(string context) =>
            _paths.Push(_paths.Count > 0 ?
                _paths.Peek() + ConfigurationPath.KeyDelimiter + context :
                context);

        private void ExitContext() => _paths.Pop();
    }
}

点关注,不迷路。

如果您喜欢这篇文章,请不要忘记点赞、关注、转发,谢谢!如果您有任何高见,欢迎在评论区留言讨论……

公众号

标签:Core,配置,Redis,private,key,new,NET
From: https://www.cnblogs.com/zhaorong/p/aspnet-configuration-redis.html

相关文章

  • netstat命令
    显示网络状态语法格式:netstat参数常用参数-a显示所有连接中的接口信息-n直接使用ip地址,而不是域名-A设置网络连接类型-N显示网络硬件外围设备的符号链接名称-c持续显示网络状态-o显示计时器数据信息-C显示路由配置信息-p显示正在使用接口的程序识别......
  • 记一次 .NET某工控 宇宙射线 导致程序崩溃分析
    一:背景1.讲故事为什么要提宇宙射线,太阳耀斑导致的程序崩溃呢?主要是昨天在知乎上看了这篇文章:莫非我遇到了传说中的bug?,由于rip中的0x41变成了0x61出现了bit位翻转导致程序崩溃,截图如下:下面的评论大多是说由于宇宙射线,这个太玄乎了,说实话看到这个传说bug的提法,我还......
  • 强大的VS插件CodeRush全新发布v23.2——支持并发.NET类型
    CodeRush是一个强大的VisualStudio.NET插件,它利用整合技术,通过促进开发者和团队效率来提升开发者体验。CodeRush能帮助你以极高的效率创建和维护源代码。Consume-first申明,强大的模板,智能的选择工具,智能代码分析和创新的导航以及一个无与伦比的重构集,在它们的帮助下能够大大的......
  • 2024年 Kubernetes 四大趋势预测
    Kubernetes在生产环境中的复杂性已经成为常态,在2023年这个平台工程盛行的时代,容器管理的最大亮点可能在于其灵活性,然而在运维政策和治理等方面仍然存在诸多挑战。Kubernetes最大的吸引力之一在于其可扩展性和跨环境的广泛用例。但是,强大的灵活性也带来了复杂性。Kubernetes用户......
  • 【C#】.net core 6.0 通过依赖注入注册和使用上下文服务
    给自己一个目标,然后坚持一段时间,总会有收获和感悟!请求上下文是指在Web应用程序中处理请求时,包含有关当前请求的各种信息的对象。这些信息包括请求的头部、身体、查询字符串、路由数据、用户身份验证信息以及其他与请求相关的数据。目录一、DbContext1.1、创建自定义类1.2、注册......
  • 2024年 Kubernetes 四大趋势预测
    Kubernetes在生产环境中的复杂性已经成为常态,在2023年这个平台工程盛行的时代,容器管理的最大亮点可能在于其灵活性,然而在运维政策和治理等方面仍然存在诸多挑战。Kubernetes最大的吸引力之一在于其可扩展性和跨环境的广泛用例。但是,强大的灵活性也带来了复杂性。Kubernetes用户......
  • 小技巧-- 断网恢复(未识别网络-无internet访问权限)
    配置host(解决延迟高问题) 安装java,配置环境变量,直接系统path中bin地址就行,不行就往上面移动 win开机启动Win+R打开运行,输入:shell:startupcalc  打开计算器powercfg/batteryreport 查看电池健康程度 netstat-anotasklist 列出进程 |findstr查询端口......
  • 各版本操作系统对.NET支持情况(2023-11-24更新)
     各版本操作系统对.NET支持情况(1124更新)(newlifex.com) 各版本操作系统对.NET支持情况(1124更新)借助虚拟机和测试机,检测各版本操作系统对.NET的支持情况。安装操作系统后,实测安装相应运行时并能够运行星尘代理为通过。 测试平台:VMwareWorkstation镜像来源:MSDNI......
  • .net core中如何自定义静态文件目录、默认主页、和文件浏览目录?
    在.NETCore中,UseStaticFiles、UseDefaultFiles、UseDirectoryBrowser和UseFileServer中间件用于处理静态文件和目录浏览。下面我将为你提供一个简单的例子,演示它们的用法。首先,确保你的项目已经安装了Microsoft.AspNetCore.StaticFiles NuGet包,因为这是这些中间件的依赖。dotn......
  • .Net Core 实现 自定义Http的Range输出实现断点续传或者分段下载
    一、Http的Range请求头,结合相应头Accept-Ranges、Content-Range可以实现如下功能:1.断点续传。用于下载文件被中断后,继续下载。2.大文件指定区块下载,如视频、音频拖动播放,直接定位到指定位置下载内容。可以避免每次都读取、传输整个文件,从而提升服务端性能。3.大文件分包批量下......