nacos配置中心动态刷新原理分析(server端)

目录

      一、背景介绍

      二、侦听客户端长轮询

      三、服务端推送变更配置


一、背景介绍

在之前上一篇文章中我们一起看了nacos配置中心动态刷新client端的长轮询逻辑监听、事件发布变更配置和client端怎么和SpringBoot整合。

所以,本篇我们主要来看nacos服务端是怎么处理客户端长轮询监听配置变更和主动推送变更配置的。

二、侦听客户端长轮询

从上一篇中我们知道nacos客户端在长轮询逻辑中通过checkUpdateDataIds()方法发出http请求到服务端探测是否有配置发生变更。

// check server config
List<String> changedGroupKeys = checkUpdateDataIds(cacheDatas, inInitializingCacheList);
if (!CollectionUtils.isEmpty(changedGroupKeys)) {
    LOGGER.info("get changedGroupKeys:" + changedGroupKeys);
}

在checkUpdateDataIds()底层我们能够发现调用服务端的地址链接是/v1/cs/configs/listener。

HttpRestResult<String> result = agent
    .httpPost(Constants.CONFIG_CONTROLLER_PATH + "/listener", headers, params, agent.getEncode(),
        readTimeoutMs);

所以,我们直接看nacos服务端/v1/cs/configs/listener对应的方法。

@PostMapping("/listener")
@Secured(action = ActionTypes.READ, parser = ConfigResourceParser.class)
public void listener(HttpServletRequest request, HttpServletResponse response) throws IOException {
    request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", true);
    String probeModify = request.getParameter("Listening-Configs");
    if (StringUtils.isBlank(probeModify)) {
        throw new IllegalArgumentException("invalid probeModify");
    }
    probeModify = URLDecoder.decode(probeModify, Constants.ENCODE);
    Map<String, String> clientMd5Map;
    try {
        //计算 MD5 值
        clientMd5Map = MD5Util.getClientMd5Map(probeModify);
    } catch (Throwable e) {
        throw new IllegalArgumentException("invalid probeModify");
    }
    // 进行长轮询逻辑
    inner.doPollingConfig(request, response, clientMd5Map, probeModify.length());
}

该方法前面都是一些校验以及生成clientMd5Map,这个Map里面包含了监听配置变更的key以及value,下面会用到,核心主要看inner.doPollingConfig()这个逻辑。

public void doPollingConfig(HttpServletRequest request, HttpServletResponse response,
                            Map<String, String> clientMd5Map, int probeRequestSize) throws IOException {
    //长轮询
    if (LongPollingService.isSupportLongPolling(request)) {
        longPollingService.addLongPollingClient(request, response, clientMd5Map, probeRequestSize);
        return;
    }
    // 兼容短轮询逻辑.
    List<String> changedGroups = MD5Util.compareMd5(request, response, clientMd5Map);
    // 兼容短轮询结果.
    String oldResult = MD5Util.compareMd5OldResult(changedGroups);
    String newResult = MD5Util.compareMd5ResultString(changedGroups);
    String version = request.getHeader(Constants.CLIENT_VERSION_HEADER);
    if (version == null) {
        version = "2.0.0";
    }
    int versionNum = Protocol.getVersionNumber(version);
    // Befor 2.0.4 version, return value is put into header.
    if (versionNum < START_LONG_POLLING_VERSION_NUM) {
        response.addHeader(Constants.PROBE_MODIFY_RESPONSE, oldResult);
        response.addHeader(Constants.PROBE_MODIFY_RESPONSE_NEW, newResult);
    } else {
        request.setAttribute("content", newResult);
    }
    Loggers.AUTH.info("new content:" + newResult);
    // Disable cache.
    response.setHeader("Pragma", "no-cache");
    response.setDateHeader("Expires", 0);
    response.setHeader("Cache-Control", "no-cache,no-store");
    response.setStatus(HttpServletResponse.SC_OK);
}

该方法首先判断客户端发送过来的请求是长轮询还是短轮询,如果是长轮询直接执行长轮询逻辑然后return,下面的逻辑是兼容短轮询的逻辑实现。

