简介:了解了 Nacos 的服务模型,也有利于咱们理解 Nacos 背地的工作原理,从而确保咱们正确地应用 Nacos。
作者:岛风
前言
依照目前市场上的支流应用场景,Nacos 被分成了两块性能:服务注册发现(Naming)和配置核心(Config)。在之前的文章中我介绍了 Nacos 配置核心的实现原理,明天这篇文章所介绍的内容则是与 Nacos 服务注册发现性能相干,来聊一聊 Nacos 的服务模型。
说到服务模型,其实须要辨别视角,一是用户视角,一个内核视角。即 Nacos 用户视角看到的服务模型和 Nacos 开发者设计的内核模型可能是齐全不一样的,而明天的文章,是站在用户视角察看的,旨在探讨 Nacos 服务发现的最佳实际。
服务模型介绍
个别我在聊注册核心时,都会以 Zookeeper 为引子,这也是很多人最相熟的注册核心。但如果你真的写过或看过应用 Zookeeper 作为注册核心的适配代码,会发现并不是那么容易,再加上注册核心波及到的一致性原理,这就导致很多人对注册核心的第一印象是:这个货色好难!但归根到底是因为 Zookeeper 基本不是专门为注册核心而设计的,其提供的 API 以及内核设计,并没有预留出「服务模型」的概念,这就使得开发者须要自行设计一个模型,去填补 Zookeeper 和服务发现之间的鸿沟。
微服务架构逐步深入人心后,Nacos、Consul、Eureka 等注册核心组件进入公众的眼帘。能够发现,这些”真正“的注册核心都有各自的「服务模型」,在应用上也更加的不便。
为什么要有「服务模型」?实践上,一个根底组件能够被塑造成任意的模样,如果你违心,一个数据库也能够被设计成注册核心,这并不是”夸大“的修辞手法,在阿里还真有人这么干过。那么代价是什么呢?肯定会在业务倒退到肯定体量后遇到瓶颈,肯定会遇到某些极其 case 导致其无奈失常工作,肯定会导致其扩展性低下。正如刚学习数据结构时,同学们常见的一个疑难一样:为什么栈只能先进后出。不是所有开发都是中间件专家,所以 Nacos 设计了本人的「服务模型」,这尽管限度了使用者的”想象力“,但保障了使用者在正确地应用 Nacos。
花了肯定的篇幅介绍 Nacos 为什么须要设计「服务模型」,再来看看理论的 Nacos 模型是个啥,其实没那么玄乎,一张图就能表白分明:
与 Consul、Eureka 设计有别,Nacos 服务发现应用的畛域模型是命名空间 - 分组 - 服务 - 集群 - 实例这样的多层构造。服务 Service 和实例 Instance 是外围模型,命名空间 Namespace、分组 Group、集群 Cluster 则是在不同粒度实现了服务的隔离。
为了更好的了解两个外围模型:Service 和 Instance,咱们以 Dubbo 和 SpringCloud 这两个曾经适配了 Nacos 注册核心的微服务框架为例,介绍下二者是如何映射对应模型的。
• Dubbo。将接口三元组(接口名 + 分组名 + 版本号)映射为 Service,将实例 IP 和端口号定义为 Instance。一个典型的注册在 Nacos 中的 Dubbo 服务:providers:com.alibaba.mse.EchoService:1.0.0:DUBBO
• Spring Cloud。将利用名映射为 Service,将实例 IP 和端口号定义为 Instance。一个典型的注册在 Nacos 中的 Spring Cloud 服务:helloApp
上面咱们将会更加具体地阐释 Nacos 提供的 API 和服务模型之间的关系。
环境筹备
须要部署一个 Nacos Server 用于测试,我这里抉择间接在 https://mse.console.aliyun.com/ 购买一个 MSE 托管的 Nacos,读者们能够抉择购买 MSE Nacos 或者自行搭建一个 Nacos Server。
MSE Nacos 提供的可视化控制台,也能够帮忙咱们更好的了解 Nacos 的服务模型。下文的一些截图,均来自 MSE Nacos 的商业化控制台。
疾速开始
先来实现一个最简略的服务注册与发现 demo。Nacos 反对从客户端注册服务实例和订阅服务,具体代码如下:
Properties properties = new Properties();
properties.setProperty(PropertyKeyConst.SERVER_ADDR, “mse-xxxx-p.nacos-ans.mse.aliyuncs.com:8848”);
String serviceName = “nacos.test.service.1”;
String instanceIp = InetAddress.getLocalHost().getHostAddress();
int instancePort = 8080;
namingService.registerInstance(serviceName, instanceIp, instancePort);
System.out.println(namingService.getAllInstances(serviceName));
上述代码定义了一个 service:nacos.test.service.1;定义了一个 instance,以本机 host 为 IP 和 8080 为端口号,察看理论的注册状况:
并且控制台也打印出了服务的详情。至此一个最简略的 Nacos 服务发现 demo 就曾经实现了。对一些细节稍作解释:
• 属性 PropertyKeyConst.SERVER_ADDR 示意的是 Nacos 服务端的地址。
• 创立一个 NamingService 实例,客户端将为该实例创立独自的资源空间,包含缓存、线程池以及配置等。Nacos 客户端没有对该实例做单例的限度,请小心保护这个实例,以防新建多于预期的实例。
• 注册服务 registerInstance 应用了最简略的重载办法,只须要传入服务名、IP、端口就能够。
上述的例子中,并没有呈现 Namespace、Group、Cluster 等前文提及的服务模型,我会在上面一节具体介绍,这个例子次要是为了演示 Nacos 反对的一些缺省配置,其中 Service 和 Instance 是必不可少的,这也验证了前文提到的服务和实例是 Nacos 的一等公民。
通过截图咱们能够发现缺省配置的默认值:
• Namespace:默认值是 public 或者空字符串,都能够代表默认命名空间。
• Group:默认值是 DEFAULT_GROUP。
• Cluster:默认值是 DEFAULT。
构建自定义实例
为了展现出 Nacos 服务模型的全貌,还须要介绍下实例相干的 API。例如咱们心愿注册的实例中,有一些可能被调配更多的流量;或者可能传入一些实例的元信息存储到 Nacos 服务端,例如 IP 所属的利用或者所在的机房,这样在客户端能够依据服务下挂载的实例元信息,来自定义负载平衡模式。Nacos 也提供了另外的注册实例接口,使得用户在注册实例时能够指定实例的属性:
/**
- register a instance to service with specified instance properties.
* - @param serviceName name of service
- @param groupName group of service
- @param instance instance to register
- @throws NacosException nacos exception
*/
void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException;
这个办法在注册实例时,能够传入一个 Instance 实例,它的属性如下:
public class Instance {
/**
* unique id of this instance.
*/
private String instanceId;
/**
* instance ip.
*/
private String ip;
/**
* instance port.
*/
private int port;
/**
* instance weight.
*/
private double weight = 1.0D;
/**
* instance health status.
*/
private boolean healthy = true;
/**
* If instance is enabled to accept request.
*/
private boolean enabled = true;
/**
* If instance is ephemeral.
*
* @since 1.0.0
*/
private boolean ephemeral = true;
/**
* cluster information of instance.
*/
private String clusterName;
/**
* Service information of instance.
*/
private String serviceName;
/**
* user extended attributes.
*/
private Map<String, String> metadata = new HashMap<String, String>();
}
有一些字段能够顾名思义,有一些则须要花些功夫专门去理解 Nacos 的设计,我这里筛选几个我认为重要的属性重点介绍下:
• healthy 实例衰弱状态。标识该实例是否衰弱,个别心跳健康检查会自动更新该字段。
• enable 是否启用。它跟 healthy 区别在于,healthy 个别是由内核健康检查更新,而 enable 更多是业务语义偏多,能够齐全依据业务场景操控。例如在 Dubbo 中,个别应用该字段标识某个实例 IP 的高低线状态。
• ephemeral 长期实例还是长久化实例。十分要害的一个字段,须要对 Nacos 有较为深刻的理解才可能了解该字段的含意。区别在于,心跳检测失败肯定工夫之后,实例是主动下线还是标记为不衰弱。个别在注册核心场景下,会应用长期实例。这样心跳检测失败之后,能够让消费者及时收到下线告诉;而在 DNS 模式下,应用长久化实例较多。在《一文详解 Nacos 高可用个性》中我也介绍过,该字段还会影响到 Nacos 的一致性协定。
• metadata 元数据。一个 map 构造,能够存储实例的自定义扩大信息,例如机房信息,路由标签,利用信息,权重信息等。
这些信息在由服务提供者上报之后,由服务消费者获取,从而实现信息的传递。以下是一个残缺的实例注册演示代码:
Properties properties = new Properties();
// 指定 Nacos Server 地址
properties.setProperty(PropertyKeyConst.SERVER_ADDR, “mse-xxxx-p.nacos-ans.mse.aliyuncs.com:8848”);
// 指定命名空间
properties.setProperty(PropertyKeyConst.NAMESPACE, “9125571e-bf50-4260-9be5-18a3b2e3605b”);
NamingService namingService = NacosFactory.createNamingService(properties);
String serviceName = “nacos.test.service.1”;
String group = “DEFAULT_GROUP”;
String clusterName = “cn-hangzhou”;
String instanceIp = InetAddress.getLocalHost().getHostAddress();
int instancePort = 8080;
Instance instance = new Instance();
// 指定集群名
instance.setClusterName(clusterName);
instance.setIp(instanceIp);
instance.setPort(instancePort);
// 指定实例的元数据
Map<String, String> metadata = new HashMap<>();
metadata.put(“app”, “nacos-demo”);
metadata.put(“site”, “cn-hangzhou”);
metadata.put(“protocol”, “1.3.3”);
instance.setMetadata(metadata);
// 指定服务名、分组和实例
namingService.registerInstance(serviceName, group, instance);
System.out.println(namingService.getAllInstances(serviceName));
构建自定义服务
除了实例之外,服务也能够自定义配置,Nacos 的服务随着实例的注册而存在,并随着所有实例的登记而沦亡。不过目前 Nacos 对于自定义服务的反对不是很敌对,除应用 OpenApi 能够批改服务的属性外,就只能应用注册实例时传入的服务属性来进行自定义配置。所以在理论的 Dubbo 和 SpringCloud 中,自定义服务个别较少应用,而自定义实例信息则绝对罕用。
Nacos 的服务与 Consul、Eureka 的模型都不同,Consul 与 Eureka 的服务等同于 Nacos 的实例,每个实例有一个服务名属性,服务自身并不是一个独自的模型。Nacos 的设计在我看来更为正当,其认为服务自身也是具备数据存储需要的,例如作用于服务下所有实例的配置、权限管制等。实例的属性该当继承自服务的属性,实例级别能够笼罩服务级别。以下是服务的数据结构:
/**
* Service name
*/
private String name;
/**
* Protect threshold
*/
private float protectThreshold = 0.0F;
/**
* Application name of this service
*/
private String app;
/**
* Service group which is meant to classify services into different sets.
*/
private String group;
/**
* Health check mode.
*/
private String healthCheckMode;
private Map<String, String> metadata = new HashMap<String, String>();
在理论应用过程中,能够像疾速开始章节中介绍的那样,仅仅应用 ServiceName 标记一个服务。
服务隔离:Namespace&Group&Cluster
出于篇幅思考,这三个概念放到一起介绍。
襄王无意,神女无心。Nacos 提出了这几种隔离策略,目前看来只有 Namespace 在理论利用中应用较多,而 Group 和 Cluster 并没有被当回事。
Cluster 集群隔离在阿里巴巴外部应用的十分广泛。一个典型的场景是这个服务下的实例,须要配置多种健康检查形式,有一些实例应用 TCP 的健康检查形式,另外一些应用 HTTP 的健康检查形式。另一个场景是,服务下挂载的机器分属不同的环境,心愿可能在某些状况下将某个环境的流量全副切走,这样能够通过集群隔离,来做到一次性切流。在 Nacos 2.0 中,也在无意的弱化集群的概念,毕竟开源还是要面向用户的,有些货色适宜阿里,但不肯定适宜开源,等再往后演进,集群这个概念又有可能从新回到大家的眼帘中了,history will repeat itself。
Group 分组隔离的概念能够参考 Dubbo 的服务隔离策略,其也有一个分组。反对分组的扩大,用意当然是好的,理论应用上,也确实有一些公司会习惯应用分组来进行隔离。须要留神的一点是:Dubbo 注册三元组(接口名 + 分组 + 版本)时,其中 Dubbo 的分组是蕴含在 Nacos 的服务名中的,并不是映射成了 Nacos 的分组,个别 Nacos 注册的服务是默认注册到 DEFAULT_GROUP 分组的。
Namespace 命名空间隔离,我认为是 Nacos 一个比拟好的设计。在理论场景中应用也比拟广泛,个别用于多个环境的隔离,例如 daily,dev,test,uat,prod 等环境的隔离。特地是当环境十分多时,应用命名空间做逻辑隔离是一个比拟节约老本的策略。但强烈建议大家仅仅在非线上环境应用 Namespace 进行隔离,例如多套测试环境能够共享一套 Nacos,而线上环境独自搭建另一套 Nacos 集群,免得线下测试流量烦扰到线上环境。
服务发现:推拉模型
下面介绍完了 Nacos 服务发现的 5 大畛域模型,最初一节,介绍下如何获取服务模型。
Nacos 的服务发现,有被动拉取和推送两种模式,这与个别的服务发现架构雷同。以下是拉模型的相干接口:
/**
- Get all instances of a service
* - @param serviceName name of service
- @return A list of instance
- @throws NacosException
*/
List<Instance> getAllInstances(String serviceName) throws NacosException;
/**
- Get qualified instances of service
* - @param serviceName name of service
- @param healthy a flag to indicate returning healthy or unhealthy instances
- @return A qualified list of instance
- @throws NacosException
*/
List<Instance> selectInstances(String serviceName, boolean healthy) throws NacosException;
/**
- Select one healthy instance of service using predefined load balance strategy
* - @param serviceName name of service
- @return qualified instance
- @throws NacosException
*/
Instance selectOneHealthyInstance(String serviceName) throws NacosException;
Nacos 提供了三个同步拉取服务的办法,一个是查问所有注册的实例,一个是只查问衰弱且上线的实例,还有一个是获取一个衰弱且上线的实例。个别状况下,订阅端并不关怀不衰弱的实例或者权重设为 0 的实例,然而也不排除一些场景下,有一些运维或者治理的场景须要拿到所有的实例。仔细的读者会留神到上述 Nacos 实例中有一个 weight 字段,便是作用在此处的 selectOneHealthyInstance 接口上,依照权重返回一个衰弱的实例。集体认为这个性能绝对鸡肋,个别的 RPC 框架都有本身配套的负载平衡策略,很少会由注册核心 cover,事实上 Dubbo 和 Spring Cloud 都没有用到 Nacos 的这个接口。
除了被动查问实例列表,Nacos 还提供订阅模式来感知服务下实例列表的变动,包含服务配置或者实例配置的变动。能够应用上面的接口来进行订阅或者勾销订阅:
/**
- Subscribe service to receive events of instances alteration
* - @param serviceName name of service
- @param listener event listener
- @throws NacosException
*/
void subscribe(String serviceName, EventListener listener) throws NacosException;
/**
- Unsubscribe event listener of service
* - @param serviceName name of service
- @param listener event listener
- @throws NacosException
*/
void unsubscribe(String serviceName, EventListener listener) throws NacosException;
在理论的服务发现中,订阅接口尤为重要。消费者启动时,个别会同步获取一次服务信息用于初始化,紧接着订阅服务,这样当服务产生高低线时,就能够感知变动了,从而实现服务发现。
总结
Nacos 为了更好的实现服务发现,提供一套成熟的服务模型,其中重点须要关注的是 Namespace、Service 和 Instance,得益于这一套服务模型的形象,以及对推拉模型的反对,Nacos 能够疾速被微服务框架集成。
了解了 Nacos 的服务模型,也有利于咱们理解 Nacos 背地的工作原理,从而确保咱们正确地应用 Nacos。但 Nacos 提供的这些模型也不肯定所有都须要用上,例如集群、分组、权重等概念,被实践证明是绝对鸡肋的设计,在应用时,也须要依据本身业务特点去评估个性用量,不要自觉地为了应用技术而去用。
原文链接
本文为阿里云原创内容,未经容许不得转载。