Java面试问题复盘: springCloud中如何通过服务名找到对应的IP原理解析

springCloud中如何通过服务名找到对应的ip

原文很详细,建议两篇一起看,以下内容整合出核心部分
spring-cloud-openfeign 源码解析
Spring Cloud Ribbon源码深度解析(没有源码阅读经验也能看懂)

面试官方答案:

首先关于这个问题的专家官方解答在这里

在Spring Cloud Alibaba环境中,Feign调用时通过服务名到Nacos中寻址的过程可以分为以下几个步骤:

  1. 服务启动与注册: 当一个服务应用(包含Feign客户端)启动时,它会根据配置的spring.cloud.nacos.discovery.*属性自动向Nacos服务注册中心注册。这通常包括服务名(spring.application.name)、Nacos服务器地址(spring.cloud.nacos.discovery.server-addr)以及可能的命名空间ID和服务分组等信息。一旦注册成功,Nacos中就会记录该服务的实例信息,包括IP地址、端口号及健康状态等。

  2. Feign客户端配置: 在使用Feign进行服务间调用前,需要在Feign客户端接口上添加相应的注解(如@FeignClient(“serviceName”)),其中”serviceName”即是要调用的服务名。这个服务名必须与目标服务在Nacos中注册的服务名匹配。

  3. 服务发现: 当Feign发起一次远程调用时,它依赖于Spring Cloud的负载均衡器(默认是Ribbon)。在初始化或每次请求时,Feign客户端会通过Spring Cloud的DiscoveryClient接口查询Nacos服务注册中心,以获取目标服务的所有可用实例列表。这个过程依据服务名进行查找,同时也考虑命名空间和服务分组的配置,确保找到的是正确的服务实例集合。

  4. 负载均衡与路由: 获取到服务实例列表后,负载均衡器(Ribbon)会根据内置的策略(如轮询、随机等)选择一个实例。选定实例后,Feign构建HTTP请求并直接与该实例通信。这个过程实现了服务路由,即根据服务名寻址到具体的实例地址并完成调用。

  5. 健康检查与重试: 在实际调用过程中,如果选定的实例不可用,Feign和Ribbon会根据配置的重试策略尝试其他实例,确保服务调用的健壮性。同时,Nacos也会持续监控服务实例的健康状态,及时剔除不健康的实例。

源码解析:

重点在第三步和第四步,以下是详细解读:
在这里插入图片描述
当ZoneAwareLoadBalancer在初始化时,会调用父类DynamicServerListLoadBalancer的构造方法,代码如下:

public DynamicServerListLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping, ServerList<T> serverList, ServerListFilter<T> filter, ServerListUpdater serverListUpdater) {
        super(clientConfig, rule, ping);
        this.isSecure = false;
        this.useTunnel = false;
        this.serverListUpdateInProgress = new AtomicBoolean(false);
        this.updateAction = new NamelessClass_1();
        this.serverListImpl = serverList;
        this.filter = filter;
        this.serverListUpdater = serverListUpdater;
        if (filter instanceof AbstractServerListFilter) {
            ((AbstractServerListFilter)filter).setLoadBalancerStats(this.getLoadBalancerStats());
        }

        this.restOfInit(clientConfig);//关键
    }

void restOfInit(IClientConfig clientConfig) {
    boolean primeConnection = this.isEnablePrimingConnections();

    this.setEnablePrimingConnections(false);
	
	//开启动态更新Server
    enableAndInitLearnNewServersFeature(); 
    
 	//更新Server列表
    updateListOfServers(); 
 
 
    if (primeConnection && this.getPrimeConnections() != null) {
        this.getPrimeConnections()
            .primeConnections(getReachableServers());
    }
    this.setEnablePrimingConnections(primeConnection);
    LOGGER.info("DynamicServerListLoadBalancer for client {} initialized: {}", clientConfig.getClientName(), this.toString());
}

enableAndInitLearnNewServersFeature
这是个定时任务,每隔30s执行一次

    public synchronized void start(final ServerListUpdater.UpdateAction updateAction) {
        if (this.isActive.compareAndSet(false, true)) {
            Runnable wrapperRunnable = new Runnable() {
                public void run() {
                    if (!PollingServerListUpdater.this.isActive.get()) {
                        if (PollingServerListUpdater.this.scheduledFuture != null) {
                            PollingServerListUpdater.this.scheduledFuture.cancel(true);
                        }

                    } else {
                        try {
                            updateAction.doUpdate();
                            PollingServerListUpdater.this.lastUpdated = System.currentTimeMillis();
                        } catch (Exception var2) {
                            PollingServerListUpdater.logger.warn("Failed one update cycle", var2);
                        }

                    }
                }
            };
            this.scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay(wrapperRunnable, this.initialDelayMs, this.refreshIntervalMs, TimeUnit.MILLISECONDS);
        } else {
            logger.info("Already active, no-op");
        }

    }

