如何在 ASP.Net Core 中使用 Consul 来存储配置

原文: USING CONSUL FOR STORING THE CONFIGURATION IN ASP.NET CORE
作者: Nathanael

[译者注:因急于分享给大家,所以本文翻译的很仓促,有些不准确的地方还望谅解]

来自 Hashicorp 公司的 Consul 是一个用于分布式架构的工具,可以用来做服务发现、运行健康检查和 kv 存储。本文详细介绍了如何使用 Consul 通过实现 ConfigurationProvider 在 ASP.Net Core 中存储配置。

为什么使用工具来存储配置?

通常,.Net 应用程序中的配置存储在配置文件中,例如 App.config、Web.config 或 appsettings.json。从 ASP.Net Core 开始,出现了一个新的可扩展配置框架,它允许将配置存储在配置文件之外,并从命令行、环境变量等等中检索它们。
配置文件的问题是它们很难管理。实际上,我们通常最终做法是使用配置文件和对应的转换文件,来覆盖每个环境。它们需要与 dll 一起部署,因此,更改配置意味着重新部署配置文件和 dll 。不太方便。
使用单独的工具集中化可以让我们做两件事:

  • 在所有机器上具有相同的配置

  • 能够在不重新部署任何内容的情况下更改值(对于功能启用关闭很有用)

Consul 介绍

本文的目的不是讨论 Consul,而是专注于如何将其与 ASP.Net Core 集成。
但是,简单介绍一下还是有必要的。Consul 有一个 Key/Value 存储功能,它是按层次组织的,可以创建文件夹来映射不同的应用程序、环境等等。这是一个将在本文中使用的层次结构的示例。每个节点都可以包含 JSON 值。

/
|-- App1| |-- Dev| | |-- ConnectionStrings| | \-- Settings| |-- Staging| | |-- ConnectionStrings| | \-- Settings| \-- Prod|   |-- ConnectionStrings|   \-- Settings\-- App2|-- Dev| |-- ConnectionStrings| \-- Settings|-- Staging| |-- ConnectionStrings| \-- Settings\-- Prod|-- ConnectionStrings\-- Settings

它提供了 REST API 以方便查询,key 包含在查询路径中。例如,获取 App1 在 Dev 环境中的配置的查询如下所示:GET http://:8500/v1/kv/App1/Dev/Settings
响应如下:

HTTP/1.1 200 OKContent-Type: application/jsonX-Consul-Index: 1071X-Consul-Knownleader: trueX-Consul-Lastcontact: 0[{        "LockIndex": 0,        "Key": "App1/Dev/Settings",        "Flags": 0,        "Value": "ewogIkludCI6NDIsCiAiT2JqZWN0IjogewogICJTdHJpbmciOiAidG90byIsCiAgIkJsYSI6IG51bGwsCiAgIk9iamVjdCI6IHsKICAgIkRhdGUiOiAiMjAxOC0wMi0yM1QxNjoyMTowMFoiCiAgfQogfQp9Cgo=",        "CreateIndex": 501,        "ModifyIndex": 1071}
]

也可以以递归方式查询任何节点,GET http://:8500/v1/kv/App1/Dev?recurse 返回 :

HTTP/1.1 200 OKContent-Type: application/jsonX-Consul-Index: 1071X-Consul-Knownleader: trueX-Consul-Lastcontact: 0[{        "LockIndex": 0,        "Key": "App1/Dev/",        "Flags": 0,        "Value": null,        "CreateIndex": 75,        "ModifyIndex": 75},{        "LockIndex": 0,        "Key": "App1/Dev/ConnectionStrings",        "Flags": 0,        "Value": "ewoiRGF0YWJhc2UiOiAiU2VydmVyPXRjcDpkYmRldi5kYXRhYmFzZS53aW5kb3dzLm5ldDtEYXRhYmFzZT1teURhdGFCYXNlO1VzZXIgSUQ9W0xvZ2luRm9yRGJdQFtzZXJ2ZXJOYW1lXTtQYXNzd29yZD1teVBhc3N3b3JkO1RydXN0ZWRfQ29ubmVjdGlvbj1GYWxzZTtFbmNyeXB0PVRydWU7IiwKIlN0b3JhZ2UiOiJEZWZhdWx0RW5kcG9pbnRzUHJvdG9jb2w9aHR0cHM7QWNjb3VudE5hbWU9ZGV2YWNjb3VudDtBY2NvdW50S2V5PW15S2V5OyIKfQ==",        "CreateIndex": 155,        "ModifyIndex": 155},{        "LockIndex": 0,        "Key": "App1/Dev/Settings",        "Flags": 0,        "Value": "ewogIkludCI6NDIsCiAiT2JqZWN0IjogewogICJTdHJpbmciOiAidG90byIsCiAgIkJsYSI6IG51bGwsCiAgIk9iamVjdCI6IHsKICAgIkRhdGUiOiAiMjAxOC0wMi0yM1QxNjoyMTowMFoiCiAgfQogfQp9Cgo=",        "CreateIndex": 501,        "ModifyIndex": 1071}
]

