nacos 源码解析以及应用

解析 Nacos 源码可以帮助你深入了解其工作原理和实现细节。Nacos 是一个动态服务发现、配置管理和服务管理平台,广泛应用于微服务架构中。以下是详细的解析步骤和关键组件介绍。

Nacos的优劣势

优势
  1. 统一的服务管理:

    • Nacos 提供了一站式的服务发现、配置管理和命名空间管理功能。
    • 开发者可以通过一个入口管理所有相关的微服务组件,简化运维工作。
  2. 高可用性:

    • 支持集群模式,通过 Raft 协议保证数据的一致性和高可用性。
    • 自动选举 Leader,确保即使某个节点故障也不会影响整体服务的正常运行。
  3. 易用性:

    • 提供简洁的 API 和 Web 控制台,方便用户进行操作和管理。
    • 文档详尽且易于理解,降低了学习门槛。
  4. 强大的配置管理:

    • 支持动态配置,可以在不重启服务的情况下更新配置。
    • 提供灰度发布和回滚功能,确保配置变更的安全性。
  5. 丰富的客户端 SDK:

    • 提供多种编程语言的客户端 SDK,包括 Java, Go, C#, Python 等。
    • 方便不同技术栈的开发者集成和使用 Nacos 的功能。
  6. 跨平台支持:

    • 支持 Docker 和 Kubernetes 等容器编排工具,适用于云原生环境。
    • 可以轻松部署在各种基础设施之上,提供一致的服务管理体验。
劣势
  1. 功能覆盖范围:

    • Nacos 主要专注于服务发现、配置管理和命名空间管理,对于服务间调用(RPC)的支持相对较弱。
    • 如果需要进行复杂的 RPC 调用,可能需要结合其他组件(如 Dubbo)来实现。
  2. 性能优化:

    • 相较于专门的 RPC 框架(如 Dubbo),Nacos 在处理大规模 RPC 请求时可能存在一定的性能瓶颈。
    • 需要根据具体场景进行调优,以达到最佳性能表现。
  3. 生态整合限制:

    • 虽然 Nacos 可以与 Spring Cloud 等框架集成,但在某些高级特性上的支持可能不如 Dubbo 强大。
    • 对于一些特定的需求,可能需要额外的开发工作来实现。
  4. 社区活跃度:

    • Nacos 社区活跃,但相比于 Spring Cloud,其影响力和参与度仍然较小。
    • 新的功能更新速度较快,但也意味着稳定性方面可能需要更多的验证和测试。
  5. 学习成本:

    • 尽管 Nacos 的文档较为详尽,但对于初次接触微服务架构的开发者来说,仍需一定时间熟悉其概念和操作。
    • 对于复杂的企业级应用,可能需要更深入的学习和实践。

解析目标

我们将逐步解析 Nacos 的核心组件和流程,包括但不限于以下部分:

  1. 服务注册与发现:如何将服务注册到 Nacos 并从 Nacos 发现可用的服务。
  2. 配置管理:如何存储和管理配置数据。
  3. 命名空间管理:如何隔离不同的环境或项目。
  4. 集群模式:如何在多个节点之间同步数据并保证高可用性。
  5. 客户端 SDK:如何使用 Nacos 客户端进行服务调用和配置获取。

准备工作

在开始解析之前,请确保你已经具备以下条件:

  1. Java 开发环境:安装 JDK 8 或更高版本。
  2. Git 工具:用于克隆 Nacos 源码仓库。
  3. IDE:推荐使用 IntelliJ IDEA 或 Eclipse。
  4. Maven:用于构建和管理依赖。

克隆 Nacos 源码

首先,克隆 Nacos 的 GitHub 仓库到本地:

bash

git clone https://github.com/alibaba/nacos.git
cd nacos

构建 Nacos 源码

使用 Maven 构建 Nacos 源码:

bash

mvn clean install -Dmaven.test.skip=true

核心组件解析

1. 服务注册与发现

Nacos 使用注册中心来管理服务的元数据,包括服务名称、IP 地址、端口等信息。

服务注册

当服务提供者启动时,会将其元数据注册到 Nacos 注册中心。主要涉及 NamingServiceNamingGrpcServer 类。

  • NamingService: 提供服务注册、发现和健康检查等功能。
  • NamingGrpcServer: 实现了 gRPC 协议的服务端逻辑。