在兼容的短轮询逻辑中拿到刚才的clientMd5Map进行解析、比对是否和服务端发生了变更,如果发生了变更就将老数据和新数据都放到响应的header里面,直接返回给nacos客户端了。

是否是长轮询其实就是判断请求头中是否设置了Long-Pulling-Timeout,刚才在ClientWorker的请求头中设置了这个参数。

确定是长轮询逻辑,我们直接看addLongPollingClient()添加客户长轮询这个逻辑。

public void addLongPollingClient(HttpServletRequest req, HttpServletResponse rsp, Map<String, String> clientMd5Map, int probeRequestSize) {
    ......
    int delayTime = SwitchService.getSwitchInteger(SwitchService.FIXED_DELAY_TIME, 500);

    //为 LoadBalance 添加延迟时间,并提前 500ms 返回响应,避免客户端超时(即超时时间减 500ms 后赋值给 timeout 变量)
    long timeout = Math.max(10000, Long.parseLong(str) - delayTime);
    //判断是否为固定轮询,是则 30s 后执行;否则 29.5s 后执行
    if (isFixedPolling()) {
        timeout = Math.max(10000, getFixedPollingInterval());
        //除了设置修复轮询超时之外什么都不做
    } else {
        long start
<think>好的,我现在需要解决用户在使用Nacos和Ribbon时遇到的503 Service Unavailable错误,具体错误信息是“LoadBalancer does not have available servers for...”。这个问题通常是因为服务实例无法被正确发现或负载均衡器配置不当导致的。让我一步步分析可能的原因和解决方案。 首先,用户提到使用Nacos作为注册中心,而Ribbon作为负载均衡器。那么,我需要检查以下几个方面: 1. **服务注册状态**:确保服务提供者已经成功注册到Nacos服务器。如果服务没有注册,Ribbon自然找不到可用的实例。用户需要登录Nacos控制台,查看目标服务是否在服务列表中,并且实例的健康状态是否为健康。这一步很关键,因为如果服务没有注册成功,后续的负载均衡也就无从谈起。 2. **命名空间和分组配置**:有时候,服务注册和发现可能因为命名空间(namespace)或分组(group)的不匹配而失败。比如,服务提供者可能注册到了不同的命名空间,而消费者在另一个命名空间下查找,导致找不到实例。需要确认服务提供者和消费者使用的命名空间和分组是否一致。此外,Nacos的配置文件中是否设置了正确的namespace ID,这一点容易被忽略。 3. **Ribbon配置**:检查Ribbon的配置是否正确。Ribbon默认的负载均衡策略可能有问题,或者配置的超时时间过短。可以尝试显式配置Ribbon的负载均衡规则,比如使用轮询策略,并调整超时参数。例如,在application.yml中添加ribbon的相关配置,如ConnectTimeout和ReadTimeout,避免因超时导致服务不可用。 4. **依赖和版本兼容性**:确保项目中使用的Spring Cloud Alibaba版本与Nacos客户版本兼容。版本不兼容可能导致服务注册发现功能异常。用户需要检查pom.xml或build.gradle中的依赖版本是否匹配官方推荐的组合。比如,Spring Cloud 2020.0.x对应Spring Cloud Alibaba 2021.1,而Nacos客户版本可能需要2.x以上。 5. **健康检查机制**:Nacos通过心跳机制来维护服务实例的健康状态。如果服务提供者的心跳间隔设置过长,或者网络问题导致心跳无法到达Nacos服务器,可能会被标记为不健康,从而被Ribbon排除。需要检查服务提供者的健康检查配置,确保心跳正常发送,并且Nacos服务器能够及时收到。 6. **网络连通性**:服务消费者和Nacos服务器之间的网络是否通畅,是否存在防火墙或安全组限制。有时候,服务注册成功,但消费者无法从Nacos获取实例列表,可能是因为网络问题。需要确认消费者可以访问Nacos口(如8848),并且没有中间网络设备拦截请求。 7. **Ribbon的刷新机制**:Ribbon默认会有缓存服务列表的机制,如果服务实例发生变化,可能需要一定时间才能刷新。可以尝试在配置中启用更积极的刷新策略,或者重启消费者服务强制刷新服务列表。 8. **日志排查**:查看服务提供者和消费者的日志,特别是Nacos客户的日志,看是否有注册失败或获取实例失败的错误信息。例如,服务提供者可能因为配置错误无法连接到Nacos服务器,或者在注册时抛出了异常。 9. **元数据匹配**:在某些情况下,Ribbon可能会根据元数据过滤实例,如果服务实例的元数据不符合条件,可能导致没有可用服务器。需要检查服务实例的元数据配置,确保符合消费者的筛选条件。 10. **测试接口连通性**:即使服务注册成功,也需要确保服务提供者的接口可以正常访问。使用curl或Postman直接调用服务提供者的接口,确认返回正常,排除服务本身的问题。 综合以上可能的因素,用户需要逐一排查,从服务注册、配置、网络到负载均衡策略等方面进行检查。特别是版本兼容性和命名空间配置,这两个问题在实际开发中较为常见,容易导致服务发现失败。</think>针对使用Nacos和Ribbon时出现`LoadBalancer does not have available servers`导致的503错误,以下是逐步排查与解决方案: --- ### **1. 确认服务注册状态** - **步骤**:登录Nacos控制台(默认地址:`http://localhost:8848/nacos`),检查目标服务是否已注册且实例状态为**健康**。 - **关键点**: - 若服务未注册,需检查服务提供者的Nacos客户配置: ```yaml spring: cloud: nacos: discovery: server-addr: localhost:8848 # Nacos服务器地址 namespace: your_namespace_id # 确认命名空间一致 group: DEFAULT_GROUP # 确认分组一致 ``` - 检查服务提供者日志,确认无注册失败错误(如网络超时或权限问题)[^1]。 --- ### **2. 检查命名空间与分组配置** - **问题**:服务提供者与消费者使用不同的命名空间或分组。 - **解决方案**: - 在`bootstrap.yml`或`application.yml`中显式指定相同配置: ```yaml spring: cloud: nacos: discovery: namespace: 相同命名空间ID group: 相同分组名称 ``` --- ### **3. 验证Ribbon配置** - **步骤**:在服务消费者的配置文件中添加Ribbon参数: ```yaml ribbon: ConnectTimeout: 3000 # 连接超时时间(毫秒) ReadTimeout: 5000 # 读取超时时间 NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule # 强制使用轮询策略 ``` - **说明**:若未显式配置负载均衡规则,Ribbon可能因默认策略问题导致实例选择失败。 --- ### **4. 检查依赖版本兼容性** - **兼容性要求**: - Spring Cloud Alibaba版本需与Spring Boot和Spring Cloud版本匹配。 - 参考官方组合(示例): - Spring Boot 2.6.x + Spring Cloud 2021.0.x + Spring Cloud Alibaba 2021.0.1.0 - Spring Boot 2.4.x + Spring Cloud 2020.0.x + Spring Cloud Alibaba 2021.1 - **操作**:更新`pom.xml`依赖至正确版本: ```xml <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <version>2021.0.1.0</version> </dependency> ``` --- ### **5. 强制刷新Ribbon缓存** - **方法**:重启服务消费者或调用`/actuator/refresh`刷新配置。 - **原理**:Ribbon默认缓存服务列表,手动刷新可立即获取最新实例。 --- ### **6. 检查网络与健康检查** - **网络验证**: - 从服务消费者执行`telnet nacos-server-ip 8848`,确认口可访问。 - 检查防火墙或安全组是否放行8848口。 - **健康检查**: - 确保服务提供者定时发送心跳至Nacos(默认间隔5秒)。 - 检查Nacos控制台中实例的“健康”状态,若为“不健康”,需排查服务提供者运行状态。 --- ### **7. 日志排查** - **查看日志**: - 服务提供者:搜索`Registered instance ... with nacos`确认注册成功。 - 服务消费者:搜索`NacosDiscoveryClient : Get service ... from server`确认实例拉取成功。 - 错误日志:关注`No instances available for {service-name}`或连接超时信息。 --- ### **总结流程** 1. 确认服务已注册且健康(Nacos控制台)。 2. 检查命名空间、分组一致性。 3. 验证Ribbon配置与依赖版本。 4. 刷新缓存或重启服务。 5. 通过日志和网络工具定位具体故障点。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值