.net core 8 配置中心热部署以及集成Consul实现微服务式调用

前言:

配置中心对于日益增长的服务集群来说是十分必要的,能有效方便我们对配置的管理,我们常见的配置中心有Apollo, Nacos等,本篇文章基于Apllo架构设计简单实现功能。

参考文档:Apollo配置中心设计

原理:

1.IOptionMontior对appsetting.json热更新的支持,底层还是FileWatch基于onchange实现;

2.Newtonsoft.Json的强大JObject对象,方便我们解析json就行个性化处理;

(强推B站Up主 十月的寒流 关于Newtonsoft的各种使用)

3. 基于File实现的读写文件

4.基于Consul的服务注册以及发现(这里大家可以具体实现自己想要的服务中心)

代码实现:

想基于nuget包给后续需要的服务使用,所以会对其进行一些精简。

基础服务

1.通过MinimalApi方式注入每个服务器,暴露一个同意接口,后续方便我们基于service address+service port + "固定url" 方式调用。

下面对于configuration等update比较粗糙,可以自定义实现自己的细节。

using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace MinimalOnlineChat.extension;

public static class UseConfigHotReloadExtension
{
    /// <summary>
    /// 将服务注册到注册中心,Consul,然后里面写一个服务去实现管理和分配每个service,保留接口给portal,用户可以选择服务列表,然后我们根据服务列表的具体ip+port +param去调用各个服务url,然后让其hot-reload.
    /// </summary>
    /// <param name="app"></param>
    /// <returns></returns>
    public static IApplicationBuilder UseConfigHotReload(this IApplicationBuilder app)
    {
        var application = app as WebApplication;
        application?.MapPost("/api/updateAppConfig", (string jsonParam, string? environmentParam,[FromServices]IConfiguration configuration) =>
        {
            try
            {
                string environment = string.Empty;
                if (!string.IsNullOrEmpty(environmentParam))
                {
                    environment = "." + environmentParam;
                }
            
                JObject destJObject = JObject.Parse(jsonParam);

                var appSettingsPath = Path.Combine(Directory.GetCurrentDirectory(), $"appsettings{environment}.json");
                var json = File.ReadAllText(appSettingsPath);
                //当前appsetting.json里面的数据。
                var sourceJObject = JObject.Parse(json);

                MergeJObjects(sourceJObject, destJObject);

                string output = JsonConvert.SerializeObject(sourceJObject, Formatting.Indented);
                File.WriteAllText(appSettingsPath, output);
                
                //触发配置重新加载
                (configuration as IConfigurationRoot)?.Reload();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                throw;
            }
        });

        return app;
    }
    
    
    private static void MergeJObjects(JObject sourceJObject, JObject destJObject)
    {
        foreach (var destProperty in destJObject.Properties())
        {
            if (sourceJObject.TryGetValue(destProperty.Name, out JToken sourceToken))
            {
                if (destProperty.Value is JObject nestedDestObject && sourceToken is JObject nestedSourceObject)
                {
                    //如果属性值是 JObject, 则递归处理
                    MergeJObjects(nestedSourceObject, nestedDestObject);
                }
                else
                {
                    // 更新 destJObject 中的值
                    sourceJObject[destProperty.Name] = destProperty.Value;
                }
            }
            else
            {
                // 如果 sourceJObject 中不存在同名属性, 则在 destJObject 中添加新的属性
                sourceJObject.Add(destProperty.Name, destProperty.Value);
            }
        }
    }
}

2.Program.cs 注入服务:

//感兴趣可以看看里面的实现细节,已经帮我们注入IOptionsMonitor了
builder.Services.AddOptions();

........
var app = builder.Build();

.......
//配置在请求管道里面,是我们注册的MinimalAPi生效
app.UseConfigHotReload();

以上就可以简单简单实现我们对指定环境的appsetting.json进行更新了,如果基于本地单服务实现的话这里就足够啦,下面将介绍如何引入Consul做我们的配置中心.

Consul微服务:

1.先本地安装Consul,如果有需要集群部署的话,可以借助Docker部署,大家可以去参考其他大佬的实现;

2.1 定义Consul配置映射实体:

public class ConsulOption
{
    public string? IP { get; set; }
    public string? Port { get; set; }
    /// <summary>
    /// 微服务名称
    /// </summary>
    public string? ServiceName { get; set; }
    