关键代码位置:

  • naming/src/main/java/com/alibaba/nacos/naming/core/NamingService.java
  • naming/src/main/java/com/alibaba/nacos/naming/grpc/server/NamingGrpcServer.java

核心方法:

  • registerInstance(String serviceName, Instance instance): 注册服务实例。
  • deregisterInstance(String serviceName, Instance instance): 注销服务实例。

示例代码:

java

@Override
public void registerInstance(String serviceName, Instance instance) throws NacosException {
    if (StringUtils.isEmpty(serviceName)) {
        throw new IllegalArgumentException("serviceName invalid");
    }
    Loggers.SRV_LOG.info("[REGISTER-SERVICE] {} registering service {}", ipUtil.getHostFromIp(instance.getIp()), JsonUtils.toJson(instance));
    String namespaceId = instance.getNamespaceId();
    Service service = getService(namespaceId, serviceName);
    if (service == null) {
        createEmptyService(namespaceId, serviceName, false);
        service = getService(namespaceId, serviceName);
    }
    addInstance(namespaceId, serviceName, instance);
}
服务发现

当服务消费者启动时,会从 Nacos 注册中心订阅服务提供者的地址列表。主要涉及 NamingServiceNamingPush 类。

  • NamingService: 提供服务注册、发现和健康检查等功能。
  • NamingPush: 处理服务实例变更的通知推送。

关键代码位置:

  • naming/src/main/java/com/alibaba/nacos/naming/core/NamingService.java
  • naming/src/main/java/com/alibaba/nacos/naming/push/NamingPush.java

核心方法:

  • getAllInstances(String serviceName, boolean healthy): 获取所有服务实例。
  • subscribe(String serviceName, EventListener listener): 订阅服务实例变更事件。

示例代码:

java

@Override
public List<Instance> getAllInstances(String serviceName, boolean healthy) throws NacosException {
    return getAllInstances(serviceName, Constants.DEFAULT_GROUP, healthy);
}

@Override
public List<Instance> getAllInstances(String serviceName, String groupName, boolean healthy) throws NacosException {
    String namespaceId = NamespaceUtil.getNamespaceParameter(request);
    Service service = getService(namespaceId, serviceName, groupName);
    if (service == null) {
        throw new NacosException(NacosException.SERVICE_NOT_FOUND, "no service: " + serviceName + "@" + groupName);
    }
    return service.allIPs(true, healthy);
}
2. 配置管理

Nacos 提供配置管理功能,允许用户集中管理和动态更新配置数据。

配置发布

当管理员发布配置时,Nacos 将配置数据存储到数据库,并通知订阅的客户端。主要涉及 ConfigControllerPersistentService 类。

  • ConfigController: 处理 HTTP 请求,接收配置发布请求。
  • PersistentService: 负责持久化配置数据到数据库。

关键代码位置:

  • config/src/main/java/com/alibaba/nacos/config/server/controller/ConfigController.java
  • config/src/main/java/com/alibaba/nacos/config/server/service/PersistentService.java

核心方法:

  • publishConfig(String dataId, String group, String content): 发布配置。
  • getConfigInner(String dataId, String group, long readTimeoutMs): 获取配置内容。

示例代码:

java

@PostMapping("/publish")
@Secured(action = ActionTypes.WRITE)
public Response publishConfig(HttpServletRequest request) throws IOException, ServletException {
    String dataId = WebUtils.required(request, CommonParams.DATA_ID);
    String group = WebUtils.optional(request, CommonParams.GROUP, DEFAULT_GROUP);
    String tenant = WebUtils.optional(request, CommonParams.TENANT, StringUtils.EMPTY);

    String content = IOUtils.toString(request.getInputStream(), StandardCharsets.UTF_8.name());

    ConfigInfo configInfo = new ConfigInfo(dataId, group, tenant, content);
    boolean isOk = persistentService.insertOrUpdate(configInfo, true);

    if (isOk) {
        ConfigTraceService.logPersistenceEvent(dataId, group, tenant, request.getRemoteAddr(), ConfigTraceService.PERSISTENCE_EVENT_PUBLISH,
                content);
        SpringContext.getAppContext().getBean(ConfigChangePublisher.class).asyncPublish(dataId, group, tenant, content);
        return Response.success();
    } else {
        return Response.fail(ResponseCode.FAIL.getCode(), "save config error");
    }
}
配置订阅

