Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Nacos服务发现原理分析

Nacos服务发现原理分析

作者头像
用户10384376
发布于 2023-02-26 05:05:40
发布于 2023-02-26 05:05:40
1.3K00
代码可运行
举报
文章被收录于专栏:码出code码出code
运行总次数:0
代码可运行

微服务将自己的实例注册到nacos注册中心,nacos服务端存储了注册列表,然后通过ribbon调用服务,具体是如何调用?如果nacos服务挂了,还能正常调用服务吗?调用的服务列表发生变化,调用方是如何感知变化的?带着这些问题,来探索一下服务发现的原理。

版本 2.1.1

  • Nacos Server:2.1.1
  • spring-cloud-starter-alibaba:2.1.1.RELEASE
  • spring-boot:2.1.1.RELEASE
  • spring-cloud-starter-netflix-ribbon:2.1.1.RELEASE

客户端和服务端版本号都为2.1.1

从 Ribbon 讲起

使用ribbon来调用服务,就添加ribbon依赖:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>

ribbon依赖包含spring-cloud-commons依赖,而在spring-cloud-commons包中spring.factories自动配置LoadBalancerAutoConfiguration类:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();

@Bean
public LoadBalancerInterceptor ribbonInterceptor(
    LoadBalancerClient loadBalancerClient,
    LoadBalancerRequestFactory requestFactory) {
  return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}

只要标注了@LoadBalanced注解的restTemplates都会添加负载均衡拦截器LoadBalancerInterceptor

使用Ribbon组件调用服务:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
restTemplate.getForObject("http://service-name",String.class);

restTemplatehttp请求方法,最终会调用到doExecute方法。doExecute在发起http请求之前,会先执行LoadBalancerInterceptor负载均衡拦截器的intercept方法。该方法调用execute方法。

而在execute方法中,主要有两个方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
Server server = getServer(loadBalancer, hint);

execute先通过getLoadBalancer获取ILoadBalancer实例,然后再通过getServer获取Server实例。

getLoadBalancer最终会调用RibbonServerList接口,具体调用流程:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
getLoadBalancer() ->
ZoneAwareLoadBalancer -> 
DynamicServerListLoadBalancer -> 
restOfInit()->
updateListOfServers()->
ServerList.getUpdatedListOfServers()->

Nacos实现类NacosServerList实现了ServerList接口。

总之我们在进行微服务调用的时候,Ribbon最终会调用NacosServerList类中的getUpdatedListOfServers方法。

Nacos 获取服务

NacosServerList类的getUpdatedListOfServers方法调用了该类的getServers方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private List<NacosServer> getServers() {
  try {
    // 获取分组 
    String group = discoveryProperties.getGroup();
    // 重点,查询实例列表
    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);
  }
}

重点看NacosNamingService类的selectInstances方法,会调用以下selectInstances三个重载方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Override
public List<Instance> selectInstances(String serviceName, String groupName, boolean healthy) throws NacosException {
    return selectInstances(serviceName, groupName, healthy, true);
}
    
@Override
public List<Instance> selectInstances(String serviceName, String groupName, boolean healthy, boolean subscribe) throws NacosException {
    return selectInstances(serviceName, groupName, new ArrayList<String>(), healthy, subscribe);
}
    
@Override
public List<Instance> selectInstances(String serviceName, String groupName, List<String> clusters, boolean healthy, boolean subscribe) throws NacosException {

    ServiceInfo serviceInfo;
    // 默认订阅
    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);
}

最后一个selectInstances方法里面的hostReactor.getServiceInfo方法是获取服务的核心方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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());
        // 请求Nacos Server实例,并更新服务实例
        updateServiceNow(serviceName, clusters);
        updatingMap.remove(serviceName);

    } else if (updatingMap.containsKey(serviceName)) {

        if (UPDATE_HOLD_INTERVAL > 0) {
            // hold a moment waiting for update finish
            synchronized (serviceObj) {
                try {
                    serviceObj.wait(UPDATE_HOLD_INTERVAL);
                } catch (InterruptedException e) {
                    NAMING_LOGGER.error("[getServiceInfo] serviceName:" + serviceName + ", clusters:" + clusters, e);
                }
            }
        }
    }
    // 定时更新本地缓存
    scheduleUpdateIfAbsent(serviceName, clusters);

    return serviceInfoMap.get(serviceObj.getKey());
}

getServiceInfo是服务发现的核心方法,先查询serviceInfoMap集合中查询本地缓存,本地缓存查询不到就请求Nacos Server实例,并更新本地缓存。

