通过 Ribbon 查询 Nacos 服务实例

本文涉及的产品
网络型负载均衡 NLB,每月750个小时 15LCU
传统型负载均衡 CLB,每月750个小时 15LCU
应用型负载均衡 ALB,每月750个小时 15LCU
简介: 通过 Ribbon 查询 Nacos 服务实例

Nacos 服务列表管理


Nacos 提供了开放 API 可通过 /nacos/v1/ns/instance/list 获取服务列表。如果我们采用 spring-cloud 方式去获取服务,最终会通过 Nacos Client + loadbalancer 的方式进行客户端负载均衡。


Ribbon 源码解析


Ribbon 简介


Spring Cloud Ribbon 是 Netflix Ribbon 实现的一套客户端负载均衡工具 简单的说,Ribbon 是 Netflix 发布的开源项目,主要功能是提供客户端的复杂算法和服务调用。 Ribbon 客户端组件提供一系列完善的配置项如超时、重试等。简单的说,就是配置文件中列出 load Balancer (简称 LB)后面所有的机器,Ribbon 会自动的帮助你基于某种规则(如简单轮询,随机链接等)去链接这些机器。我们很容易使用 Ribbon 自定义的负载均衡算法。


Ribbon 使用


首先需要定义 RestTemplate 使用 Ribbon 策略;


@Configuration
public class RestTemplateConfig {
    @LoadBalanced
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}


本地使用 RestTemplate 调用远程接口;


@Autowired
private RestTemplate restTemplate;
@RequestMapping(value = "/echo/{id}", method = RequestMethod.GET)
public String echo(@PathVariable Long id) {
    return restTemplate.getForObject("http://member-service/member/get/" + id, String.class);
}


Ribbon 源码分析


RestTemplate 继承 InterceptingHttpAccessor 通过 interceptors 字段接受

HttpRequestInterceptor 请求拦截器。对于 Ribbion 初始化类是 RibbonAutoConfiguration 中的, 它在 spring-cloud-netflix-ribbon 中定义。但是它在初始化之前,又需要加载 RibbonAutoConfiguration 配置,它是在 spring-cloud-common 中。具体的代码如下:


@Configuration(proxyBeanMethods = false)
// 工程中一定存在 RestTemplate 类
@ConditionalOnClass(RestTemplate.class)
// 容器中一定存在 LoadBalancerClient 类 Bean 实例
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {
   // 获取 Spring 容器中所有的 RestTemplate 实例
   @LoadBalanced
   @Autowired(required = false)
   private List<RestTemplate> restTemplates = Collections.emptyList();
   // 获取 Spring 容器中 LoadBalancerRequestTransformer 实例
   @Autowired(required = false)
   private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();
   // 在 Bean 初始化完成后会调用 afterSingletonsInstantiated 方法
   // 这里是一个 lambda 表达式方式的实现, 主要是为 restTemplate 实例设置 RestTemplateCustomizer
   @Bean
   public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
         final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
      return () -> restTemplateCustomizers.ifAvailable(customizers -> {
         for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
            for (RestTemplateCustomizer customizer : customizers) {
               customizer.customize(restTemplate);
            }
         }
      });
   }
   // LoadBalancerRequestFactory 工厂类
   // 主要是用来提供 LoadBalancerClient 实例和 LoadBalancerRequestTransformer
   @Bean
   @ConditionalOnMissingBean
   public LoadBalancerRequestFactory loadBalancerRequestFactory(
         LoadBalancerClient loadBalancerClient) {
      return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers);
   }
   // LoadBalancerInterceptor 拦截器 
   @Configuration(proxyBeanMethods = false)
   @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
   static class LoadBalancerInterceptorConfig {
      // 创建默认的拦截器 LoadBalancerInterceptor 的实例
      @Bean
      public LoadBalancerInterceptor ribbonInterceptor(
            LoadBalancerClient loadBalancerClient,
            LoadBalancerRequestFactory requestFactory) {
         return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
      }
      // 如果没有 RestTemplateCustomizer 实例才会创建
      // 这里就就会为咱们所有的  restTemplate 实例添加 loadBalancerInterceptor 拦截器
      @Bean
      @ConditionalOnMissingBean
      public RestTemplateCustomizer restTemplateCustomizer(
            final LoadBalancerInterceptor loadBalancerInterceptor) {
         return restTemplate -> {
            List<ClientHttpRequestInterceptor> list = new ArrayList<>(
                  restTemplate.getInterceptors());
            list.add(loadBalancerInterceptor);
            restTemplate.setInterceptors(list);
         };
      }
   }
  // ...
}