我们可以看到许多内容通过这个响应,首先我们可以看到每个 key 的 value 值都使用了 Base64 编码,以避免 value 值和 JSON 本身混淆,然后我们注意到属性“Index”在 JSON 和 HTTP 头中都有。 这些属性是一种时间戳,它们可以我们知道是否或何时创建或更新的 value。它们可以帮助我们知道是否需要重新加载这些配置了。

ASP.Net Core 配置系统

这个配置的基础结构依赖于 Microsoft.Extensions.Configuration.Abstractions NuGet包中的一些内容。首先,IConfigurationProvider 是用于提供配置值的接口,然后IConfigurationSource 用于提供已实现上述接口的 provider 的实例。
您可以在 ASP.Net GitHub 上查看一些实现。
与直接实现 IConfigurationProvider 相比,可以在 Microsoft.Extensions.Configuration 包中继承一个名为 ConfigurationProvider 的类,该类提供了一些样版代码(例如重载令牌的实现)。
这个类包含两个重要的东西:

/* Excerpt from the implementation */public abstract class ConfigurationProvider : IConfigurationProvider{    protected IDictionary<string, string> Data { get; set; }    public virtual void Load()    {}
}

Data 是包含所有键和值的字典,Load 是应用程序开始时使用的方法,正如其名称所示,它从某处(配置文件或我们的 consul 实例)加载配置并填充字典。

在 ASP.Net Core 中加载 consul 配置

我们第一个想到的方法就是利用 HttpClient 去获取 consul 中的配置。然后,由于配置在层级式的,像一棵树,我们需要把它展开,以便放入字典中,是不是很简单?

首先,实现 Load 方法,但是我们需要一个异步的方法,原始方法会阻塞,所以加入一个异步的 LoadAsync 方法

public override void Load() => LoadAsync().ConfigureAwait(false).GetAwaiter().GetResult();

然后,我们将以递归的方式查询 consul 以获取配置值。它使用类中定义的一些对象,例如_consulUrls,这是一个数组用来保存 consul 实例们的 url(用于故障转移),_path 是键的前缀(例如App1/Dev)。一旦我们得到 json ,我们迭代每个键值对,解码 Base64 字符串,然后展平所有键和JSON对象。

private async Task<IDictionary<string, string>> ExecuteQueryAsync()
{    int consulUrlIndex = 0;    while (true){        try{            using (var httpClient = new HttpClient(new HttpClientHandler { AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip }, true))            using (var request = new HttpRequestMessage(HttpMethod.Get, new Uri(_consulUrls[consulUrlIndex], "?recurse=true")))            using (var response = await httpClient.SendAsync(request)){response.EnsureSuccessStatusCode();                var tokens = JToken.Parse(await response.Content.ReadAsStringAsync());                return tokens.Select(k => KeyValuePair.Create(k.Value<string>("Key").Substring(_path.Length + 1),k.Value<string>("Value") != null ? JToken.Parse(Encoding.UTF8.GetString(Convert.FromBase64String(k.Value<string>("Value")))) : null)).Where(v => !string.IsNullOrWhiteSpace(v.Key)).SelectMany(Flatten).ToDictionary(v => ConfigurationPath.Combine(v.Key.Split('/')), v => v.Value, StringComparer.OrdinalIgnoreCase);}}        catch{consulUrlIndex++;            if (consulUrlIndex >= _consulUrls.Count)                throw;}}
}

使键值变平的方法是对树进行简单的深度优先搜索。

private static IEnumerable<KeyValuePair<string, string>> Flatten(KeyValuePair<string, JToken> tuple)
{    if (!(tuple.Value is JObject value))        yield break;    foreach (var property in value){        var propertyKey = $"{tuple.Key}/{property.Key}";        switch (property.Value.Type){            case JTokenType.Object:                foreach (var item in Flatten(KeyValuePair.Create(propertyKey, property.Value)))                    yield return item;                break;            case JTokenType.Array:                break;            default:                yield return KeyValuePair.Create(propertyKey, property.Value.Value<string>());                break;}}
}

