大家好,好久没有输出博文了,一是因为比较忙,另外一个原因是最近主要的精力是在给 AgileConfig 添加一个新的功能:服务注册与发现。
先说说为什么会添加这个功能。我自己的项目是用 Consul 来做为服务注册发现组件的。自从我上线了 AgileConfig 做为配置中心后,我就很少去 Consul 观察服务的在线状态了,因为 AgileConfig 客户端列表已经在一定程度上能代表服务的状态了。服务注册发现与配置中心其实本质上都是解决了一类问题,那就是配置的动态化,所以大家会看到业界著名的组件很多都是同时实现这2个功能的,如 Consul,Nacos 等。所以我想干脆把这个功能给加上吧,这样可以省去部署一个组件。
当然也有同学说我不务正业,不去好好搞配置中心去搞什么服务注册发现。但是我还是做了。。。不过大家放心 AgileConfig 的主业还是在配置中心上,服务注册发现只是附赠的小菜,可以用也可以不用,决定权完全在你。在实现上我也是对两个功能是完全解耦的。也就是说这2个功能都是互不影响独立运行的。唯一有交集的一个地方是,如果配置中心的客户端的 websocket 通道建立成功的时候,服务的心跳会借用这个通道。
✨✨✨Github地址:https://github.com/dotnetcore/AgileConfig 开源不易,欢迎star✨✨✨
什么是服务注册与发现
首先先让我们回顾下服务注册发现的概念。
在实施微服务之后,我们的调用都变成了服务间的调用。服务间调用需要知道IP、端口等信息。在没有微服务之前,我们的调用信息一般都是写死在调用方的配置文件里(当然这话不绝对,有些公司会把这些信息写到数据库等公共的地方,以方便维护)。又由于业务的复杂,每个服务可能依赖N个其他服务,如果某个服务的IP,端口等信息发生变更,那么所有依赖该服务的服务的配置文件都要去修改,这样显然太麻烦了。有些服务为了负载是有个多个实例的,而且可能是随时会调整实例的数量。如果每次调整实例数量都要去修改其他服务的配置并重启那太麻烦了。为了解决这个问题,业界就有了服务注册发现组件。假设我们有服务A需要调用服务B,并且有服务注册发现组件R。整个大致流程将变成大概3步:
服务B启动向服务R注册自己的信息 服务A从服务R拉取服务B的信息 服务A调用服务B 有了服务注册发现组件之后,当修改A服务信息的时候再也不用去修改其他相关服务了。
参考我的另外一篇:.Net Core with 微服务 - Consul 注册中心
使用服务注册与发现
使用服务注册与发现功能需要更新服务端与客户端至 1.6.0 及以上版本。
启动服务端
服务端更新至 latest 镜像或 v-1.6.0 以上的镜像。
使用 docker 运行服务端实例:
sudo docker run \
--name agile_config \
-e TZ=Asia/Shanghai \
-e adminConsole=true \
-e db:provider=sqlite \
-e db:conn="Data Source=agile_config.db" \
-p 5000:5000 \
#-v /your_host_dir:/app/db \
-d kklldog/agile_config:latest
基本的使用没有太大的变化,只是在界面上添加了服务的相关管理界面,这里不再赘述。
相关教程: .Net Core & Agile Config配置中心
使用客户端
客户端需要从 nuget 上安装 1.6.0 版本以上的 client 包。
Install-Package AgileConfig.Client -Version 1.6.0
新版的 client 简化了使用方式,以下以 .net6 为示例:
调用 UseAgileConfig 扩展方法即可注入 AgileConfig client .
var builder = WebApplication.CreateBuilder(args);
//use agileconfig client
builder.Host.UseAgileConfig();
...
在 appsettings.json 添加配置信息:
"AgileConfig": {
"appId": "test_app",
"secret": "test_app",
"nodes": "http://agileconfig_server.xbaby.xyz/",
"name": "client123",
"tag": "tag123",
"serviceRegister": { //服务注册信息,如果不配置该节点,则不会启动任何跟服务注册相关的服务 可选
"serviceId": "net6", //服务id,全局唯一,用来唯一标示某个服务
"serviceName": "net6MVC服务测试", //服务名,可以重复,某个服务多实例部署的时候这个serviceName就可以重复
"ip": "127.0.0.1", //服务的ip 可选
"port": 5005, //服务的端口 可选
}
其中 appId , secret 等配置同原来配置中心的使用方式没有任何改变。serviceRegister
节点描述的是服务注册信息(如果删除这个节点那么服务注册功能就不会启动):
serviceId
服务id,全局唯一,用来唯一标示某个服务serviceName
服务名,可以重复,某个服务多实例部署的时候这个serviceName就可以重复ip
服务的ip 可选port
服务的端口 可选metaData
一个字符串数组,可以携带一些服务的相关信息,如版本等 可选alarmUrl
告警地址 可选。
如果某个服务出现异常情况,如一段时间内没有心跳,那么服务端会往这个地址 POST 一个请求并且携带服务相关信息,用户可以自己去实现提醒功能,比如发短信,发邮件等:
{
"serviceId":"0001",
"serviceName":"xxxx",
"time":"2022-01-01T12:00:000",
"status":"Unhealty",
"message": "服务不健康"
}
heartbeat:mode
指定心跳的模式,server/client 。server代表服务端主动检测,client代表客户端主动上报。不填默认client模式 可选heartbeat:interval
心跳的间隔,默认时间30s 可选heartbeat:url
心跳模式为 server 的时候需要填写健康检测地址,如果是httpstatus为200段则判定存活,其它都视为失败 可选
服务的注册
当配置好客户端后,启动对应的应用程序,服务信息会自动注册到服务端并且开始心跳。如果服务正确注册到服务端,控制台的服务管理界面可以查看:
服务发现
现在服务已经注册上去了,那么怎么才能拿到注册中心所有的服务呢?同样非常简单,在程序内只要注入IDiscoveryService
接口就可以通过它拿到所有的注册的服务。
public interface IDiscoveryService
{
string DataVersion { get; }
List<ServiceInfo> UnHealthyServices { get; }
List<ServiceInfo> HealthyServices { get; }
List<ServiceInfo> Services { get; }
Task RefreshAsync();
}
除了接口内置的方法,还有几个扩展方法方便用户使用,比如随机一个服务:
public static class DiscoveryServiceExtension
{
public static IEnumerable<ServiceInfo> GetByServiceName(this IDiscoveryService ds, string serviceName)
{
return ds.Services.GetByServiceName(serviceName);
}
public static ServiceInfo GetByServiceId(this IDiscoveryService ds, string serviceId)
{
return ds.Services.GetByServiceId(serviceId);
}
public static ServiceInfo RandomOne(this IDiscoveryService ds, string serviceName)
{
return ds.Services.RandomOne(serviceName);
}
}
至此服务的注册与发现就已经完成了。
一些重要的信息
以上就是服务注册发现的简单使用,但是还有一些比较重要的信息希望大家在使用之前能够了解,这样有利于更好的使用以及出现问题的时候定位问题。
高可用
同 AgileConfig 的配置中心功能一样,服务注册后最后都是写到了数据库里。AgileConfig 的服务端可以部署多个来防止单点故障,同时可以分担压力。所以高可用的最佳实践就是部署 2 个以上的服务端节点,然后数据库做高可用方案。这样足够应付大多数要求不是特别高的场景。
强一致性
同上 AgileConfig 通过数据库保证多个节点部署的时候的一致性问题。
服务的健康检测
服务的健康检测一般有2种方案:
服务端主动询问
客户端主动心跳
AgileConfig 同时支持以上2个方案。AgileConfig client 默认实现了主动心跳。AgileConfig client 的主动心跳有2个渠道:
websockt
长连接,如果AgileConfig client做为配置中心客户端是正常工作的,那么心跳会走websocket通道http
如果 websocket 不可用,那么会直接发起 http 请求做为心跳。
但是对于一些应用主动的心跳并不能代表服务真的是可以用的,因为心跳从服务已启动就会开始,但是某些接口可能还没真正的做好准备被调用。那么这个时候就可以选择服务端主动询问(heartbeat:mode=server)对应的检测接口来确定服务是否真的可用。
AgileConfig 其实还实现了第三种方式:
不检测
如果一个服务你确定它会永远在线,或者是没办法集成 AgileConfig client 的 sdk ,那么你可以标记它为不检测,这样它会一直是健康状态。
服务发现是如何即时更新的
我们的 client 在启动后会拉取一次全量的服务列表。但是服务是会不断的上线,下线的,所以服务状态的更新是需要通知客户端的,然后客户端去拉取新的服务列表。AgileConfig 同样有2个策略来保证服务列表的即时刷新:
当服务状态变化的时候,服务端通过 websocket 即时通知所有的 client 主动刷新配置列表
如果服务端的主动通知由于网络等原因失效的时候,client 会在每次心跳的时候比较本地服务列表 md5 版本跟服务端的列表的 md5 信息,如果不一致,那么 client 会主动拉取一次新的服务列表。
关闭服务注册与发现
删除 serviceRegister
配置节点或不要配置任何信息。
最后
✨✨✨Github地址:https://github.com/dotnetcore/AgileConfig 开源不易,欢迎star✨✨✨
演示地址:http://agileconfig_server.xbaby.xyz/ 超级管理员账号:admin 密码:123456