Nacos 服务发现与配置中心原理:AP 架构与 Distro 协议
·
Nacos 服务发现与配置中心原理:AP 架构与 Distro 协议
摘要
Nacos 作为阿里巴巴开源的云原生服务发现与配置中心,同时支持 AP 和 CP 两种一致性架构。本文深入剖析 Nacos 2.2.0 的 AP 架构设计及 Distro 协议原理,通过源码分析和实战示例,揭示其如何通过"最终一致性"和"自举机制"实现高可用的服务注册中心,在性能与一致性之间找到最佳平衡点。
一、服务注册中心的挑战与 Nacos 架构
1.1 CAP 理论与注册中心选型
在分布式系统中,Consistency(一致性)、Availability(可用性)、Partition Tolerance(分区容错性)三者只能同时满足两个。
渲染错误: Mermaid 渲染失败: Parse error on line 11: ...] A3[Consul (默认)] ----------------------^ Expecting 'SQE', 'DOUBLECIRCLEEND', 'PE', '-)', 'STADIUMEND', 'SUBROUTINEEND', 'PIPE', 'CYLINDEREND', 'DIAMOND_STOP', 'TAGEND', 'TRAPEND', 'INVTRAPEND', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'PS'
1.2 Nacos 整体架构
1.3 AP vs CP 架构对比
| 特性 | AP 架构 (Distro) | CP 架构 (Raft) |
|---|---|---|
| 一致性 | 最终一致 | 强一致 |
| 可用性 | 高(部分节点故障不影响) | 中(Leader 故障需选举) |
| 性能 | 高(内存存储,异步同步) | 中(磁盘写入,同步复制) |
| 适用场景 | 服务注册与发现(非核心配置) | 配置管理、元数据 |
| 数据存储 | 内存 | 磁盘 + 内存 |
二、Distro 协议核心原理
2.1 Distro 设计理念
Distro 是 Nacos 自研的最终一致性协议,专为 AP 场景设计:
- 自举机制:每个节点平等,无 Leader 角色
- 增量同步:仅同步变更数据,减少网络开销
- 故障自愈:节点故障后自动从其他节点恢复数据
- 心跳续约:客户端定期续约,服务端定期清理过期实例
2.2 Distro 数据同步流程
三、源码分析:AP 架构实现
3.1 服务注册入口
源码位置:nacos-src/naming/src/main/java/com/alibaba/nacos/naming/
// naming/src/main/java/com/alibaba/nacos/naming/core/InstanceController.java
@RestController
@RequestMapping("/v1/ns")
public class InstanceController {
@CanDistro
@PostMapping("/instance")
public String register(HttpServletRequest request) throws Exception {
// 1. 解析请求参数
String serviceName = WebUtils.required(request, "serviceName");
String ip = WebUtils.required(request, "ip");
int port = Integer.parseInt(WebUtils.required(request, "port"));
// 2. 构建 Instance 对象
Instance instance = new Instance();
instance.setIp(ip);
instance.setPort(port);
instance.setWeight(Double.parseDouble(WebUtils.optional(request, "weight", "1.0")));
instance.setHealthy(true);
instance.setEphemeral(true); // 临时实例
// 3. 注册服务
getInstanceOperator().registerInstance(serviceName, "DEFAULT_GROUP", instance);
return "ok";
}
}
// naming/src/main/java/com/alibaba/nacos/naming/core/InstanceOperatorServiceImpl.java
public class InstanceOperatorServiceImpl implements InstanceOperator {
@Override
public void registerInstance(String serviceName, String groupName, Instance instance) {
// 1. 构建完整服务名
String fullServiceName = groupName + Constants.SERVICE_INFO_SPLITER + serviceName;
// 2. 写入 AP 存储(Distro 协议)
DistroConsistencyServiceImpl distroConsistencyService =
(DistroConsistencyServiceImpl) consistencyService;
// 3. 注册实例(写入内存)
distroConsistencyService.put(fullServiceName, instance);
// 4. 广播到其他节点
distroProtocol.sync(new DistroKey(fullServiceName, instance.getIp() + ":" + instance.getPort()),
DataOperation.CHANGE);
}
}
3.2 Distro 数据存储
源码位置:naming/src/main/java/com/alibaba/nacos/naming/consistency/distro/
// naming/src/main/java/com/alibaba/nacos/naming/consistency/distro/DistroConsistencyServiceImpl.java
public class DistroConsistencyServiceImpl implements ConsistencyService {
// 本地数据存储(内存)
private final ConcurrentHashMap<String, Datum> dataStore = new ConcurrentHashMap<>(1024);
// 监听器集合
private final ConcurrentHashMap<String, RecordListener> listeners = new ConcurrentHashMap<>(1024);
@Override
public void put(String key, Record value) throws NacosException {
// 1. 构建 Datum(带时间戳的数据)
Datum datum = new Datum();
datum.key = key;
datum.value = value;
datum.timestamp = System.currentTimeMillis(); // 关键:时间戳用于版本控制
// 2. 写入本地内存
dataStore.put(key, datum);
// 3. 通知监听器(触发推送到客户端)
notifyListener(key, DataOperation.CHANGE);
// 4. 广播到其他节点
distroProtocol.sync(new DistroKey(key, value), DataOperation.CHANGE);
}
@Override
public Datum get(String key) {
// 从本地内存读取
return dataStore.get(key);
}
@Override
public void remove(String key) {
// 1. 从本地内存删除
dataStore.remove(key);
// 2. 广播删除操作到其他节点
distroProtocol.sync(new DistroKey(key, null), DataOperation.DELETE);
// 3. 通知监听器
notifyListener(key, DataOperation.DELETE);
}
}
3.3 Distro 协议同步
源码位置:naming/src/main/java/com/alibaba/nacos/naming/core/DistroProtocol.java
// naming/src/main/java/com/alibaba/nacos/naming/core/DistroProtocol.java
public class DistroProtocol {
// 同步数据到其他节点
public void sync(DistroKey key, DataOperation action) {
// 1. 遍历所有集群节点(除自己)
for (Member member : memberManager.allMembersWithoutSelf()) {
// 2. 异步同步(不阻塞主流程)
distroTaskEngineHolder.getExecuteWorkers().get(member.getAddress())
.addTask(new DistroSyncTask(key, action, member.getAddress()));
}
}
}
// naming/src/main/java/com/alibaba/nacos/naming/core/DistroSyncTask.java
public class DistroSyncTask implements Runnable {
@Override
public void run() {
try {
// 1. 构建 HTTP 请求
String url = "http://" + targetServer + "/v1/ns/distro/datum";
// 2. 序列化数据
Map<String, Object> data = new HashMap<>();
data.put("key", distroKey.getKey());
data.put("timestamp", System.currentTimeMillis());
// 3. 发送 HTTP 请求
RestResult<String> result = restTemplate.post(url, data);
if (result.ok()) {
log.info("同步成功: {}", distroKey);
}
} catch (Exception e) {
log.error("同步失败: {}", distroKey, e);
}
}
}
3.4 版本冲突处理
// naming/src/main/java/com/alibaba/nacos/naming/consistency/distro/DistroConsistencyServiceImpl.java
public void onDatum(DistroDatum datum) {
// 1. 获取本地数据
Datum localDatum = dataStore.get(datum.key);
// 2. 版本校验
if (localDatum != null && localDatum.timestamp > datum.timestamp) {
// 本地数据更新,丢弃远程数据
log.warn("丢弃旧数据: local={}, remote={}", localDatum.timestamp, datum.timestamp);
return;
}
// 3. 应用远程数据
dataStore.put(datum.key, datum);
// 4. 通知监听器
notifyListener(datum.key, DataOperation.CHANGE);
}
四、健康检查与故障自愈
4.1 客户端心跳机制
// 客户端心跳代码(Spring Cloud Alibaba Nacos Discovery)
@Configuration
public class NacosDiscoveryClientConfig {
@Bean
public NacosNamingService nacosNamingService() {
Properties properties = new Properties();
properties.put("serverAddr", "127.0.0.1:8848");
properties.put("namespace", "public");
return new NacosNamingService(properties);
}
@Scheduled(fixedRate = 5000) // 每 5 秒发送心跳
public void sendBeat() {
try {
Instance instance = new Instance();
instance.setIp(InetAddress.getLocalHost().getHostAddress());
instance.setPort(8080);
instance.setServiceName("user-service");
// 发送心跳
BeatInfo beatInfo = new BeatInfo();
beatInfo.setServiceName("user-service");
beatInfo.setIp(instance.getIp());
beatInfo.setPort(instance.getPort());
beatInfo.setPeriod(5000); // 心跳间隔
nacosNamingService.sendBeat(beatInfo);
} catch (Exception e) {
log.error("心跳发送失败", e);
}
}
}
4.2 服务端健康检查
// naming/src/main/java/com/alibaba/nacos/naming/healthcheck/ClientBeatCheckTask.java
public class ClientBeatCheckTask implements Runnable {
@Override
public void run() {
try {
// 1. 获取所有服务实例
Collection<Service> services = serviceManager.getAllServices();
for (Service service : services) {
// 2. 获取服务的所有实例
List<Instance> instances = service.allIPs();
for (Instance instance : instances) {
// 3. 检查实例是否健康
if (instance.isEphemeral()) { // 临时实例需要健康检查
if (System.currentTimeMillis() - instance.getLastBeat() > HEARTBEAT_TIMEOUT) {
// 超过 20 秒未收到心跳,标记为不健康
instance.setHealthy(false);
// 通知客户端
distroConsistencyService.put(service.getName(), instance);
}
if (System.currentTimeMillis() - instance.getLastBeat() > IP_DELETE_TIMEOUT) {
// 超过 30 秒未收到心跳,删除实例
distroConsistencyService.remove(service.getName(), instance);
}
}
}
}
} catch (Exception e) {
log.error("健康检查失败", e);
}
}
}
4.3 故障自愈机制
// naming/src/main/java/com/alibaba/nacos/naming/core/DistroProtocol.java
public void loadDatumFromRemote() {
// 1. 遍历所有其他节点
for (Member member : memberManager.allMembersWithoutSelf()) {
try {
// 2. 请求全量数据
String url = "http://" + member.getAddress() + "/v1/ns/distro/datums";
RestResult<Map<String, Datum>> result = restTemplate.get(url);
if (result.ok()) {
Map<String, Datum> datums = result.getData();
// 3. 写入本地内存
for (Datum datum : datums.values()) {
distroConsistencyService.onDatum(datum);
}
log.info("从节点 {} 恢复数据成功", member.getAddress());
break;
}
} catch (Exception e) {
log.error("从节点 {} 恢复数据失败", member.getAddress(), e);
}
}
}
五、配置中心原理
5.1 配置推送流程
5.2 长轮询机制
// 客户端长轮询代码(Spring Cloud Alibaba Nacos Config)
public class ConfigRpcTransportClient implements TransportClient {
public String getConfig(String dataId, String group, long timeoutMs) throws NacosException {
// 1. 检查本地缓存
String cacheKey = GroupKey.getKey(dataId, group);
CacheItem cacheItem = cacheMap.get(cacheKey);
if (cacheItem != null) {
// 2. 检查本地 MD5
String localMd5 = MD5Utils.md5Hex(cacheItem.content);
// 3. 向服务端发起长轮询
ConfigQueryResponse response = server.queryConfig(dataId, group, localMd5, timeoutMs);
if (response.isChanged()) {
// 配置变更,更新本地缓存
cacheItem.content = response.getContent();
cacheItem.md5 = response.getMd5();
// 通知监听器
notifyListener(dataId, group, response.getContent());
return response.getContent();
}
}
return cacheItem.content;
}
}
5.3 配置存储结构
-- Nacos 配置表
CREATE TABLE `config_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`data_id` varchar(255) NOT NULL COMMENT '配置ID',
`group_id` varchar(128) DEFAULT NULL COMMENT '分组ID',
`content` longtext NOT NULL COMMENT '配置内容',
`md5` varchar(32) DEFAULT NULL COMMENT 'MD5值',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`src_user` varchar(128) DEFAULT NULL,
`src_ip` varchar(50) DEFAULT NULL,
`app_name` varchar(128) DEFAULT NULL,
`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段(对应命名空间ID)',
`c_desc` varchar(256) DEFAULT NULL,
`c_use` varchar(64) DEFAULT NULL,
`effect` varchar(64) DEFAULT NULL,
`type` varchar(64) DEFAULT NULL,
`c_schema` text,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfo_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 配置历史表
CREATE TABLE `his_config_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`nid` bigint(20) NOT NULL AUTO_INCREMENT,
`data_id` varchar(255) NOT NULL,
`group_id` varchar(128) NOT NULL,
`app_name` varchar(128) DEFAULT NULL,
`content` longtext NOT NULL,
`md5` varchar(32) DEFAULT NULL,
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`src_user` varchar(128) DEFAULT NULL,
`src_ip` varchar(50) DEFAULT NULL,
`op_type` char(10) DEFAULT NULL,
`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfo_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
六、实战:Spring Cloud Alibaba Nacos
6.1 服务提供者配置
# application.yml
spring:
application:
name: user-service
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
namespace: public
group: DEFAULT_GROUP
register-enabled: true
ephemeral: true # 临时实例(AP 模式)
heart-beat-interval: 5000 # 心跳间隔
heart-beat-timeout: 15000 # 心跳超时
ip-delete-timeout: 30000 # 删除超时
metadata:
version: 1.0.0
region: beijing
// 启动类
@SpringBootApplication
@EnableDiscoveryClient
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}
6.2 服务消费者配置
# application.yml
spring:
application:
name: order-service
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
namespace: public
group: DEFAULT_GROUP
// 服务调用
@RestController
public class OrderController {
@Autowired
private LoadBalancerClient loadBalancerClient;
@GetMapping("/order/create")
public String createOrder() {
// 1. 从 Nacos 获取服务实例
ServiceInstance instance = loadBalancerClient.choose("user-service");
// 2. 发起 HTTP 调用
String url = instance.getUri() + "/user/get";
RestTemplate restTemplate = new RestTemplate();
String result = restTemplate.getForObject(url, String.class);
return "订单创建成功,用户信息:" + result;
}
}
6.3 配置中心集成
# bootstrap.yml
spring:
application:
name: user-service
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848
namespace: public
group: DEFAULT_GROUP
file-extension: yaml # 配置文件格式
refresh-enabled: true # 启用自动刷新
shared-configs:
- dataId: common.yaml # 共享配置
group: DEFAULT_GROUP
refresh: true
// 使用配置
@RestController
@RefreshScope // 支持配置动态刷新
public class ConfigController {
@Value("${user.title}")
private String title;
@GetMapping("/config")
public String getConfig() {
return "配置信息:" + title;
}
}
七、性能优化与最佳实践
7.1 集群部署规划
| 节点数 | 推荐配置 | 说明 |
|---|---|---|
| 3 节点 | 2C4G | 最小集群(允许 1 节点故障) |
| 5 节点 | 4C8G | 生产推荐(允许 2 节点故障) |
| 7 节点+ | 8C16G | 大规模集群(允许 3 节点故障) |
7.2 性能调优参数
# application.properties
# 服务注册性能优化
nacos.naming.distro.taskDispatchPeriod=200 # 同步任务派发周期
nacos.naming.distro.taskRetryDelay=5000 # 任务重试延迟
nacos.naming.distro.batchSyncKeyCount=1000 # 批量同步数量
# 健康检查优化
nacos.naming.expireInstance.period=5000 # 过期实例扫描周期
# 配置中心优化
nacos.config.notify.connectTimeout=5000 # 配置推送超时
7.3 避免雪崩效应
// ❌ 错误:同步调用导致雪崩
@FeignClient(name = "user-service")
public interface UserClient {
@GetMapping("/user/{id}")
User getUser(@PathVariable Long id); // 同步调用
}
// ✅ 正确:使用异步 + 熔断
@FeignClient(name = "user-service", fallback = UserClientFallback.class)
public interface UserClient {
@GetMapping("/user/{id}")
CompletableFuture<User> getUserAsync(@PathVariable Long id); // 异步调用
}
// 降级处理
public class UserClientFallback implements UserClient {
@Override
public CompletableFuture<User> getUserAsync(Long id) {
return CompletableFuture.completedFuture(new User(id, "默认用户"));
}
}
八、版本演进:Nacos 2.2.0 新特性
8.1 gRPC 协议支持
# Nacos 2.x 默认使用 gRPC(性能优于 HTTP 1.x)
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
grpc-port: 9848 # gRPC 端口
8.2 访问控制优化
// Nacos 2.2.0 支持基于角色的访问控制(RBAC)
@Configuration
public class NacosSecurityConfig {
@Bean
public Properties nacosProperties() {
Properties properties = new Properties();
properties.put("username", "nacos");
properties.put("password", "nacos");
properties.put("accessKey", "your-access-key");
properties.put("secretKey", "your-secret-key");
return properties;
}
}
九、总结
Nacos AP 架构通过以下机制实现高可用服务发现:
- Distro 协议:自举机制,无 Leader 单点故障
- 内存存储:高性能读写,异步持久化
- 增量同步:减少网络开销,提升同步效率
- 故障自愈:节点恢复后自动从其他节点拉取数据
- 健康检查:自动剔除不健康实例
AP 模式适用场景:
- 服务注册与发现(非核心元数据)
- 对可用性要求高于一致性
- 允许短暂的不一致窗口
CP 模式适用场景:
- 配置中心(配置变更需要强一致)
- 核心元数据存储(如 Leader 选举)
参考资料
- Nacos 官方文档:https://nacos.io/zh-cn/
- Nacos GitHub 源码(2.2.0):https://github.com/alibaba/nacos/tree/2.2.0
- 《Spring Cloud Alibaba 微服务原理与实战》第4章:服务注册与发现
标签:Nacos,服务发现,配置中心,AP架构,Distro协议
更多推荐




所有评论(0)