目录
前言
Nacos 的简介
启动 Nacos
配置管理
服务发现
写在最后
前言
今年 4 月份的时候, 和平台组的同事一起调研了一下 Nacos, 也就在那个时候写了. net core 版本的非官方版的 SDK https://github.com/catcherwong/nacos-sdk-csharp .
虽然公司内部由于某些原因最后没有真正的用起来, 但很多人还是挺看好的. 在和镇汐大大沟通后, 决定写一篇博客简单介绍一下.
下面这个图, 就是本文的重点了.
Nacos 的简介
Nacos 是一个易于构建云原生应用的动态服务发现, 配置管理和服务管理平台, 它提供了一组简单易用的特性集, 帮助我们快速实现动态服务发现, 服务配置, 服务元数据及流量管理.
它有下面的关键特性
服务发现和服务健康监测
动态配置服务
动态 DNS 服务
服务及其元数据管理
...
特性还是挺多的, 也有挺多值的挖掘的地方. 有关 Nacos 的更多信息可以访问下面的地址:
- https://nacos.io/zh-cn/
- https://github.com/nacos-group
- https://github.com/alibaba/nacos
下面就开始正题了, 第一步肯定是先把 Nacos 跑起来.
启动 Nacos
由于是演示, 所以直接用 docker 启动了 Standalone MySQL 模式的.
- Git clone --depth 1 https://github.com/nacos-group/nacos-docker.git
- cd nacos-docker
- docker-compose -f example/standalone-MySQL.YAML up
运行 docker-compose 后, 会先拉取几个镜像回来, 然后就看到下面的输出, 基本就是正常启动了.
打开浏览器访问 http://localhost:8848/nacos 就可以看到 Nacos 控制台的登录界面了.
初始的用户名和密码都是 nacos, 登录进来之后大概是这样的.
可以看到运行起来的 Nacos, 版本是 1.1.3, 还有清晰可见的几个大菜单, 这些都是可以很方便我们去进行管理的.
那我们就先来看一下 Nacos 的配置管理吧.
配置管理
在上面的特性大图中, 已经很明确的告诉了我们配置管理的几个重要功能.
在配置中有几个比较重要的概念需要先了解一下.
tenant 租户信息, 对应 Nacos 的命名空间字段.
dataId 配置 ID.
group 配置分组.
先添加下面这个 nuget 包, 然后看一下这个配置要怎么玩.
dotnet add package nacos-sdk-csharp-unofficial
还有必不可少的就是在 Startup 里面进行配置.
- public void ConfigureServices(IServiceCollection services)
- {
- // configuration
- services.AddNacos(configure =>
- {
- // default timeout
- configure.DefaultTimeOut = 8;
- // nacos's endpoint
- configure.ServerAddresses = new System.Collections.Generic.List<string> { "localhost:8848" };
- // namespace
- configure.Namespace = "";
- // listen interval
- configure.ListenInterval = 1000;
- });
- services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
- }
这个也算是比较常见的配置了, 就不多说了, 还可以通过配置文件来加载配置.
这些配置里面, 其实最主要的就是 Nacos 的地址.
先来看看最简单的获取配置信息.
SDK 中提供了一个名为 INacosConfigClient 的 Client 接口, 这个接口里面的所有内容都是操作配置相关的.
- [Route("api/[controller]")]
- [ApiController]
- public class ConfigController : ControllerBase
- {
- private readonly INacosConfigClient _configClient;
- public ConfigController(INacosConfigClient configClient)
- {
- _configClient = configClient;
- }
- // GET API/config?key=demo1
- [HttpGet("")]
- public async Task<string> Get([FromQuery]string key)
- {
- var res = await _configClient.GetConfigAsync(new GetConfigRequest
- {
- DataId = key,
- Group = "DEFAULT_GROUP",
- //Tenant = "tenant"
- }) ;
- return string.IsNullOrWhiteSpace(res) ? "Not Found" : res;
- }
- }
上面获取配置的这个获取配置的方法, 大意就是 读取默认命名空间 (public) 下面的 DEFAULT_GROUP 这个配置分组下面的, 名为 key 的配置 Id 的值.
如果我们输入的 key, 在 Nacos 上面没有, 那个这个方法就会返回 Not Found 给调用方, 如果有, 那就会返回具体的配置值.
由于我们是刚运行起行, 什么都没有操作, 所以肯定是没有任何配置信息的.
那我们就先添加一个, 看看效果如何.
同样在上面的控制器中加入下面的发布配置的方法, 同样也是通过 INacosConfigClient 来添加配置.
- // GET API/config/add?key=demo1&value=123
- [HttpGet("add")]
- public async Task<string> Add([FromQuery]string key, [FromQuery]string value)
- {
- var res = await _configClient.PublishConfigAsync(new PublishConfigRequest
- {
- DataId = key,
- Group = "DEFAULT_GROUP",
- //Tenant = "tenant"
- Content = value
- });
- return res.ToString();
- }
这个时候我们已经添加成功了.
\ 回去控制台, 也可以看到刚才加的配置已经出来了.
再一次访问获取配置信息的接口, 就已经可以拿到对应的配置内容了.
下面通过控制台去修改一下配置的内容.
点发布按钮的时候, 会有一个比较页面, 让我们对比前后修改了那些内容.
这个时候我们通过 INacosConfigClient 去访问的话, 发现是获取不到我们刚才更新的内容的.
这个是因为, 从 Nacos 读取配置成功后, 会写入配置信息到本地缓存中, 后面访问的话会优先去读缓存的内容.
那么要怎么做到有人修改了配置内容后, 它能实时生效呢? 其实很简单, 只需要添加一下对配置的监听就可以了.
这个得益于 Nacos 允许我们监听配置, 以便实时感知配置变更. 如果配置变更, 则用获取配置接口获取配置的最新值, 动态刷新本地缓存.
下面是一个简单的示例, 这里用的是 BackgroundService 来处理的.
- public class ListenConfigurationBgTask : BackgroundService
- {
- private readonly ILogger _logger;
- private readonly INacosConfigClient _configClient;
- public ListenConfigurationBgTask(ILoggerFactory loggerFactory, INacosConfigClient configClient)
- {
- _logger = loggerFactory.CreateLogger<ListenConfigurationBgTask>();
- _configClient = configClient;
- }
- protected override async Task ExecuteAsync(CancellationToken stoppingToken)
- {
- // Add listener
- await _configClient.AddListenerAsync(new AddListenerRequest
- {
- DataId = "demo1",
- //Group = "DEFAULT_GROUP",
- //Tenant = "tenant",
- Callbacks = new List<Action<string>>
- {
- x =>
- {
- _logger.LogInformation($"We found something changed!!! {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} [{x}]");
- },
- }
- });
- }
- public override async Task StopAsync(CancellationToken cancellationToken)
- {
- // Remove listener
- await _configClient.RemoveListenerAsync(new RemoveListenerRequest
- {
- DataId = "demo1",
- Callbacks = new List<Action>
- {
- () =>
- {
- _logger.LogInformation($"Removed listerner");
- },
- }
- });
- await base.StopAsync(cancellationToken);
- }
- }
这里其实没有什么内容, 就是在程序启动的时候添加一下监听, 然后在程序退出的时候, 同样也退出监听.
不要忘记在 Startup 中加下面的代码, 这样配置的监听才会生效!
services.AddHostedService<ListenConfigurationBgTask>();
当我们添加监听之后, 修改了配置文件的内容, 它就可以动态的更新加载了.
同样的, 控制台里面也有监听的记录, 可以在监听查询里面找到.
下面是具体的程序日志输出
配置的每一次修改, 都会有历史记录, 可以从历史版本里面找到.
除了能看历史的记录, 还可以回滚到指定的版本, 这是个很有用的功能.
在数据库中, 配置信息的保存是这样的
还有一个删除配置的方法, 这里就不介绍了, 都是差不多的用法, 不过正常情况下是不应该删除配置的, 除非是多余的.
关于 Nacos 配置管理的介绍就先到这里了, 有兴趣的朋友可以继续去深究.
下面我们就来看看 Nacos 的服务发现.
服务发现
关于服务注册和发现, 听的比较多的大概就是, consul, eureka, etcd , k8s 等等.
思路其实都差不多, 在服务启动的时候, 把当前服务的相关信息注册上去, 然后要调用某个服务的时候, 就获取这个服务下面的列表, 然后选一个可用的进行访问. 最后就是当服务停止的时候, 我们要注销当前的服务.
目前这个 SDK 提供了两种形式, 一种是原始的 API, 一种是对原始 API 进行了封装, 可以直接注册和发现相应的下游服务.
原始的 API 在一个名为 INacosNamingClient 的 Client 接口中提供, 这个接口里面的所有内容都是服务发现相关的.
不过在这里只介绍封装过后的使用方法, 当然也可以自己根据原始的 API 进行封装处理.
首先要添加下面这个 nuget 包.
dotnet add package nacos-sdk-csharp-unofficial.AspNetCore
先起来一个服务.
先在配置文件 appsettings.JSON 中添加下面的内容
- {
- "nacos": {
- "ServerAddresses": [ "localhost:8848" ],
- "DefaultTimeOut": 15,
- "Namespace": "",
- "ListenInterval": 1000,
- "ServiceName": "BaseService",
- "Weight": 10
- }
- }
这个配置主要表达了, 这个实例的服务名是 BaseService, 权重是 10, Nacos 的地址是 localhost:8848.
然后在 Startup 中把当前实例注册到 Nacos.
- namespace BaseService
- {
- using Microsoft.AspNetCore.Builder;
- using Microsoft.AspNetCore.Hosting;
- using Microsoft.AspNetCore.Mvc;
- using Microsoft.Extensions.Configuration;
- using Microsoft.Extensions.DependencyInjection;
- using Nacos.AspNetCore;
- public class Startup
- {
- public Startup(IConfiguration configuration)
- {
- Configuration = configuration;
- }
- public IConfiguration Configuration { get; }
- public void ConfigureServices(IServiceCollection services)
- {
- // important step
- services.AddNacosAspNetCore(Configuration);
- services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
- }
- public void Configure(IApplicationBuilder App, IHostingEnvironment env)
- {
- if (env.IsDevelopment())
- {
- App.UseDeveloperExceptionPage();
- }
- App.UseMvc();
- // important step
- App.UseNacosAspNetCore();
- }
- }
- }
这里只需要简单的配置这两个地方就可以完成服务的注册功能了!!
下面就启动这个程序.
可以看到在启动程序的时候, 当前实例就会向 Nacos 发送心跳, 心跳的里面包含了 IP 和端口等信息.
回到控制台, 我们可以看到这个服务现在已经有一个实例了.
再启动一个同服务名的实例, 这里只对接口返回的内容做了一下调整, 其他都是一样的!
这个时候点进服务的详情里面, 可以看到更加具体的信息.
服务现在是已经注册上来了, 下面我们就再来一个服务去调用上面这个注册好的服务.
Startup 中的内容都是差不多的, 不同的是, 如果确定服务不被内部其它应用调用的话, 可以不注册到 Nacos 上面.
- public class Startup
- {
- public Startup(IConfiguration configuration)
- {
- Configuration = configuration;
- }
- public IConfiguration Configuration { get; }
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddHttpClient();
- services.AddNacosAspNetCore(Configuration);
- services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
- }
- public void Configure(IApplicationBuilder App, IHostingEnvironment env)
- {
- if (env.IsDevelopment())
- {
- App.UseDeveloperExceptionPage();
- }
- App.UseMvc();
- //App.UseNacosAspNetCore();
- }
- }
然后就是发现服务了.
INacosServerManager 里面提供了一个只根据服务名来获取健康的实例的地址信息. 不足的地方就是忽略了命名空间和集群这些参数, 会考虑在后面的版本中加上吧.
这里获取到的地址信息是随机取出来的, 最简单的轮训算法.. 获取到一次所有的实例地址信息后会缓存 10 秒钟, 这 10 秒钟里面就会直接从缓存中的地址信息取一个.
- [Route("api/[controller]")]
- [ApiController]
- public class ValuesController : ControllerBase
- {
- private readonly INacosServerManager _serverManager;
- private readonly IHttpClientFactory _clientFactory;
- public ValuesController(INacosServerManager serverManager, IHttpClientFactory clientFactory)
- {
- _serverManager = serverManager;
- _clientFactory = clientFactory;
- }
- // GET API/values
- [HttpGet]
- public async Task<string> GetAsync()
- {
- var result = await GetResultAsync();
- if (string.IsNullOrWhiteSpace(result))
- {
- result = "ERROR!!!";
- }
- return result;
- }
- private async Task<string> GetResultAsync()
- {
- var baseUrl = await _serverManager.GetServerAsync("BaseService");
- if (string.IsNullOrWhiteSpace(baseUrl))
- {
- return "";
- }
- var url = $"{baseUrl}/api/values";
- var client = _clientFactory.CreateClient();
- var result = await client.GetAsync(url);
- return await result.Content.ReadAsStringAsync();
- }
- }
效果就来看动图了.
在两个实例的健康状态都是 true 的时候, 会随机调用一个实例.
当把其中一个实例停掉的时候, 这个实例的健康状态就会被标识为 false, 这个时候就不会调用到这个 false 的实例.
当把这个实例重新运行之后, 又恢复到随机调用的情况.
Nacos 的服务发现除了上面介绍的, 还有系统开关, 数据指标, 集群信息等功能, 有待去深入挖掘.
写在最后
Nacos 使用起来不算复杂, 算是比较容易上手的, 用的公司也挺多的了.
还有个把 steeltoe 和 Nacos 结合起来的项目 https://github.com/magicsgxie/skynet-cloud 也可以看看.
文中的示例代码可以戳这里
SDK 的地址 https://github.com/catcherwong/nacos-sdk-csharp
希望感兴趣的大佬给个星星, 也十分希望有大佬来一起维护这个项目, 和提些建议.
因为是第一次写 SDK 类的东西, 参考了其他平台提供. NET 的 SDK, 然后结合 Nacos 的 Open API 写的, 有可能会有不少遗漏和 bug, 还请各位大佬多多包涵.
来源: https://www.cnblogs.com/catcher1994/p/11489052.html