请求Nacos Server实例,实际就是发送http请求Nacos Server

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public void updateServiceNow(String serviceName, String clusters) {
    ServiceInfo oldService = getServiceInfo0(serviceName, clusters);
    try {
        // 调用 Nacos Server 查询服务
        String result = serverProxy.queryList(serviceName, clusters, pushReceiver.getUDPPort(), false);
        // 结果不为空,更新缓存  
        if (StringUtils.isNotEmpty(result)) {
            processServiceJSON(result);
        }
    } catch (Exception e) {
        NAMING_LOGGER.error("[NA] failed to update serviceName: " + serviceName, e);
    } finally {
        if (oldService != null) {
            synchronized (oldService) {
                oldService.notifyAll();
            }
        }
    }
}

//向 Nacos Server发起 HTTP 列表查询
public String queryList(String serviceName, String clusters, int udpPort, boolean healthyOnly)throws NacosException {

    final Map<String, String> params = new HashMap<String, String>(8);
    params.put(CommonParams.NAMESPACE_ID, namespaceId);
    params.put(CommonParams.SERVICE_NAME, serviceName);
    params.put("clusters", clusters);
    params.put("udpPort", String.valueOf(udpPort));
    params.put("clientIP", NetUtils.localIP());
    params.put("healthyOnly", String.valueOf(healthyOnly));

    return reqAPI(UtilAndComs.NACOS_URL_BASE + "/instance/list", params, HttpMethod.GET);
}

queryList方法主要封装号请求参数,然后向Nacos Server服务端发送http请求。

当服务端实例发生改变时,Nacos Server会推送最新的实例给服务端。

服务发现是先获取本地缓存,如果没有本地缓存,就请求Nacos Server服务端获取数据,如果Nacos Server挂了,也不会影响服务的调用。

总结

  • Ribbon
    • 项目启动时,会创建一个负载均衡拦截器。
    • Ribbon发起服务请求开始,最终会调用到拦截器的拦截方法。
    • 拦截方法又调用ServerList获取实例接口,而NacosServerList实现获取实例列表。
  • Nacos调用服务
    • NacosServerList实现了获取服务实例列表。
    • NacosServerListselectInstances方法最终调用了hostReactor.getServiceInfo方法
    • getServiceInfo方法先从serviceInfoMap集合中获取本地缓存,如果本地缓存找不到,就请求Nacos Server获取服务实例,并更新本地缓存。
    • 获取服务之后,定时更新本地缓存。