在这里插入图片描述

updateListOfServers
全量更新一次服务列表。

   public void enableAndInitLearnNewServersFeature() {
       LOGGER.info("Using serverListUpdater {}", this.serverListUpdater.getClass().getSimpleName());
       this.serverListUpdater.start(this.updateAction);
   }

	public void updateListOfServers() {
	    List<T> servers = new ArrayList<T>();
	    if (serverListImpl != null) {
	        servers = serverListImpl.getUpdatedListOfServers();
	        LOGGER.debug("List of Servers for {} obtained from Discovery client: {}",
	                     getIdentifier(), servers);
	 
	        if (filter != null) {
	            servers = filter.getFilteredListOfServers(servers);
	            LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}",
	                         getIdentifier(), servers);
        }
    }
    updateAllServerList(servers);
}

在这里插入图片描述
调用nacosServerList,在这里就会获取到服务名所对应的host
在这里插入图片描述
updateAllServerList
更新所有Server到本地缓存中。

protected void updateAllServerList(List<T> ls) {
        if (this.serverListUpdateInProgress.compareAndSet(false, true)) {
            try {
                Iterator var2 = ls.iterator();

                while(var2.hasNext()) {
                    T s = (Server)var2.next();
                    s.setAlive(true);
                }

                this.setServersList(ls);
                // 进行心跳健康检测。
                super.forceQuickPing();
            } finally {
                this.serverListUpdateInProgress.set(false);
            }
        }

    }

完整链路:

  1. 通过注解 @EnableFeignClients 来集成feign客户端。这个注解开启了FeignClient的解析过程。这个注解的声明如下,它用到了一个@Import注解,Import是用来导入一个配置类的,FeignClientsRegistrar实现了ImportBeanDefinitionRegistrar,它是一个动态注入bean的接口,Spring Boot启动的时候,会去调用这个类中的registerBeanDefinitions来实现动态Bean的装载,将其添加到spring的ioc容器中。

  2. FeignClient标注的这个接口,会通过FeignClientFactoryBean.getObject()这个方法获得一个代理对象,也就是说OpenFeign最终返回的是一个 ReflectiveFeign.FeignInvocationHandler 的对象。那么当客户端发起请求时,会进入到 FeignInvocationHandler.invoke 方法中,它是一个动态代理的实现。

							FeignClientFactoryBean.getObject
							           |
							    Targeter.target()
							           |
							ReflectiveFeign.newInstance() //有一行代码是
							           |
Map<String, InvocationHandlerFactory.MethodHandler> nameToHandler = this.targetToHandlersByName.apply(target);
                                      

targetToHandlersByName.apply(target) :根据Contract协议规则,会解析接口方法上的注解,从而解析出方法粒度的特定的配置信息,然后生产一个SynchronousMethodHandler 然后需要维护一个<method,MethodHandler>的map,放入InvocationHandler的实现FeignInvocationHandler中。
在这里插入图片描述

  1. 而接着,在FeignInvocationHandler.invoke方法中,会调用 this.dispatch.get(method)).invoke(args) 。this.dispatch.get(method) 会返回一个SynchronousMethodHandler,进行拦截处理。这个方法会根据参数生成完成的RequestTemplate对象,这个对象是Http请求的模版。
    在这里插入图片描述在这里插入图片描述
  2. 执行 executeAndDecode() 方法,该方法通过RequestTemplate生成Request请求对象,然后利用Http Client获取response,来获取响应信息。在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    然后执行executeWithLoadBalancer,进入submit(),调用selectServer的方法获取server
    在这里插入图片描述
    在这里插入图片描述
    lb.chooseServer() - > BaseLoadBalancer.chooseServer() 获取server,其中包含ip
    在这里插入图片描述
    在这里插入图片描述
    默认情况下,rule的实现为ZoneAvoidanceRule,ZoneAvoidanceRule继承自PredicateBasedRule,它是在RibbonClientConfiguration这个配置类中定义的,代码如下:
    在这里插入图片描述
    这里的allServerList就是上述每隔30s就更新一次的那个参数。
    在这里插入图片描述
    然后执行回调方法call(),call方法中执行execute()方法。
    在这里插入图片描述
    利用 JDK 提供的 HttpURLConnection 发起远程的 HTTP通讯。
    在这里插入图片描述
    在这里插入图片描述
  • 14
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值