包含构造方法和私有字段的完整的类代码如下:

public class SimpleConsulConfigurationProvider : ConfigurationProvider{    private readonly string _path;    private readonly IReadOnlyList<Uri> _consulUrls;    public SimpleConsulConfigurationProvider(IEnumerable<Uri> consulUrls, string path)    {_path = path;_consulUrls = consulUrls.Select(u => new Uri(u, $"v1/kv/{path}")).ToList();        if (_consulUrls.Count <= 0){            throw new ArgumentOutOfRangeException(nameof(consulUrls));}}    public override void Load() => LoadAsync().ConfigureAwait(false).GetAwaiter().GetResult();    private async Task LoadAsync()    {Data = await ExecuteQueryAsync();}    private async Task<IDictionary<string, string>> ExecuteQueryAsync(){        int consulUrlIndex = 0;        while (true){            try{                var requestUri = "?recurse=true";                using (var httpClient = new HttpClient(new HttpClientHandler { AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip }, true))                using (var request = new HttpRequestMessage(HttpMethod.Get, new Uri(_consulUrls[consulUrlIndex], requestUri)))                using (var response = await httpClient.SendAsync(request)){response.EnsureSuccessStatusCode();                    var tokens = JToken.Parse(await response.Content.ReadAsStringAsync());                    return tokens.Select(k => KeyValuePair.Create(k.Value<string>("Key").Substring(_path.Length + 1),k.Value<string>("Value") != null ? JToken.Parse(Encoding.UTF8.GetString(Convert.FromBase64String(k.Value<string>("Value")))) : null)).Where(v => !string.IsNullOrWhiteSpace(v.Key)).SelectMany(Flatten).ToDictionary(v => ConfigurationPath.Combine(v.Key.Split('/')), v => v.Value, StringComparer.OrdinalIgnoreCase);}}            catch{consulUrlIndex = consulUrlIndex + 1;                if (consulUrlIndex >= _consulUrls.Count)                    throw;}}}    private static IEnumerable<KeyValuePair<string, string>> Flatten(KeyValuePair<string, JToken> tuple){        if (!(tuple.Value is JObject value))            yield break;        foreach (var property in value){            var propertyKey = $"{tuple.Key}/{property.Key}";            switch (property.Value.Type){                case JTokenType.Object:                    foreach (var item in Flatten(KeyValuePair.Create(propertyKey, property.Value)))                        yield return item;                    break;                case JTokenType.Array:                    break;                default:                    yield return KeyValuePair.Create(propertyKey, property.Value.Value<string>());                    break;}}}
}

动态重新加载配置

我们可以进一步使用 consul 的变更通知。它只是通过添加一个参数(最后一个索引配置的值)来工作的,HTTP 请求会一直阻塞,直到下一次配置变更(或 HttpClient 超时)。

与前面的类相比,我们只需添加一个方法 ListenToConfigurationChanges,以便在后台监听 consul 的阻塞 HTTP 。