当客户端订阅配置时,Nacos 会推送最新的配置数据,并在配置变更时实时通知客户端。主要涉及 ConfigControllerLongPollingService 类。

  • ConfigController: 处理 HTTP 请求,接收配置订阅请求。
  • LongPollingService: 实现长轮询机制,处理配置变更通知。

关键代码位置:

  • config/src/main/java/com/alibaba/nacos/config/server/controller/ConfigController.java
  • config/src/main/java/com/alibaba/nacos/config/server/service/impl/LongPollingService.java

核心方法:

  • listenerDiff(HttpServletRequest request): 处理配置监听请求。
  • doLongPolling(HttpRequestWrapper request, HttpServletResponse response, InnerAsyncProcessEntry entry): 执行长轮询操作。

示例代码:

java

@GetMapping("/listener")
@Secured(action = ActionTypes.READ)
public void listenerDiff(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String probeModifyCountStr = request.getHeader(CommonHeaders.PROBE_MODIFY_COUNT);
    Long clientMd5MapStr = RequestUtil.getClientSpasCheckHeader(request);
    Map<String, String> clientMd5Map;
    if (clientMd5MapStr != null) {
        clientMd5Map = SpasAdapter.clientMd5(strToClientMd5Map(clientMd5MapStr), request);
    } else {
        clientMd5Map = Collections.emptyMap();
    }

    long lastRefTime = WebUtils.optional(request, CommonParams.LAST_REF_TIME, 0L);
    int timeout = WebUtils.optional(request, CommonParams.TIMEOUT_MILLS, 30000);
    long start = System.currentTimeMillis();

    String actionName = "listener";
    RecordLog.warn(actionName, request.getRemoteAddr() + "|" + request.getHeader("User-Agent"));

    final AsyncContext asyncContext = request.startAsync();
    asyncContext.setTimeout(timeout);
    InnerAsyncProcessEntry processEntry = new InnerAsyncProcessEntry(asyncContext.getRequest(), asyncContext.getResponse(),
            clientMd5Map, lastRefTime, actionName, timeout, start, asyncContext);
    long pollingTimeoutFuture = start + timeout;

    // Add to processor queue
    long delayTime = pollingTimeoutFuture - System.currentTimeMillis();
    futureManager.add(processEntry, delayTime);
}
3. 命名空间管理

Nacos 支持命名空间(Namespace),用于隔离不同的环境或项目。

创建命名空间

当管理员创建命名空间时,Nacos 将命名空间信息存储到数据库。主要涉及 NamespaceControllerNamespacePersistService 类。

  • NamespaceController: 处理 HTTP 请求,接收命名空间创建请求。
  • NamespacePersistService: 负责持久化命名空间数据到数据库。

关键代码位置:

  • console/src/main/java/com/alibaba/nacos/console/controller/NamespaceController.java
  • console/src/main/java/com/alibaba/nacos/console/service/NamespacePersistService.java

核心方法:

  • createNamespace(@RequestParam String customNamespaceId, @RequestParam String namespaceName, @RequestParam(required = false) String namespaceDesc): 创建命名空间。
  • getNamespaceList() : 获取所有命名空间列表。

示例代码:

java

@AuthAction(AuthAction.Type.CREATE)
@RequestMapping(value = "/namespace", method = RequestMethod.POST)
@Secured(parser = NamingResourceParser.class, action = Action.Create)
public Result<String> createNamespace(
        @RequestParam String customNamespaceId,
        @RequestParam String namespaceName,
        @RequestParam(required = false) String namespaceDesc) throws Exception {

    if (StringUtils.isBlank(customNamespaceId)) {
        customNamespaceId = UUID.randomUUID().toString();
    }

    Namespace namespace = new Namespace();
    namespace.setNamespace(customNamespaceId);
    namespace.setName(namespaceName);
    namespace.setDesc(namespaceDesc);
    namespace.setDefaultNamespace(false);
    namespacePersistService.insertNamespace(namespace);

    return Result.successMessage("success");
}
查询命名空间

当用户查询命名空间时,Nacos 从数据库中读取命名空间信息。主要涉及 NamespaceControllerNamespacePersistService 类。

关键代码位置:

  • console/src/main/java/com/alibaba/nacos/console/controller/NamespaceController.java
  • console/src/main/java/com/alibaba/nacos/console/service/NamespacePersistService.java

核心方法:

  • getNamespaceList() : 获取所有命名空间列表。
  • getNamespaceById(@RequestParam String namespaceId) : 根据 ID 获取命名空间信息。

