![0efaff6d4a7c1372fd1aef55c7b9c1e5.png](https://i-blog.csdnimg.cn/blog_migrate/33d0181dfb46cd65d2e180e8b4767881.jpeg)
什么是 Nacos?
阿里出的分布式注册中心。
CAP理论:
- 一致性(Consistency) (所有节点在同一时间具有相同的数据)
- 可用性(Availability) (保证每个请求不管成功或者失败都有响应)
- 分隔容忍--分区容错性(Partition tolerance) (系统中任意信息的丢失或失败不会影响系统的继续运作)
![b3fce5e67f734d7225f4ee57d140d971.png](https://i-blog.csdnimg.cn/blog_migrate/78b9372e378176e84dd80369cd8a00d5.jpeg)
Nacos简介:
服务(Service)是 Nacos 世界的一等公民。Nacos 支持几乎所有主流类型的“服务”的发现、配置和管理:
- Kubernetes Service
- gRPC & Dubbo RPC Service
- Spring Cloud RESTful Service
Nacos 的关键特性包括:
- 服务发现和服务健康监测
- 动态配置服务
- 动态 DNS 服务
- 服务及其元数据管理
![dbb3762ce620dabb41d0a1f04c2f096d.png](https://i-blog.csdnimg.cn/blog_migrate/7928c590a756505a98623763e5b25779.jpeg)
![2e7aef57495ed4632a39b670d578becd.png](https://i-blog.csdnimg.cn/blog_migrate/beeef93cfd0d777e49313146f1653b63.png)
Nacos服务领域模型主要分为命名空间、集群、服务。在下图的分级存储模型可以看到,在服务级别,保存了健康检查开关、元数据、路由机制、保护阈值等设置,而集群保存了健康检查模式、元数据、同步机制等数据,实例保存了该实例的ip、端口、权重、健康检查状态、下线状态、元数据、响应时间。
![337ddce9e2519b91eec200d76b088469.png](https://i-blog.csdnimg.cn/blog_migrate/59375cfc8c1b99099645235e30c8157c.jpeg)
![868a0c34a3bce0ad6b41ee2a6a32c9fd.png](https://i-blog.csdnimg.cn/blog_migrate/87091c206245fdf1bc06863601cafc5d.jpeg)
注册中心原理:
![318f234ba27debadca7509ada7177516.png](https://i-blog.csdnimg.cn/blog_migrate/b335226eff6d9bbc568d4a250f47e6c5.jpeg)
服务注册方法:服务注册的策略的是每5秒向nacos server发送一次心跳,心跳带上了服务名,服务ip,服务端口等信息。同时 nacos server也会向client 主动发起健康检查,支持tcp/http检查。如果15秒内无心跳且健康检查失败则认为实例不健康,如果30秒内健康检查失败则剔除实例。
配置中心原理:
![0833c24c8b76c7ec37bcc859c3cc0aad.png](https://i-blog.csdnimg.cn/blog_migrate/b57c3ba2b0c0e6cd2329f7924dcc0c0c.jpeg)
Nacos使用方法:
- 快速搭建:查看官方文档,路径在最底部 ,下个zip包执行 -。-~~
- 默认控制台地址:http://localhost:8848/nacos/#
- 默认账号/密码 nacos/nacos
创建命名空间:
不同的命名空间逻辑上是隔离的,不特殊设置的情况下,服务不会跨命名空间请求,命名空间主要的作用是区分服务使用的范围,比如开发、测试、生产、灰度可以分别设置四个命名空间来互相隔离。如下图,在控制台的 服务管理-命名空间-新建命名空间按钮可以创建新的命名空间,命名空间创建后,会在列表显示 命名空间ID,这个ID后面会用在服务的配置文件中
![219c823fbd613f75dd57602fa3100e55.png](https://i-blog.csdnimg.cn/blog_migrate/84539b09bd7cb74e81bde8bd128ba951.jpeg)
以springcloud为例,pom导入依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
启动类需要加上@EnableDiscoveryClient注解:
![66f97234fae8e184f1d72b17942d4fff.png](https://i-blog.csdnimg.cn/blog_migrate/72eb6cf0b53752e0b7986217498cf3a8.png)
配置文件bootstrap.yml 注意这里不要配置在application.yml/properties中 有加载顺序原因。
spring:
application:
name: nacos-test
cloud:
nacos:
discovery:
server-addr: localhost:8848
group: test
namespace: 命名空间ID
通过配置更改动态刷新参数:
@RefreshScope
public class TestController {
@Value("${value}")
private String value;
public int getValue(){
return this.value;
}
普通application参数在配置中心直接配置皆可,如果需要可以动态刷新的配置,需要在相应类上加上@RefreshScope
注解。
配置中心参数修改/设置:
在nacos控制台的 配置管理-配置列表中顶部选择相应的命名空间,点击列表右上角的加号新增配置,Data ID 为 项目名-{spring.profiles.active}.properties,Group如果在bootstrap.properties中不指定则填默认的DEFAULT_GROUP,描述写该配置的描述,配置内容填写Properties格式或者Yaml格式。
控制台手动上下线实例:
在控制台的 服务管理-服务列表选择一个服务点击详情,在下方的集群列表可以看到有上线/下线按钮,点击即可以对该实例执行上线/下线操作,下线后的实例不会被请求
配置实例权重:
可以通过手动配置权重来控制流量,当一个集群内两个实例,权重越高,到达该实例的请求比例越多。
配置保护阈值:
保护阈值的范围是0~1
服务的健康比例=服务的健康实例/总实例个数
当服务健康比例<=保护阈值时候,无论实例健不健康都会返回给调用方
当服务健康比例>保护阈值的时候,只会返回健康实例给调用方
在 服务管理-服务列表选择一个服务点击详情可以配置
简单搭建好了,看看服务注册源码(版本号1.3.2):
Nacos Client端:
定位到package com.alibaba.nacos.client.naming包中NacosNamingService的registerInstance
方法最底层方法实现:
@Override
public void registerInstance(String serviceName, String groupName, String ip, int port, String clusterName)
throws NacosException {
Instance instance = new Instance();
instance.setIp(ip);
instance.setPort(port);
instance.setWeight(1.0);
instance.setClusterName(clusterName);
registerInstance(serviceName, groupName, instance);
}
这里的registerInstance
中:将ip和port放入这个Instance
对象就是一个实例,,而serviceName
以及groupName
参数是将实例注册到Nacos
中的分组名为groupName
服务名为serviceName
的服务中
@Override
public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName);
if (instance.isEphemeral()) {
BeatInfo beatInfo = beatReactor.buildBeatInfo(groupedServiceName, instance);
beatReactor.addBeatInfo(groupedServiceName, beatInfo);
}
serverProxy.registerService(groupedServiceName, groupName, instance);
}
这里出现了BeatInfo
对象,这个BeatInfo
对象是Nacos
用于心跳任务的心跳信息,首先要对实例进行判断是否是一个临时标签的实例对象,如果是的话需要为该Instance
设置一个心跳任务信息,用于Client
主动上报自己的健康状态信息。那为什么只有临时的实例才需要设置心跳任务信息呢?这里引用官方博客的解释:
如果是临时实例,则不会在Nacos服务端持久化存储,需要通过上报心跳的方式进行保活,如果一段时间内没有上报心跳,则会被Nacos服务端摘除。在被摘除后如果又开始上报心跳,则会重新将这个实例注册。持久化实例则会持久化到Nacos服务端,此时即使注册实例的客户端进程不在,这个实例也不会从服务端删除,只会将健康状态设为不健康
由于临时的实例不会在Nacos
服务中心进行持久化存储,因此才需要一个心跳任务,将实例的信息放在心跳任务中不断的向Nacos
服务中心上报。接着就是向Nacos Server
段发送注册实例的Http
请求了,在package http://com.alibaba.nacos.client.naming.net中的NamingProxy类registerService方法:
public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance: {}", namespaceId, serviceName,
instance);
final Map<String, String> params = new HashMap<String, String>(16);
params.put(CommonParams.NAMESPACE_ID, namespaceId);
params.put(CommonParams.SERVICE_NAME, serviceName);
params.put(CommonParams.GROUP_NAME, groupName);
params.put(CommonParams.CLUSTER_NAME, instance.getClusterName());
params.put("ip", instance.getIp());
params.put("port", String.valueOf(instance.getPort()));
params.put("weight", String.valueOf(instance.getWeight()));
params.put("enable", String.valueOf(instance.isEnabled()));
params.put("healthy", String.valueOf(instance.isHealthy()));
params.put("ephemeral", String.valueOf(instance.isEphemeral()));
params.put("metadata", JacksonUtils.toJson(instance.getMetadata()));
reqApi(UtilAndComs.nacosUrlInstance, params, HttpMethod.POST);
}
这个registerService
就是将Instance
实例的信息封装成Http
的请求进行发送到Nacos Server
端进行注册,下面就是真正注册的方法了。
public String reqApi(String api, Map<String, String> params, Map<String, String> body, List<String> servers,
String method) throws NacosException {
params.put(CommonParams.NAMESPACE_ID, getNamespaceId());
if (CollectionUtils.isEmpty(servers) && StringUtils.isEmpty(nacosDomain)) {
throw new NacosException(NacosException.INVALID_PARAM, "no server available");
}
NacosException exception = new NacosException();
if (servers != null && !servers.isEmpty()) {
Random random = new Random(System.currentTimeMillis());
int index = random.nextInt(servers.size());
for (int i = 0; i < servers.size(); i++) {
String server = servers.get(index);
try {
return callServer(api, params, body, server, method);
} catch (NacosException e) {
exception = e;
if (NAMING_LOGGER.isDebugEnabled()) {
NAMING_LOGGER.debug("request {} failed.", server, e);
}
}
index = (index + 1) % servers.size();
}
}
if (StringUtils.isNotBlank(nacosDomain)) {
for (int i = 0; i < UtilAndComs.REQUEST_DOMAIN_RETRY_COUNT; i++) {
try {
return callServer(api, params, body, nacosDomain, method);
} catch (NacosException e) {
exception = e;
if (NAMING_LOGGER.isDebugEnabled()) {
NAMING_LOGGER.debug("request {} failed.", nacosDomain, e);
}
}
}
}
NAMING_LOGGER.error("request: {} failed, servers: {}, code: {}, msg: {}", api, servers, exception.getErrCode(),
exception.getErrMsg());
throw new NacosException(exception.getErrCode(),
"failed to req API:" + api + " after all servers(" + servers + ") tried: " + exception.getMessage());
}
可以看到如果Nacos Server
端的地址列表为空,那么Nacos Server
应该是单机模式部署的,因此直接到最后一个for循环,循环次数为默认设置的Http
请求可重试次数;如果Nacos Serve
是已集群模式部署的话,那么会采用随机策略选择一个Nacos Server Addr
作为进行Instance
注册的Http
请求地址;如果请求失败的话则再次重新选取一个Nacos Server
Nacos Server端:
Nacos Server
端负责处理Nacos Client
的实例注册的Controller
在com.alibaba.nacos.naming.controllers
里面的InstanceController
,可以找到处理Instance
注册的控制器
@CanDistro
@PostMapping
@Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE)
public String register(HttpServletRequest request) throws Exception {
final String namespaceId = WebUtils
.optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);
final String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);
checkServiceNameFormat(serviceName);
final Instance instance = parseInstance(request);
serviceManager.registerInstance(namespaceId, serviceName, instance);
return "ok";
}
com.alibaba.nacos.naming.core包中ServiceManager类的registerInstance方法:
public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {
createEmptyService(namespaceId, serviceName, instance.isEphemeral());
Service service = getService(namespaceId, serviceName);
if (service == null) {
throw new NacosException(NacosException.INVALID_PARAM,
"service not found, namespace: " + namespaceId + ", service: " + serviceName);
}
addInstance(namespaceId, serviceName, instance.isEphemeral(), instance);
}
Nacos
的Model
是service->cluster->instance
这样的,因此在注册实例时,需要先注册一个Service
,具体的创建在createEmptyService
方法,该方法底层为createServiceIfAbsent方法:
public void createServiceIfAbsent(String namespaceId, String serviceName, boolean local, Cluster cluster)
throws NacosException {
Service service = getService(namespaceId, serviceName);
if (service == null) {
Loggers.SRV_LOG.info("creating empty service {}:{}", namespaceId, serviceName);
service = new Service();
service.setName(serviceName);
service.setNamespaceId(namespaceId);
service.setGroupName(NamingUtils.getGroupName(serviceName));
// now validate the service. if failed, exception will be thrown
service.setLastModifiedMillis(System.currentTimeMillis());
service.recalculateChecksum();
if (cluster != null) {
cluster.setService(service);
service.getClusterMap().put(cluster.getName(), cluster);
}
service.validate();
putServiceAndInit(service);
if (!local) {
addOrReplaceService(service);
}
}
}
这里会先根据namespaceId
以及serviceName
去获取一个Service
对象,如果service==null
,则代表该Service
未在Nacos Server
端注册,因此需要先注册一个Service
,才可以进行接下来的注册Instance
;同时根据实例是否是临时的标识决定该实例是否需要持久化到Nacos Server
中,如果是一个临时的Instance
,则Nacos Server
会为其设置一致性consistencyService
的listener
,同时Service
开启一个ClientBeatCheckTask
,用于检查该Service
下的Instance
实例的健康状态。
Service
创建之后就是注册Instance
了
public void addInstance(String namespaceId, String serviceName, boolean ephemeral, Instance... ips)
throws NacosException {
String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, ephemeral);
Service service = getService(namespaceId, serviceName);
synchronized (service) {
List<Instance> instanceList = addIpAddresses(service, ephemeral, ips);
Instances instances = new Instances();
instances.setInstanceList(instanceList);
consistencyService.put(key, instances);
}
}
先读到这里了
参考资料:
homenacos.io![0c82e0f229e4fa220ecccded420455c6.png](https://i-blog.csdnimg.cn/blog_migrate/d5ed2c833e85bcd5fbaec1fb01371450.png)