public class ConsulConfigurationProvider : ConfigurationProvider{    private const string ConsulIndexHeader = "X-Consul-Index";    private readonly string _path;    private readonly HttpClient _httpClient;    private readonly IReadOnlyList<Uri> _consulUrls;    private readonly Task _configurationListeningTask;    private int _consulUrlIndex;    private int _failureCount;    private int _consulConfigurationIndex;    public ConsulConfigurationProvider(IEnumerable<Uri> consulUrls, string path)    {_path = path;_consulUrls = consulUrls.Select(u => new Uri(u, $"v1/kv/{path}")).ToList();        if (_consulUrls.Count <= 0){            throw new ArgumentOutOfRangeException(nameof(consulUrls));}_httpClient = new HttpClient(new HttpClientHandler { AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip }, true);_configurationListeningTask = new Task(ListenToConfigurationChanges);}    public override void Load() => LoadAsync().ConfigureAwait(false).GetAwaiter().GetResult();    private async Task LoadAsync()    {Data = await ExecuteQueryAsync();        if (_configurationListeningTask.Status == TaskStatus.Created)_configurationListeningTask.Start();}    private async void ListenToConfigurationChanges()    {        while (true){            try{                if (_failureCount > _consulUrls.Count){_failureCount = 0;                    await Task.Delay(TimeSpan.FromMinutes(1));}Data = await ExecuteQueryAsync(true);OnReload();_failureCount = 0;}            catch (TaskCanceledException){_failureCount = 0;}            catch{_consulUrlIndex = (_consulUrlIndex + 1) % _consulUrls.Count;_failureCount++;}}}    private async Task<IDictionary<string, string>> ExecuteQueryAsync(bool isBlocking = false){        var requestUri = isBlocking ? $"?recurse=true&index={_consulConfigurationIndex}" : "?recurse=true";        using (var request = new HttpRequestMessage(HttpMethod.Get, new Uri(_consulUrls[_consulUrlIndex], requestUri)))        using (var response = await _httpClient.SendAsync(request)){response.EnsureSuccessStatusCode();            if (response.Headers.Contains(ConsulIndexHeader)){                var indexValue = response.Headers.GetValues(ConsulIndexHeader).FirstOrDefault();                int.TryParse(indexValue, out _consulConfigurationIndex);}            var tokens = JToken.Parse(await response.Content.ReadAsStringAsync());            return tokens.Select(k => KeyValuePair.Create(k.Value<string>("Key").Substring(_path.Length + 1),k.Value<string>("Value") != null ? JToken.Parse(Encoding.UTF8.GetString(Convert.FromBase64String(k.Value<string>("Value")))) : null)).Where(v => !string.IsNullOrWhiteSpace(v.Key)).SelectMany(Flatten).ToDictionary(v => ConfigurationPath.Combine(v.Key.Split('/')), v => v.Value, StringComparer.OrdinalIgnoreCase);}}    private static IEnumerable<KeyValuePair<string, string>> Flatten(KeyValuePair<string, JToken> tuple){        if (!(tuple.Value is JObject value))            yield break;        foreach (var property in value){            var propertyKey = $"{tuple.Key}/{property.Key}";            switch (property.Value.Type){                case JTokenType.Object:                    foreach (var item in Flatten(KeyValuePair.Create(propertyKey, property.Value)))                        yield return item;                    break;                case JTokenType.Array:                    break;                default:                    yield return KeyValuePair.Create(propertyKey, property.Value.Value<string>());                    break;}}}
}

组合在一起

我们现在有了一个 ConfigurationProvider, 让我们再写一个 ConfigurationSource 来创建 我们的 provider.

public class ConsulConfigurationSource : IConfigurationSource{    public IEnumerable<Uri> ConsulUrls { get; }    public string Path { get; }    public ConsulConfigurationSource(IEnumerable<Uri> consulUrls, string path)    {ConsulUrls = consulUrls;Path = path;}    public IConfigurationProvider Build(IConfigurationBuilder builder)    {        return new ConsulConfigurationProvider(ConsulUrls, Path);}
}

以及一些扩展方法 :

public static class ConsulConfigurationExtensions{public static IConfigurationBuilder AddConsul(this IConfigurationBuilder configurationBuilder, IEnumerable<Uri> consulUrls, string consulPath){        return configurationBuilder.Add(new ConsulConfigurationSource(consulUrls, consulPath));}    public static IConfigurationBuilder AddConsul(this IConfigurationBuilder configurationBuilder, IEnumerable<string> consulUrls, string consulPath){        return configurationBuilder.AddConsul(consulUrls.Select(u => new Uri(u)), consulPath);}
}

现在可以在 Program.cs 中添加 Consul,使用其他的来源(例如环境变量或命令行参数)来向 consul 提供 url

public static IWebHost BuildWebHost(string[] args) =>WebHost.CreateDefaultBuilder(args).ConfigureAppConfiguration(cb =>{            var configuration = cb.Build();cb.AddConsul(new[] { configuration.GetValue<Uri>("CONSUL_URL") }, configuration.GetValue<string>("CONSUL_PATH"));}).UseStartup<Startup>().Build();

现在,可以使用 ASP.Net Core 的标准配置模式了,例如 Options。

public void ConfigureServices(IServiceCollection services){services.AddMvc();services.AddOptions();services.Configure<AppSettingsOptions>(Configuration.GetSection("Settings"));services.Configure<AccountingFeaturesOptions>(Configuration.GetSection("FeatureFlags"));services.Configure<CartFeaturesOptions>(Configuration.GetSection("FeatureFlags"));services.Configure<CatalogFeaturesOptions>(Configuration.GetSection("FeatureFlags"));
}

要在我们的代码中使用它们,请注意如何使用 options ,对于可以动态重新加载的 options,使用 IOptions 将获得初始值。反之,ASP.Net Core 需要使用 IOptionsSnapshot。
这种情况对于功能切换非常棒,因为您可以通过更改 Consul 中的值来启用或禁用新功能,并且在不重新发布的情况下,用户就可以使用这些新功能。同样的,如果某个功能出现 bug,你可以禁用它,而无需回滚或热修复。