示例代码:

java

@RequestMapping(value = "/namespace-list", method = RequestMethod.GET)
@Secured(parser = NamingResourceParser.class, action = Action.Read)
public Result<List<Namespace>> getNamespaceList() throws Exception {
    List<Namespace> namespaces = namespacePersistService.findNamespaces(0, Integer.MAX_VALUE);
    return Result.successData(namespaces);
}
4. 集群模式

Nacos 支持集群模式,通过 Raft 协议实现数据的一致性和高可用性。

集群初始化

当 Nacos 节点启动时,会加入集群并选举 Leader。主要涉及 RaftCorePeerSet 类。

  • RaftCore: 实现 Raft 协议的核心逻辑。
  • PeerSet: 管理集群中的 Peer 节点。

关键代码位置:

  • core/src/main/java/com/alibaba/nacos/core/raft/RaftCore.java
  • core/src/main/java/com/alibaba/nacos/core/raft/store/peerset/PeerSet.java

核心方法:

  • start() : 启动 Raft 节点。
  • becomeLeader() : 成为 Leader。
  • addPeer(Peer peer) : 添加 Peer 节点。

示例代码:

java

public synchronized void start() throws NacosException {
    if (started) {
        return;
    }
    started = true;
    raftStore.loadSnapshot();
    currentTerm = raftStore.getCurrentTerm();
    votedFor = raftStore.getVotedFor();
    initPeers();
    leaderElection.scheduleAtFixedRate(() -> {
        try {
            election();
        } catch (Exception e) {
            Loggers.RAFT.error("leader election failed", e);
        }
    }, 0, ELECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS);
    heartbeat.scheduleAtFixedRate(() -> {
        try {
            sendHeartbeat();
        } catch (Exception e) {
            Loggers.RAFT.error("send heartbeat failed", e);
        }
    }, HEARTBEAT_INTERVAL_MS, HEARTBEAT_INTERVAL_MS, TimeUnit.MILLISECONDS);
}
数据同步

Leader 节点负责处理客户端请求并将日志条目复制到 Follower 节点。主要涉及 AppendEntriesRpcApplyTask 类。

  • AppendEntriesRpc: 处理 AppendEntries RPC 请求。
  • ApplyTask: 应用日志条目的任务。

关键代码位置:

  • core/src/main/java/com/alibaba/nacos/core/raft/rpc/AppendEntriesRpc.java
  • core/src/main/java/com/alibaba/nacos/core/raft/log/task/ApplyTask.java

核心方法:

  • appendEntries(AppendEntries appendEntries) : 处理 AppendEntries 请求。
  • apply(LogEntry logEntry) : 应用日志条目。

示例代码:

java

@Override
public AppendEntriesResponse appendEntries(AppendEntries appendEntries) {
    RaftCore raftCore = raftHolder.getRaftCore(appendEntries.getGroupId());
    if (raftCore == null) {
        return new AppendEntriesResponse(-1, false);
    }
    raftCore.onAppendEntries(appendEntries);
    return new AppendEntriesResponse(raftCore.getCurrentTerm(), true);
}
5. 客户端 SDK

Nacos 提供客户端 SDK,允许开发者轻松地进行服务注册、发现和配置管理。主要涉及 NacosFactoryNamingService 类。

  • NacosFactory: 创建 Nacos 客户端实例。
  • NamingService: 提供服务注册、发现和健康检查等功能。

关键代码位置:

  • clients/src/main/java/com/alibaba/nacos/api/NacosFactory.java
  • clients/src/main/java/com/alibaba/nacos/api/naming/NamingService.java

核心方法:

  • createNamingService(Properties properties): 创建 NamingService 实例。
  • getService(String serviceName, String groupName, boolean healthy): 获取服务实例。

示例代码:

java