针对下面的代码我们可以总结一下:


如果需要使用负载均衡,工程下面必须要有 RestTemplate 类, 然后Spring 容器中要有 LoadBalancerClient 的实例。


LoadBalancerClient 在 spring-cloud-netflix-ribbon 中只有一个实现类:


RibbonLoadBalancerClient


利用 Spring 的 SmartInitializingSingleton 拓展点,在 restTemplateCustomizer() 中为所有的 RestTemplate 添加 LoadBalancerInterceptor 拦截器


其实 LoadBalancer 的本质就是通过拦截器。利用 RestTemplate 的拓展点来实现请求服务的负载均衡。


LoadBalancerInterceptor


LoadBalancerInterceptor 拦截器会将请求交给 LoadBalancerClient 去处理,首先会选择一个 ILoadBalancer 的实现来处理获取和选择服务,然后通过 serviceName 和负载均衡算法去选择 Server 对象。最后执行请求。


public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
   // 负载均衡
   private LoadBalancerClient loadBalancer;
   // 构建请求 
   private LoadBalancerRequestFactory requestFactory;
   // ...
   @Override
   public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
         final ClientHttpRequestExecution execution) throws IOException {
      final URI originalUri = request.getURI();
      String serviceName = originalUri.getHost(); 
      return this.loadBalancer.execute(serviceName,
            this.requestFactory.createRequest(request, body, execution));
   }
}


RibbonLoadBalancerClient


我们通过跟踪 this.loadBalancer.execute 代码发现。最终所有的请求都交由

RibbonLoadBalancerClient 去处理。它实现了。LoadBalancerClient 接口, 代码如下:


public interface ServiceInstanceChooser {
  // 通过 serviceId 选择具体的服务实例
  ServiceInstance choose(String serviceId);
}
public interface LoadBalancerClient extends ServiceInstanceChooser {
  <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;
  <T> T execute(String serviceId, ServiceInstance serviceInstance,
         LoadBalancerRequest<T> request) throws IOException;
  // 将服务实例信息替换还具体的 IP 信息 
  URI reconstructURI(ServiceInstance instance, URI original);
}


我们先来分析 RibbonLoadBalancerClient 的 choose 方法


@Override
public ServiceInstance choose(String serviceId) {
   return choose(serviceId, null);
}
// 通过服务名选择具体的服务实例
public ServiceInstance choose(String serviceId, Object hint) {
   Server server = getServer(getLoadBalancer(serviceId), hint);
   if (server == null) {
      return null;
   }
   return new RibbonServer(serviceId, server, isSecure(server, serviceId),
         serverIntrospector(serviceId).getMetadata(server));
}
// 通过服务名选择一个负载均衡器, 默认是 `ZoneAwareLoadBalancer`
protected ILoadBalancer getLoadBalancer(String serviceId) {
   return this.clientFactory.getLoadBalancer(serviceId);
}
// 获取服务
protected Server getServer(ILoadBalancer loadBalancer) {
   return getServer(loadBalancer, null);
}
protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
   if (loadBalancer == null) {
      return null;
   }
   // Use 'default' on a null hint, or just pass it on?
   return loadBalancer.chooseServer(hint != null ? hint : "default");
}


LoadBalancerInterceptor 执行的时候是直接委托执行的 loadBalancer.execute() 这个方法:


// LoadBalancerRequest 是通过 LoadBalancerRequestFactory.createRequest(request, body, execution) 创建
// 它实现 LoadBalancerRequest 接口是用的一个匿名内部类,泛型类型是ClientHttpResponse
// 因为最终执行的显然还是执行器:ClientHttpRequestExecution.execute()
@Override
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
  return execute(serviceId, request, null);
}
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint) throws IOException {
  // 拿到负载均衡器,然后拿到一个serverInstance实例
  ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
  Server server = getServer(loadBalancer, hint);
  if (server == null) { // 若没找到就直接抛出异常。这里使用的是IllegalStateException这个异常
    throw new IllegalStateException("No instances available for " + serviceId);
  }
  // 把Server适配为RibbonServer  isSecure:客户端是否安全
  // serverIntrospector内省  参考配置文件:ServerIntrospectorProperties
  RibbonServer ribbonServer = new RibbonServer(serviceId, server,
      isSecure(server, serviceId), serverIntrospector(serviceId).getMetadata(server));
  //调用本类的重载接口方法
  return execute(serviceId, ribbonServer, request);
}
// 它的参数是 ServiceInstance --> 已经确定了唯一的Server实例
@Override
public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException {
  // 拿到 Server,RibbonServer 是 execute 时的唯一实现
  Server server = null;
  if (serviceInstance instanceof RibbonServer) {
    server = ((RibbonServer) serviceInstance).getServer();
  }
  if (server == null) {
    throw new IllegalStateException("No instances available for " + serviceId);
  }
  // 执行的上下文是和serviceId绑定的
  RibbonLoadBalancerContext context = this.clientFactory.getLoadBalancerContext(serviceId);
  ... 
  // 真正的向server发送请求,得到返回值
  // 因为有拦截器,所以这里肯定说执行的是InterceptingRequestExecution.execute()方法
  // so会调用ServiceRequestWrapper.getURI(),从而就会调用reconstructURI()方法
    T returnVal = request.apply(serviceInstance);
    return returnVal;
  ... // 异常处理
}


returnVal 是一个 ClientHttpResponse,最后交给 handleResponse()方法来处理异常情况(若存在的话),若无异常就交给提取器提值:


responseExtractor.extractData(response),这样整个请求就算全部完成了。


ZoneAwareLoadBalancer


负载均衡器 ZoneAwareLoadBalancer 的类图结构如下图所示。它 DynamicServerListLoadBalancer 它的父类, 核心方法 重置和初始化:


restOfInit(clientConfig) 更新服务列表:updateListOfServers(); 这个方需要调用到 ServerList.getUpdatedListOfServers() 这里就会调用到具体的注册中心实现,以 Nacos 为例他的实现就是 NacosServerList#getUpdatedListOfServers();


  • 更新所有服务列表:updateAllServerList();
  • 设置所有服务列表 setServersList() ZoneAwareLoadBalancer 它的核心方法:
  • 选择服务实例 chooseServer()
  • 选择负载均衡器 getLoadBalancer
  • 选择区域内的服务实例:zoneLoadBalancer.chooseServer


Ribbon 总结