public class CartController : Controller{[HttpPost]    
   public IActionResult AddProduct([FromServices]IOptionsSnapshot<CartFeaturesOptions> options, [FromBody] Product product)    {  
     var cart = _cartService.GetCart(this.User);cart.Add(product);      
      if (options.Value.UseCartAdvisorFeature){ViewBag.CartAdvice = _cartAdvisor.GiveAdvice(cart);}        return View(cart);} }

尾声

这几行代码允许我们在 ASP.Net Core 应用程序中添加对 consul 配置的支持。事实上,任何应用程序(甚至使用 Microsoft.Extensions.Configuration 包的经典 .Net 应用程序)都可以从中受益。在 DevOps 环境中这将非常酷,你可以将所有配置集中在一个位置,并使用热重新加载功能进行实时切换。

原文链接:https://www.cnblogs.com/Rwing/p/consul-configuration-aspnet-core.html

.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com

640?wx_fmt=jpeg

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/320006.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[XSY3381] 踢罐子(几何)

XSY3381 点被选为点对之一的贡献我们单独计算&#xff08;这部分贡献的总和为4n(n−1)(n−2)4n(n-1)(n-2)4n(n−1)(n−2)&#xff09;。接下来只讨论剩余部分的贡献。 先把任意三个点构成的六种选择方案合并&#xff0c;发现在外接圆周和弦之间的点每个有2的贡献&#xff0c;…

The Bottom of a Graph Poj 2553

牛客网 poj 2553 文章目录Description题意&#xff1a;题解&#xff1a;代码&#xff1a;Description We will use the following (standard) definitions from graph theory. Let V be a nonempty and finite set, its elements being called vertices (or nodes). Let E be …

关于.NET Core是否应该支持WCF Hosting的争论

本文要点本文试图回答“.NET Core 是否应该支持 Windows 通信基础&#xff08;WCF&#xff09; Hosting&#xff1f;”的问题&#xff1b;支持者论据&#xff1a;许多工程师喜欢把 WCF 作为一种编程模型&#xff0c;不希望因为迁移到 .NET Core 而产生&#xff08;机会成本&…

ASP.NET Core 2.0使用Autofac实现IOC依赖注入竟然能如此的优雅简便

初识ASP.NET Core的小伙伴一定会发现&#xff0c;其几乎所有的项目依赖都是通过依赖注入方式进行链式串通的。这是因为其使用了依赖注入 (DI) 的软件设计模式&#xff0c;代码的设计是遵循着“高内聚、低耦合”的原则&#xff0c;使得各个类与类之间的关系依赖于接口&#xff0…

连续段问题小结

一个好用的工具——析合树 oi-wiki 例题 CF526F 题意&#xff1a; 给出一个1~nnn的排列&#xff0c;问有多少个区间的值域是连续的。 题解&#xff1a; 线段树单调栈做法 分治做法 析合树做法 图论做法 CF997E 题意&#xff1a; 给出一个1~nnn的排列&#xff0c;有qqq次…

实战中的asp.net core结合Consul集群Docker实现服务治理

一、前言在写这篇文章之前&#xff0c;我看了很多关于consul的服务治理&#xff0c;但发现基本上都是直接在powershell或者以命令工具的方式在服务器上面直接输入consul agent .... 来搭建启动consul集群&#xff0c;一旦把命令工具关掉&#xff0c;则consul无法再后台启动&…

POJ3177 Redundant Paths

POJ3177 Redundant Paths 文章目录Description题意&#xff1a;题解&#xff1a;代码&#xff1a;Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 21945 Accepted: 9056Description In order to get from one of the F (1 < F < 5,000) grazing fields (…

【最小生成树】路线规划(nowcoder 217603)

路线规划 nowcoder 217603 题目大意 给一个无向连通图&#xff0c;问你在经过的边最少的前提下&#xff0c;从1走过所有点&#xff0c;再走回1的最短距离 样例#1 输入样例#1 5 5 5 4 3 4 3 5 2 3 7 1 2 4 2 4 1输出样例#1 26样例解释#1 最少时间的路径: 1 →2 →…

计算几何学习小记

文章目录前言正题平面运算加减乘积常见问题直线/线段规范交点求垂线/点问题判断点在多边形的内/外求两个圆的交点前言 因为懒得画图理解计算几何所以要来这里鼓励一下自己 以后新学的应该也会写在这里。就当我是水博客 应该都是二维的计算几何&#xff0c;三维的有生之年再学 …

