Nacos,英文全称Dynamic Naming and Configuration Service,Na为naming命名,co为configuration配置,s为服务,动态命名与配置服务。
Nacos主旨是一个易于构建云原生应用的动态服务发现、配置管理和服务管理平台。它包含了注册中心、配置中心、健康检查、动态DNS、服务元数据和流量管理等功能。
直接看一张Nacos的架构图:
从图可以大概知道,
①服务提供者通过Nacos客户端向Nacos注册信息,消费者通过Nacos客户端从Nacos拉取信息
②Nacos客户端与Nacos服务通过OpenAPI交互,根据功能需要使用了http/dns/udp/tls协议
③包含配置服务和命名服务(指通过指定的名字来获取资源、服务的地址或提供者的信息)
④支持集群
⑤一致性协议包括priv-raft/sync renew/rdbms based
⑥提供管理控制台
大概对比一下同类型的组件
Zookeeper | Eureka | Consul | Nacos | |
一致性协议 | CP模型,ZAB算法, 数据强一致性 |
AP模型,数据最终一致性 | AP模型,Raft+Gossip算法, 数据最终一致性 |
AP + CP + 模型 |
健康检查 | Keep Alive | Client Beat | TCP/HTTP/gRPC/Cmd | TCP/HTTP/MYSQL/Client Beat |
负载均衡策略 | 不支持 | Ribbon | Fabio | 权重/metadata/Selector |
雪崩保护 | 不支持 | 支持 | 不支持 | 支持 |
自动注销实例 | 支持 | 支持 | 不支持 | 支持 |
访问协议 | TCP | HTTP | HTTP/DNS | HTTP/DNS |
分组 | 不支持 | 不支持 | 不支持 | 可根据环境和业务分组 |
sh bin/startup.sh -m standalone
sh bin/startup.sh -m cluster
spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://192.168.2.183:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
db.user=iplastest
db.password=123456
这样的话就可以使用自己的mysql数据库了。
Nacos分为服务端和客户端,服务端采用Java编写,为客户端提供注册中心和配置中心。nacos提供了openApi和多种语言的sdk(底层依然是调用openApi),有了基于http的openApi则可以兼容任何语言,从而使用nacos进行服务注册,配置存储。
特别说明:
①命名空间namespace 用于环境资源隔离,如区分生产和开发环境,需在控制台上新建命名空间,然后拿到对应的id
properties.setProperty("namespace", "a5d13b9e-58bc-4be6-ad0e-c85f1e63d552");
②data id 通常用于区分项目名或文件名,如order-service.properties、mysql.yml、redis.yml、es.properties
③group 通常用于区分业务配置,如Buy 或 Trade
④content为保存的配置内容
boolean publishConfig(String dataId, String group, String content) throws NacosException;
特别说明:
①获取健康实例接口使用到了随机策略 + 权重的负载均衡
②控制台允许设置服务流量权重,想增大流量则可以把实例的权重配置大一点,不接受流量把权重设置为0
③控制台可以对服务实例的元数据进行管理,格式为json,可以存储一些自定义的配置
④控制台可以实现服务优雅上线下线
依赖:
com.alibaba.nacos
nacos-spring-context
0.3.1
代码:(虽然用了springboot项目,但是没有使用starter,用的还是spring注解方式)
@SpringBootApplication
@RestController
@NacosPropertySource(dataId = "nacos-test", autoRefreshed = true) // 加载配置源,dataId为读取的配置id
@EnableNacos(
globalProperties =
@NacosProperties(serverAddr = "${nacos.server-addr:localhost:8848}")
)
public class SingleNacosApplication {
/**
* 发布配置
* */
@NacosInjected
private ConfigService configService;
@GetMapping("/publishConfig")
public String publishConfig(@RequestParam String content) throws NacosException {
boolean result = configService.publishConfig("nacos-test", "DEFAULT_GROUP", content);
if (result) {
return "OK";
}
return "FAIL";
}
/**
* 读取配置
* */
@GetMapping("/getConfig")
public String getConfig() throws NacosException {
return configService.getConfig("nacos-test", "DEFAULT_GROUP", 2000);
}
/**
* 另外一种读取配置
* */
@NacosValue(value = "${test.properties.defaultName:无名氏}", autoRefreshed = true)
private String defaultName;
@GetMapping("/hi")
public String hi() {
return "hi " + defaultName;
}
@NacosInjected
private NamingService namingService;
// @PostConstruct
// public void registerInstance() throws NacosException {
// namingService.registerInstance("test-service", "192.168.0.101", 8888);
// }
/**
* 服务注册
* */
@GetMapping("/setInstance")
public String setInstance() throws NacosException {
namingService.registerInstance("test-service", "192.168.0.101", 8888);
namingService.registerInstance("test-service", "192.168.0.102", 8888);
namingService.registerInstance("test-service", "192.168.0.103", 8888);
namingService.registerInstance("test-service", "192.168.0.104", 8888);
return "ok";
}
/**
* 服务发现
* */
@GetMapping("/getInstance")
public List getInstance() throws NacosException {
return namingService.getAllInstances("test-service");
}
/**
* 获取一个健康实例
* */
@GetMapping("/getOneInstance")
public Instance getOneInstance() throws NacosException {
return namingService.selectOneHealthyInstance("test-service");
}
public static void main(String[] args) {
SpringApplication.run(SingleNacosApplication.class, args);
}
}
curl -X POST 'http://127.0.0.1:8848/nacos/v1/ns/instance?serviceName=order-services&ip=66.66.66.66&port=8080'
curl -X GET 'http://127.0.0.1:8848/nacos/v1/ns/instances?serviceName=order-services'
curl -X POST "http://127.0.0.1:8848/nacos/v1/cs/configs?dataId=test&group=test&content=REDIS_PORT=6379"
curl -X GET "http://127.0.0.1:8848/nacos/v1/cs/configs?dataId=test&group=test"
Nacos 的配置信息保存到服务端的数据库中,客户端连接到服务端之后,根据 namespace (不填默认是public),dataID,group可以获取到具体的配置信息,当服务端的配置发生变更时,客户端订阅会收到通知。
客户端是通过一个长连接来监听的配置项的数据,Nacos服务端则把订阅轮询存放在一个allSubs队列中,
当用户修改配置时,将配置信息入库并执行通知事件方法。
事件方法会从队列中取出相关长轮询订阅的客户端,然后通知客户端配置变更。
客户端收到服务端的配置变更消息,客户端将会获取到最新的数据,然后计算数据内容的MD5,跟cache中的MD5进行比较,如果不同则会重新保存配置信息到磁盘。
此时还会对绑定的 Listener 触发回调。
一致性方案业界只有两种,一种是基于 Leader 的非对等部署的单点写一致性,一种是对等部署的多写一致性。
Nacos支持三种模式:AP、CP和 MIXED 。
这里的模式,指的是是CAP理论里的C、A和P概念。MIXED 代表AP + CP并存。
C:Consistency数据一致性,即所有分布式节点在同一时间看到的数据是一致的。
A:Availability服务可用性,即任何时间集群都能响应客户端的请求。
P:Partition tolerance分区容错性,任何分区发生故障时,仍然能够对外提供满足一致性和可用性的服务,除非整个网络环境都发生了故障。
CAP原则是三者只能选其二,在分布式系统中没有一种设计可以同时满足CAP三个特性。
Nacos的一致性协议实现,一个是基于简化的 Raft 的 CP 一致性,一个是基于自研协议 Distro 的 AP 一致性。Raft 协议是基于 Leader 进行写入,其 CP 也并不是严格的,只是能保证一半所见一致,以及数据的丢失概率较小。Distro 协议则是参考开源 Eureka,各个节点都是平等的,在网络稳定时会自动同步到其他节点,弱一致性。
何时选择使用何种模式?一般来说,如果需要在服务级别编辑或者存储配置信息,那么 CP 是必须要使用的模式,如果不需要存储服务级别的信息,且服务实例是通过nacos-client
注册,并能够保持心跳上报,那么就可以选择AP模式。
使用如下请求进行Server运行模式的设定:
curl -X PUT
'$NACOS_SERVER:8848/nacos/v1/ns/operator/switches?entry=serverMode&value=CP'
客户端调用naming.registerInstance进行服务注册时,会同时创建发送心跳信息的定时任务,不断请求服务端/instance/beat接口
最终服务端的接口/instance/beat里面会更新该实例的最后心跳时间
另外一边,服务端有个定时任务ClientBeatCheckTask每5秒会检查一次
检查最近心跳时间是否超过默认15秒超时时间,超时则把实例设置为不健康状态
如果最近心跳时间超过默认30秒超时时间,则会删除该实例
(1)从功能上看,Nacos自带服务优雅上下线、负载均衡和流量管理(API+后台管理页面),具有分组隔离功能,一套Nacos集群可以支撑多项目、多环境。
(2)从部署来看,Nacos整合了注册中心、配置中心功能,把原来两套集群整合成一套,简化了部署维护。
(3)从伸缩性和扩展性来看,Nacos支持跨注册中心同步,而Eureka和Zookeeper都不支持,且在伸缩扩容方面,Nacos比Eureka、Zookeeper更优(nacos支持大数量级的集群)。
(5)从性能上看,配置中心读写性能方面,Nacos的tps比apollo更强一些。
Nacos刚刚开源不久,由国产大佬牵头,目前在功能上看,算是一个集大成者。刚好遇上Eureka2.0闭源,社区逐渐火热,以后也会增加更多的新特性,成为主流指日可待。
参考:
Nacos仓库:https://github.com/nacos-group
主流微服务注册中心浅析和对比:https://www.jianshu.com/p/d1b5c8b76194
Nacos中文文档:https://nacos.io/zh-cn/docs/what-is-nacos.html
Nacos 1.0.0 功能预览:https://www.jianshu.com/p/7342f3a63094