参考

  • Spring Cloud nacos Ribbon整合源码分析
  • 服务发现:服务之间调用请求链路分析
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-02-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码出code 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
聊聊NacosNamingService的selectInstances
本文主要研究一下NacosNamingService的selectInstances
code4it
2019/10/08
8240
聊聊NacosNamingService的selectInstances
从实现原理来讲,Nacos 为什么这么强?
这位朋友的简历上写了:对Spring、Spring Cloud Alibaba、MyBatis等源码有深入研究
田维常
2024/04/18
2180
从实现原理来讲,Nacos 为什么这么强?
通过 Ribbon 查询 Nacos 服务实例
Nacos 提供了开放 API 可通过 /nacos/v1/ns/instance/list 获取服务列表。如果我们采用 spring-cloud 方式去获取服务,最终会通过 Nacos Client + loadbalancer 的方式进行客户端负载均衡。
没有故事的陈师傅
2021/08/13
1.9K0
Nacos 为什么这么强?
点击上方“芋道源码”,选择“设为星标” 管她前浪,还是后浪? 能浪的浪,才是好浪! 每天 10:33 更新文章,每天掉亿点点头发... 源码精品专栏 原创 | Java 2021 超神之路,很肝~ 中文详细注释的开源项目 RPC 框架 Dubbo 源码解析 网络应用框架 Netty 源码解析 消息中间件 RocketMQ 源码解析 数据库中间件 Sharding-JDBC 和 MyCAT 源码解析 作业调度中间件 Elastic-Job 源码解析 分布式事务中间件 TCC-Transaction
芋道源码
2022/10/09
4740
Nacos 为什么这么强?
微服务组件-----Spring Cloud Alibaba 注册中心 Nacos源码(1.4.x版本)分析
【1】服务注册:Nacos Client会通过发送REST请求的方式向Nacos Server注册自己的服务,提供自身的元数据,比如ip地址、端口等信息。Nacos Server接收到注册请求后,就会把这些元数据信息存储在一个双层的内存Map中。
忧愁的chafry
2022/10/30
1.1K0
微服务组件-----Spring Cloud Alibaba 注册中心 Nacos源码(1.4.x版本)分析
聊聊NacosNamingService的selectOneHealthyInstance
本文主要研究一下NacosNamingService的selectOneHealthyInstance
code4it
2019/10/09
1K0
聊聊NacosNamingService的selectOneHealthyInstance
Nacos1# 服务注册与发现客户端示例与源码解析(一)
Nacos在业界注册中心的选型中举足轻重,值得去深入分析和研究。本文就注册和发现客户端的初始话逻辑从源码角度分析其做了什么事情,另外,其服务发现的设计架构可作为我们相似场景设计的模型作为参考。源码分析较长,暂时用不到的直接看内容提要即可。
瓜农老梁
2021/06/10
2.1K0
阿里二面:使用 Nacos 做注册中心怎么做优雅发布?
Service Provider 启动后注册到 Nacos Server,Service Consumer 则从 Nacos Server 拉取服务列表,根据一定算法选择一个 Service Provider 来发送请求。
jinjunzhu
2023/08/18
7300
阿里二面:使用 Nacos 做注册中心怎么做优雅发布?
Nacos服务注册与发现源码
- https://cloud.tencent.com/developer/article/1881617
eeaters
2021/09/24
1.6K0
Nacos服务注册与发现源码
06篇 Nacos Client本地缓存及故障转移
本篇文章我们来通过源码分析一下Nacos的本地缓存及故障转移功能,涉及到核心类为ServiceInfoHolder和FailoverReactor。
程序新视界
2021/12/07
2.9K0
06篇 Nacos Client本地缓存及故障转移
聊聊nacos-sdk-go的NamingClient
nacos-sdk-go-v0.3.2/clients/naming_client/naming_client.go
code4it
2020/06/24
7110
聊聊nacos-sdk-go的NamingClient
03篇 Nacos Client服务发现源码分析
本篇带大家通过源码层面分析一下Nacos Client的服务发现的路程,事实可能并不像你想象的那样简单。
程序新视界
2021/12/07
9700
03篇 Nacos Client服务发现源码分析
golang 源码分析:nacos服务发现
https://github.com/alibaba/nacos 是阿里开源的服务发现和配置同步组件,上手非常容易,我们介绍下如何部署,然后看下nacos提供的golang sdk:https://github.com/nacos-group/nacos-sdk-go如何使用,分析下具体的源码实现。
golangLeetcode
2022/12/17
8380
golang 源码分析:nacos服务发现
08篇 要给Nacos的UDP通信功能点个赞
Nacos在服务注册功能中使用到了UDP的通信方式,主要功能就是用来辅助服务实例变化时对客户端进行通知。然而,对于大多数使用Nacos的程序员来说,可能还不知道这个功能,更别说灵活运用了。
程序新视界
2021/12/07
1K0
08篇 要给Nacos的UDP通信功能点个赞
05篇 Nacos Client服务订阅之事件机制剖析
上篇文章,我们分析了Nacos客户端订阅的核心流程:Nacos客户端通过一个定时任务,每6秒从注册中心获取实例列表,当发现实例发生变化时,发布变更事件,订阅者进行业务处理,然后更新内存中和本地的缓存中的实例。
程序新视界
2021/12/07
1.3K0
05篇 Nacos Client服务订阅之事件机制剖析
04篇 Nacos Client服务订阅机制之【核心流程】
说起Nacos的服务订阅机制,对此不了解的朋友,可能感觉非常神秘,这篇文章就大家深入浅出的了解一下Nacos 2.0客户端的订阅实现。由于涉及到的内容比较多,就分几篇来讲,本篇为第一篇。
程序新视界
2021/12/07
1.7K0
04篇 Nacos Client服务订阅机制之【核心流程】
云原生nacos之服务发现SDK
注册实例 描述 注册一个实例到服务。 void registerInstance(String serviceName, String ip, int port) throws NacosException; void registerInstance(String serviceName, String ip, int port, String clusterName) throws NacosException; void registerInstance(String serviceName
一个风轻云淡
2022/11/15
5880
云原生nacos之服务发现SDK
聊聊NacosNamingService的subscribe及unsubscribe
本文主要研究一下NacosNamingService的subscribe及unsubscribe
code4it
2019/10/11
2K0
聊聊NacosNamingService的subscribe及unsubscribe
Nacos 服务注册源码分析
本文我们一起以源码的维度来分析 Nacos 做为服务注册中心的服务注册过程,我会以服务端、客户端两个角度来进行分析,Nacos 客户端我主要是采用 spring-cloud-alibaba 作为核心的客户端组件。对于 Nacos 服务端我会讲解到, Nacos 如何实现 AP/CP 两种模式共存的,以及如何区分的。最后还会分享我在源码调试过程中如何定位核心类的一点经验。
没有故事的陈师傅
2021/07/21
9560
一个Spring Boot Admin 监控多个Nacos集群
我们有多个系统,每个系统一个集群,每个集群都部署了自己的Spring Boot Admin(以下简称Admin),用起来不仅不方便,私有化部署的时候还得多部署几个服务,为了解决这个问题,我想到了是否可以用一个Admin同时监控多个集群,这里集群指监控Nacos集群。
阿提说说
2024/02/13
3400
推荐阅读
相关推荐
聊聊NacosNamingService的selectInstances
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验