Asp.Net Core SignalR 用泛型Hub优雅的调用前端方法及传参

继续学习最近一直在使用Asp.Net Core SignalR(下面成SignalR Core)为小程序提供websocket支持,前端时间也发了一个学习笔记&#xff0c;在使用过程中稍微看了下它的源码,不得不说微软现在真的强大,很多事情都帮你考虑到了,比如使用Redis,使用Redis后,你的websocket就支持横向扩…

Network POJ-3694

Network POJ-3694 文章目录Description题意&#xff1a;样例分析&#xff1a;题解&#xff1a;代码&#xff1a;Description A network administrator manages a large network. The network consists of N computers and M links between pairs of computers. Any pair of com…

使用.NET Core 2.1的Azure WebJobs

WebJobs不是Azure和.NET中的新事物。 Visual Studio 2017中甚至还有一个默认的Azure WebJob模板&#xff0c;用于完整的.NET Framework。 但是&#xff0c;Visual Studio中以某种方式遗漏了.NET Core中WebJobs的类似模板。 在这篇文章中&#xff0c;我使用的是.NET Core 2.1来创…

.NET Core中的CSV解析库

感谢本篇首先特别感谢从此启程兄的《.NetCore外国一些高质量博客分享》, 发现很多国外的.NET Core技术博客资源, 我会不定期从中选择一些有意思的文章翻译总结一下。.NET Core中的CSV解析库本篇博客来源于.NET Core Totorials的《CSV Parsing In .NET Core》。背景介绍对于初级…

为什么要使用Entity Framework

本文介绍从DDD(Domain-Driven Design[领域驱动设计])的角度来说说为什么要使用Entity Framework(以下都会简称为EF)&#xff0c;同时也看出类似Drapper之类的简陋ORM不足的地方。设想业务都是大家知晓的权限管理&#xff0c;实体类如下。读到这里&#xff0c;请先思考一下&…

Tarjan算法

Tarjan算法可以应用在求解 强连通分量&#xff0c;缩点&#xff0c;桥&#xff0c;割点&#xff0c;双连通分量&#xff0c;LCA等 关于文章目录强连通分量代码题目tarjan求割点割点概念流程代码&#xff1a;求无向图的割边&#xff0f;桥理解&#xff1a;代码&#xff1a;强连通…

Ocelot简易教程(一)之Ocelot是什么

简单的说Ocelot是一个用.NET Core实现并且开源的API网关技术。可能你又要问了&#xff0c;什么是API网关技术呢&#xff1f;Ocelot又有什么特别呢&#xff1f;我们又该如何集成到我们的asp.net core程序中呢&#xff1f;下面我会通过一些列通俗易懂的教程来为大家讲解。今天的这…

如何在你的项目中集成 CAP【手把手视频教程】

前言之前录制过一期关于CAP的视频&#xff0c;但是由于当时是直播时录制的视频&#xff0c;背景音比较杂所以质量有点差。这次的视频没有直播&#xff0c;直接录制的&#xff0c;视频质量会好很多&#xff0c;第一遍录制完成之后发现播放到一半没有声音&#xff0c;所以又重新录…

【Splay】文艺平衡树(金牌导航 Splay-2)

#文艺平衡树 金牌导航 Splay-2 题目大意 给你一个1~n的序列&#xff0c;然后对序列的区间做若干次翻转&#xff0c;问你最后的序列 输入样例 5 3 1 3 1 3 1 4输出样例 4 3 2 1 5数据范围 1⩽n,m⩽105,1⩽l⩽r⩽n1\leqslant n,m\leqslant 10^5,1\leqslant l\leqslant r \l…

.net core实践系列之短信服务-Sikiro.SMS.Api服务的实现

前言本篇会继续讲解Sikiro.SMS.Job服务的实现&#xff0c;在我写第一篇的时候&#xff0c;我就发现我当时设计的架构里Sikiro.SMS.Job这个可以选择不需要&#xff0c;而使用MQ代替。但是为了说明调度任务使用实现也坚持写了下。后面会一篇针对架构、实现优化的讲解。源码地址&a…

Drainage Ditches POJ1273

Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 93263 Accepted: 36174试题链接 文章目录Description题意&#xff1a;题解&#xff1a;代码&#xff1a;Dinic做法EK做法Description Every time it rains on Farmer John’s fields, a pond forms over Bessie’…