    /// <summary>
    /// consul对应的请求地址
    /// </summary>
    public string? ConsulHost { get; set; }
    
    /// <summary>
    /// consul数据中心,默认dc1
    /// </summary>
    public string?  ConsulDataCenter { get; set; }
}

2.2 appsetting.json配置我们的Consul配置:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "ConsulOption": {
    "IP": "localhost",
    "Port": "5029",
    "ServiceName": "HotReloadService",
    "ConsulHost": "http://localhost:8500",
    "ConsulDataCenter": "dc1"
  },
  "AllowedHosts": "*",
  "Test": "dddddd"
}

3.连接Consul,并将当前server注册到Consul上

using Consul;
using Microsoft.Extensions.Options;
using MinimalOnlineChat.entity;

namespace MinimalOnlineChat.extension;

public static class ServiceExtension
{
    public static WebApplication UseConsulRegistry(this WebApplication webApplication, IHostApplicationLifetime builder)
    {
        var optionsMonitor = webApplication.Services.GetService<IOptionsMonitor<ConsulOption>>();
        
        var consulOption = optionsMonitor!.CurrentValue;
        //获取心跳监测的Ip和port
        var consulOptionIp = webApplication.Configuration["ip"] ?? consulOption.IP;
        var port = webApplication.Configuration["Port"] ?? consulOption.Port;
        
        //生成serviceId
        var id = Guid.NewGuid().ToString();

        var client = webApplication.Services.GetService<IConsulClient>() ?? new ConsulClient(c=>
        {
            c.Address = new Uri(consulOption.ConsulHost!);
            c.Datacenter = consulOption.ConsulDataCenter;
        });
        
        //把服务注册到consul上
        client.Agent.ServiceRegister(new AgentServiceRegistration()
        {
            ID = id,
            Name = consulOption.ServiceName,
            Address = consulOptionIp,
            Port = Convert.ToInt32(port),
            Check = new AgentServiceCheck
            {
                Interval = TimeSpan.FromSeconds(12),
                HTTP = $"http://{consulOptionIp}:{port}/api/health",
                Timeout = TimeSpan.FromSeconds(5),
                DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(20)
            }
        });
        
        builder.ApplicationStopped.Register(async () =>
        {
            Console.WriteLine("服务注销");
            await client.Agent.ServiceDeregister(id);
        });

        return webApplication;
    }
}

4.Program.cs  Minimal Api暴露一个接口与Consul服务进行健康检查.

app.MapGet("/api/health", () =>
{
    Console.WriteLine("Ok");
    return new
    {
        Message = "Ok"
    };
});

5.依赖注入服务到容器中,配置请求管道

builder.Services.Configure<ConsulOption>(builder.Configuration.GetSection("ConsulOption"));
//注入IConsulClient,方便我们后续使用(注册,接触,获取service 列表)
//下面的Uri应该从配置文件读,相信大家都会啦
builder.Services.AddSingleton<IConsulClient>(new ConsulClient(x =>
{
    x.Address = new Uri("http://localhost:8500");
    x.Datacenter = "dc1";
}));

.................

var app = builder.Build();

.................


app.UseConsulRegistry(app.Lifetime);

.................

6.获取服务列表,拼接请求字符串:

    //1.依赖注入IConsulClient ,我们使用其来获取服务列表
    private readonly IConfiguration _configuration;
    private static int _version = 0;
    private IConsulClient _client;
    
    public HotReloadController(IConfiguration configuration,IConsulClient client)
    {
        _configuration = configuration;
        _client = client;
    }

............................

    [HttpGet]
    public async Task QueryServiceNode()
    {
        var queryResult = await _client.Agent.Services();

        //这里实现你自己具体想要掉用哪个服务,通常这个接口暴露给用户UI去调用的选择更新哪个服务的config.

    }

............................

以上就是全部代码: 上面未实现的有版本控制管理,移除本地appsetting(因为我们是基于已传值foreach,所以我们需要引入版本管理比较删除非必要的KV)

该上面的小技巧已经发到Nuget上了,大家需要的话可以直接引用:

dotnet add package ConfigHotReload --version 1.0.0

后面会完善各种细节更新到github上,如果大家感兴趣或者有更好的idea 欢迎star留言。

Ending......

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值