针对 @LoadBalanced 下的 RestTemplate 的使用,我总结如下:


  • 传入的String类型的url必须是绝对路径(http://...),否则抛出异常:
  • java.lang.IllegalArgumentException: URI is not absolute
  • serviceId 不区分大小写(http://order-service/...效果同http://OERDER-SERVICE/...)
  • serviceId 后请不要跟 port 端口号


最后,需要特别指出的是:标注有@LoadBalanced 的 RestTemplate 只能填写 serviceId 而不能再写 IP地址/域名去发送请求了, 若你的项目中两种 case 都有需要,需要定义多个 RestTemplate 分别应对不同的使用场景


Nacos 服务查询


客户端查询


如果我们使用默认的 Nacos 客户端,那么走的就是

NacosServerList#getUpdatedListOfServers();接口来查询服务列表。


public class NacosServerList extends AbstractServerList<NacosServer> {
  private NacosDiscoveryProperties discoveryProperties;
  @Override
  public List<NacosServer> getUpdatedListOfServers() {
    return getServers();
  }
    private List<NacosServer> getServers() {
    try {
      String group = discoveryProperties.getGroup();
      // discoveryProperties.namingServiceInstance() 
            // 最终通过反射获取 com.alibaba.nacos.client.naming.NacosNamingService 实例
            List<Instance> instances = discoveryProperties.namingServiceInstance()
          .selectInstances(serviceId, group, true);
      return instancesToServerList(instances);
    }
    catch (Exception e) {
      throw new IllegalStateException(
          "Can not get service instances from nacos, serviceId=" + serviceId,
          e);
    }
  }
}


然后调用 selectInstances 方法


@Override
public List<Instance> selectInstances(String serviceName, String groupName, List<String> clusters, boolean healthy,
                                      boolean subscribe) throws NacosException {
    ServiceInfo serviceInfo;
    // subscribe 默认传的是 true
    if (subscribe) {
        serviceInfo = hostReactor.getServiceInfo(NamingUtils.getGroupedName(serviceName, groupName),
                                                 StringUtils.join(clusters, ","));
    } else {
        serviceInfo = hostReactor
            .getServiceInfoDirectlyFromServer(NamingUtils.getGroupedName(serviceName, groupName),
                                              StringUtils.join(clusters, ","));
    }
    return selectInstances(serviceInfo, healthy);
}


其实核心的逻辑在 hostReactor.getServiceInfo 在查询服务信息里面会把当前的 serviceName、 clusters 转换为 key, 然后通过 getServiceInfo0 方法查询服务信息这里主要是查询的是本地的数据。


如果 null == serviceObj 会在 updateServiceNow 里面去调用 /instance/list接口查询服务信息


public ServiceInfo getServiceInfo(final String serviceName, final String clusters) {
        NAMING_LOGGER.debug("failover-mode: " + failoverReactor.isFailoverSwitch());
        String key = ServiceInfo.getKey(serviceName, clusters);
        if (failoverReactor.isFailoverSwitch()) {
            return failoverReactor.getService(key);
        }
        ServiceInfo serviceObj = getServiceInfo0(serviceName, clusters);
        if (null == serviceObj) {
            serviceObj = new ServiceInfo(serviceName, clusters);
            serviceInfoMap.put(serviceObj.getKey(), serviceObj);
            updatingMap.put(serviceName, new Object());
            updateServiceNow(serviceName, clusters);
            updatingMap.remove(serviceName);
        } else if (updatingMap.containsKey(serviceName)) {
            // UPDATE_HOLD_INTERVAL 为常量默认金辉进去
            if (UPDATE_HOLD_INTERVAL > 0) {
                // hold a moment waiting for update finish
                synchronized (serviceObj) {
                    try {
                        // 最大等待时间 5s, 在更新 serviceObj 之后, 就会执行 notifyAll()
                        // 方法入口 updateService(String serviceName, String clusters)
                        // 最大延迟 2s DEFAULT_DELAY = 1
                        serviceObj.wait(UPDATE_HOLD_INTERVAL);
                    } catch (InterruptedException e) {
                        NAMING_LOGGER
                                .error("[getServiceInfo] serviceName:" + serviceName + ", clusters:" + clusters, e);
                    }
                }
            }
        }
      // 通过 Schedule 更新 服务信息 
        scheduleUpdateIfAbsent(serviceName, clusters);
      // 获取最新的值
        return serviceInfoMap.get(serviceObj.getKey());
    }


代码看到这里我们不难理解,为什么第一次 Ribbon 调用的时候都会比较慢,因为它回去初始化服务列表,然后通过 Nacos Client 去 Nacos 查询服务实例信息。


服务端处理


服务端通过 /instance/list 接口来处理服务实例信息查询请求。首先服务实例信息都是被存储在 ConcurrentHashMap 中


/**
     * Map(namespace, Map(group::serviceName, Service)).
     */
    private final Map<String, Map<String, Service>> serviceMap = new ConcurrentHashMap<>();


在我们查询的过程中主要是通过 ServiceManager 来进行管理, 核心的入口方法在 InstanceController#doSrvIpxt 中


public ObjectNode doSrvIpxt(String namespaceId, String serviceName, String agent, String clusters, String clientIP,
            int udpPort, String env, boolean isCheck, String app, String tid, boolean healthyOnly) throws Exception {
        ClientInfo clientInfo = new ClientInfo(agent);
        ObjectNode result = JacksonUtils.createEmptyJsonNode();
        Service service = serviceManager.getService(namespaceId, serviceName);
        long cacheMillis = switchDomain.getDefaultCacheMillis();
        // now try to enable the push
        try {
            if (udpPort > 0 && pushService.canEnablePush(agent)) {
                pushService
                        .addClient(namespaceId, serviceName, clusters, agent, new InetSocketAddress(clientIP, udpPort),
                                pushDataSource, tid, app);
                cacheMillis = switchDomain.getPushCacheMillis(serviceName);
            }
        } catch (Exception e) {
            Loggers.SRV_LOG
                    .error("[NACOS-API] failed to added push client {}, {}:{}", clientInfo, clientIP, udpPort, e);
            cacheMillis = switchDomain.getDefaultCacheMillis();
        }
        if (service == null) {
            if (Loggers.SRV_LOG.isDebugEnabled()) {
                Loggers.SRV_LOG.debug("no instance to serve for service: {}", serviceName);
            }
            result.put("name", serviceName);
            result.put("clusters", clusters);
            result.put("cacheMillis", cacheMillis);
            result.replace("hosts", JacksonUtils.createEmptyArrayNode());
            return result;
        }
        checkIfDisabled(service);
        List<Instance> srvedIPs;
        // 查询所有的服务
        // 内部会更新服务列表
        // allInstances.addAll(persistentInstances);
        // allInstances.addAll(ephemeralInstances);
        srvedIPs = service.srvIPs(Arrays.asList(StringUtils.split(clusters, ",")));
        // filter ips using selector:
        if (service.getSelector() != null && StringUtils.isNotBlank(clientIP)) {
            srvedIPs = service.getSelector().select(clientIP, srvedIPs);
        }
        if (CollectionUtils.isEmpty(srvedIPs)) {
            if (Loggers.SRV_LOG.isDebugEnabled()) {
                Loggers.SRV_LOG.debug("no instance to serve for service: {}", serviceName);
            }
            if (clientInfo.type == ClientInfo.ClientType.JAVA
                    && clientInfo.version.compareTo(VersionUtil.parseVersion("1.0.0")) >= 0) {
                result.put("dom", serviceName);
            } else {
                result.put("dom", NamingUtils.getServiceName(serviceName));
            }
            result.put("name", serviceName);
            result.put("cacheMillis", cacheMillis);
            result.put("lastRefTime", System.currentTimeMillis());
            result.put("checksum", service.getChecksum());
            result.put("useSpecifiedURL", false);
            result.put("clusters", clusters);
            result.put("env", env);
            result.set("hosts", JacksonUtils.createEmptyArrayNode());
            result.set("metadata", JacksonUtils.transferToJsonNode(service.getMetadata()));
            return result;
        }
        Map<Boolean, List<Instance>> ipMap = new HashMap<>(2);
        ipMap.put(Boolean.TRUE, new ArrayList<>());
        ipMap.put(Boolean.FALSE, new ArrayList<>());
        for (Instance ip : srvedIPs) {
            ipMap.get(ip.isHealthy()).add(ip);
        }
        if (isCheck) {
            result.put("reachProtectThreshold", false);
        }
        double threshold = service.getProtectThreshold();
        if ((float) ipMap.get(Boolean.TRUE).size() / srvedIPs.size() <= threshold) {
            Loggers.SRV_LOG.warn("protect threshold reached, return all ips, service: {}", serviceName);
            if (isCheck) {
                result.put("reachProtectThreshold", true);
            }
            ipMap.get(Boolean.TRUE).addAll(ipMap.get(Boolean.FALSE));
            ipMap.get(Boolean.FALSE).clear();
        }
        if (isCheck) {
            result.put("protectThreshold", service.getProtectThreshold());
            result.put("reachLocalSiteCallThreshold", false);
            return JacksonUtils.createEmptyJsonNode();
        }
        ArrayNode hosts = JacksonUtils.createEmptyArrayNode();
        for (Map.Entry<Boolean, List<Instance>> entry : ipMap.entrySet()) {
            List<Instance> ips = entry.getValue();
            if (healthyOnly && !entry.getKey()) {
                continue;
            }
            for (Instance instance : ips) {
                // remove disabled instance:
                if (!instance.isEnabled()) {
                    continue;
                }
                ObjectNode ipObj = JacksonUtils.createEmptyJsonNode();
                ipObj.put("ip", instance.getIp());
                ipObj.put("port", instance.getPort());
                // deprecated since nacos 1.0.0:
                ipObj.put("valid", entry.getKey());
                ipObj.put("healthy", entry.getKey());
                ipObj.put("marked", instance.isMarked());
                ipObj.put("instanceId", instance.getInstanceId());
                ipObj.set("metadata", JacksonUtils.transferToJsonNode(instance.getMetadata()));
                ipObj.put("enabled", instance.isEnabled());
                ipObj.put("weight", instance.getWeight());
                ipObj.put("clusterName", instance.getClusterName());
                if (clientInfo.type == ClientInfo.ClientType.JAVA
                        && clientInfo.version.compareTo(VersionUtil.parseVersion("1.0.0")) >= 0) {
                    ipObj.put("serviceName", instance.getServiceName());
                } else {
                    ipObj.put("serviceName", NamingUtils.getServiceName(instance.getServiceName()));
                }
                ipObj.put("ephemeral", instance.isEphemeral());
                hosts.add(ipObj);
            }
        }
        result.replace("hosts", hosts);
        if (clientInfo.type == ClientInfo.ClientType.JAVA
                && clientInfo.version.compareTo(VersionUtil.parseVersion("1.0.0")) >= 0) {
            result.put("dom", serviceName);
        } else {
            result.put("dom", NamingUtils.getServiceName(serviceName));
        }
        result.put("name", serviceName);
        result.put("cacheMillis", cacheMillis);
        result.put("lastRefTime", System.currentTimeMillis());
        result.put("checksum", service.getChecksum());
        result.put("useSpecifiedURL", false);
        result.put("clusters", clusters);
        result.put("env", env);
        result.replace("metadata", JacksonUtils.transferToJsonNode(service.getMetadata()));
        return result;
    }


在上面的核心逻辑主要是:


  1. 调用 service.srvIPs 方法查询所有的服务实例信息
  2. Cluster#allIPs会将所有的服务注册信息写到服务注册列表。



相关实践学习
SLB负载均衡实践
本场景通过使用阿里云负载均衡 SLB 以及对负载均衡 SLB 后端服务器 ECS 的权重进行修改,快速解决服务器响应速度慢的问题
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
相关文章
|
2月前
|
Dubbo Cloud Native 应用服务中间件
阿里云的 Dubbo 和 Nacos 深度整合,提供了高效的服务注册与发现、配置管理等关键功能,简化了微服务治理,提升了系统的灵活性和可靠性。
在云原生时代,微服务架构成为主流。阿里云的 Dubbo 和 Nacos 深度整合,提供了高效的服务注册与发现、配置管理等关键功能,简化了微服务治理,提升了系统的灵活性和可靠性。示例代码展示了如何在项目中实现两者的整合,通过 Nacos 动态调整服务状态和配置,适应多变的业务需求。
64 2
|
2月前
|
数据管理 Nacos 开发者
"Nacos架构深度解析:一篇文章带你掌握业务层四大核心功能,服务注册、配置管理、元数据与健康检查一网打尽!"
【10月更文挑战第23天】Nacos 是一个用于服务注册发现和配置管理的平台,支持动态服务发现、配置管理、元数据管理和健康检查。其业务层包括服务注册与发现、配置管理、元数据管理和健康检查四大核心功能。通过示例代码展示了如何在业务层中使用Nacos,帮助开发者构建高可用、动态扩展的微服务生态系统。
139 0
|
2月前
|
SQL 关系型数据库 数据库连接
"Nacos 2.1.0版本数据库配置写入难题破解攻略:一步步教你排查连接、权限和配置问题,重启服务轻松解决!"
【10月更文挑战第23天】在使用Nacos 2.1.0版本时,可能会遇到无法将配置信息写入数据库的问题。本文将引导你逐步解决这一问题,包括检查数据库连接、用户权限、Nacos配置文件,并提供示例代码和详细步骤。通过这些方法,你可以有效解决配置写入失败的问题。
122 0
|
4月前
|
负载均衡 Java Nacos
SpringCloud基础1——远程调用、Eureka,Nacos注册中心、Ribbon负载均衡
微服务介绍、SpringCloud、服务拆分和远程调用、Eureka注册中心、Ribbon负载均衡、Nacos注册中心
SpringCloud基础1——远程调用、Eureka,Nacos注册中心、Ribbon负载均衡
|
5月前
|
存储 设计模式 缓存
OpenFeign集成Ribbon负载均衡-过滤和选择服务核心实现
该文章主要介绍了如何在OpenFeign中集成Ribbon以实现负载均衡,并详细分析了Ribbon中服务选择和服务过滤的核心实现过程。文章还涉及了Ribbon中负载均衡器(ILoadBalancer)和负载均衡策略(IRule)的初始化方式。
OpenFeign集成Ribbon负载均衡-过滤和选择服务核心实现
|
5月前
|
负载均衡 监控 Java
SpringCloud常见面试题(一):SpringCloud 5大组件,服务注册和发现,nacos与eureka区别,服务雪崩、服务熔断、服务降级,微服务监控
SpringCloud常见面试题(一):SpringCloud 5大组件,服务注册和发现,nacos与eureka区别,服务雪崩、服务熔断、服务降级,微服务监控
SpringCloud常见面试题(一):SpringCloud 5大组件,服务注册和发现,nacos与eureka区别,服务雪崩、服务熔断、服务降级,微服务监控
|
6月前
|
监控 安全 网络安全
inishConnect(..) failed: Connection refused,服务本地正常服务器网关报400,nacos服务实例不能下线
总之,这种问题需要通过多方面的检查和校验来定位和解决,并可能需要结合实际环境的具体情况来进行相应的调整。在处理分布式系统中这类问题时,耐心和细致的调试是必不可少的。
122 13
|
5月前
|
Kubernetes Nacos 微服务
【技术难题破解】Nacos v2.2.3 + K8s 微服务注册:强制删除 Pod 却不消失?!7步排查法+实战代码,手把手教你解决Nacos Pod僵死问题,让服务瞬间满血复活!
【8月更文挑战第15天】Nacos作为微服务注册与配置中心受到欢迎,但有时会遇到“v2.2.3 k8s 微服务注册nacos强制删除 pod不消失”的问题。本文介绍此现象及其解决方法,帮助开发者确保服务稳定运行。首先需检查Pod状态与事件、配置文件及Nacos配置,确认无误后可调整Pod生命周期管理,并检查Kubernetes版本兼容性。若问题持续,考虑使用Finalizers、审查Nacos日志或借助Kubernetes诊断工具。必要时,可尝试手动强制删除Pod。通过系统排查,通常能有效解决此问题。
121 0
|
5月前
|
Java Nacos 开发工具
【Nacos】心跳断了怎么办?!8步排查法+实战代码,手把手教你解决Nacos客户端不发送心跳检测问题,让服务瞬间恢复活力!
【8月更文挑战第15天】Nacos是一款广受好评的微服务注册与配置中心。然而,“客户端不发送心跳检测”的问题时有发生,可能导致服务实例被视为离线。本文介绍如何排查此类问题:确认Nacos服务器地址配置正确;检查网络连通性;查看客户端日志;确保Nacos SDK版本兼容;调整心跳检测策略;验证服务实例注册状态;必要时重启应用;检查影响行为的环境变量。通过这些步骤,通常可定位并解决问题,保障服务稳定运行。
324 0
|
5月前
|
网络安全 Nacos 开发者
【Nacos】神操作!节点提示暂时不可用?别急!7步排查法+实战代码,手把手教你解决Nacos服务实例状态异常,让服务瞬间满血复活!
【8月更文挑战第15天】Nacos作为微服务注册与配置中心,虽广受好评,但仍可能遇到“节点提示暂时不可用”的问题。本文解析此现象及其解决之道。首先需理解该提示意味着服务实例未能正常响应。解决步骤包括:检查服务状态与网络、审查Nacos配置、调整健康检查策略、重启服务及分析日志。通过系统化排查,可有效保障服务稳定运行。
213 0

热门文章

最新文章