public static NamingService createNamingService(Properties properties) throws NacosException {
    String serverAddr = properties.getProperty(PropertyKeyConst.SERVER_ADDR);
    if (StringUtils.isEmpty(serverAddr)) {
        throw new NullPointerException("serverAddr is empty");
    }
    String endpoint = properties.getProperty(PropertyKeyConst.ENDPOINT);
    String namespace = properties.getProperty(PropertyKeyConst.NAMESPACE);
    String accessKey = properties.getProperty(PropertyKeyConst.ACCESS_KEY);
    String secretKey = properties.getProperty(PropertyKeyConst.SECRET_KEY);
    String ramRoleName = properties.getProperty(PropertyKeyConst.RAM_ROLE_NAME);
    String clusterName = properties.getProperty(PropertyKeyConst.CLUSTER_NAME);
    String namingLoadCacheAtStart = properties.getProperty(PropertyKeyConst.NAMING_LOAD_CACHE_AT_START);
    String subscribe = properties.getProperty(PropertyKeyConst.SUBSCRIBE);
    String pushReceiver = properties.getProperty(PropertyKeyConst.PUSH_RECEIVER);
    String username = properties.getProperty(PropertyKeyConst.USERNAME);
    String password = properties.getProperty(PropertyKeyConst.PASSWORD);

    return new NacosNamingService(serverAddr, endpoint, namespace, accessKey, secretKey, ramRoleName, clusterName,
            Boolean.parseBoolean(namingLoadCacheAtStart), Boolean.parseBoolean(subscribe), pushReceiver, username, password);
}

示例项目

为了更好地理解上述解析内容,我们可以创建一个简单的 Nacos 示例项目,包含服务提供者和消费者。

1. 创建服务接口

定义一个简单的服务接口:

UserApi.java:

java

package com.example.nacos.service;

public interface UserApi {
    String sayHello(String name);
}
2. 实现服务提供者

实现服务接口并启动服务提供者:

UserServiceImpl.java:

java

package com.example.nacos.provider;

import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingFactory;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.example.nacos.service.UserApi;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.util.Properties;

@Service
public class UserServiceImpl implements UserApi {

    @Value("${spring.application.name}")
    private String serviceName;

    @Value("${server.port}")
    private int port;

    @PostConstruct
    public void init() throws NacosException {
        Properties properties = new Properties();
        properties.put("serverAddr", "localhost:8848"); // Nacos 服务器地址
        NamingService namingService = NamingFactory.createNamingService(properties);
        Instance instance = new Instance();
        instance.setIp("127.0.0.1");
        instance.setPort(port);
        instance.setServiceName(serviceName);
        namingService.registerInstance(serviceName, instance);
    }

    @Override
    public String sayHello(String name) {
        return "Hello, " + name;
    }
}

ProviderApplication.java:

java

package com.example.nacos.provider;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ProviderApplication {
    public static void main(String[] args) {
        SpringApplication.run(ProviderApplication.class, args);
    }
}

application.properties:

# Application Info
spring.application.name=user-provider
server.port=8081
3. 实现服务消费者

创建服务消费者并调用远程服务:

UserServiceConsumer.java:

java

package com.example.nacos.consumer;

import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingFactory;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.example.nacos.service.UserApi;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.PostConstruct;
import java.net.URI;
import java.util.List;
import java.util.Properties;

@RestController
public class UserController {

    @Autowired
    private Environment env;

    @Autowired
    private UserApiClient userApiClient;

    @GetMapping("/hello")
    public String hello(@RequestParam String name) {
        return userApiClient.sayHello(name);
    }
}

@FeignClient(name = "user-provider")
interface UserApiClient {
    @GetMapping("/sayHello")
    String sayHello(@RequestParam String name);
}

@Component
class FeignConfiguration {

    @Autowired
    private Environment env;

    @Bean
    public NamingService namingService() throws NacosException {
        Properties properties = new Properties();
        properties.put("serverAddr", "localhost:8848"); // Nacos 服务器地址
        return NamingFactory.createNamingService(properties);
    }
}

ConsumerApplication.java:

java

package com.example.nacos.consumer;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableFeignClients
public class ConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class, args);
    }
}

application.properties:

# Application Info
spring.application.name=user-consumer
server.port=8082

运行示例

  1. 启动 Nacos 服务器: 确保 Nacos 服务器正在运行。如果没有安装,可以从 Nacos 官网 下载并按照官方文档进行安装和启动。

  2. 启动服务提供者

bash

cd provider
mvn spring-boot:run

3.启动服务消费者:

bash

cd consumer
mvn spring-boot:run

总结

通过对 Nacos 源码的解析,我们深入了解了其核心组成部分和工作原理,包括服务注册与发现、配置管理、命名空间管理、集群模式和客户端 SDK。此外,我们还创建了一个简单的示例项目,展示了如何使用 Nacos 进行服务开发和部署。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

慧香一格

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值