写点什么

Spring Cloud 源码分析之 Eureka 篇第七章:续约

作者:程序员欣宸
  • 2022 年 7 月 10 日
  • 本文字数:3602 字

    阅读完需:约 12 分钟

Spring Cloud源码分析之Eureka篇第七章:续约

欢迎访问我的 GitHub

这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos



  1. 周期性更新服务列表;

  2. 周期性服务续约;

  3. 服务注册逻辑;


  • 本章学习的是周期性服务续约的相关代码,对应用如何将自身信息注册到 Eureka 进行深入了解

概览

  • 以下图片来自 Netflix 官方,图中显示 Eureka Client 会发起 Renew 向注册中心做周期性续约,这样其他 Eureka client 通过 Get Registry 请求就能获取到新注册应用的相关信息:


关于源码版本

  • 本次分析的 Spring Cloud 版本为 Edgware.RELEASE,对应的 eureka-client 版本为 1.7.0;

来自官方文档的指导信息

  • 最准确的说明信息来自 Netflix 的官方文档,地址:https://github.com/Netflix/eureka/wiki/Understanding-eureka-client-server-communication#renew

  • 学习源码之前先看文档可以确定大方向,不会因为陷入源码细节导致偏离学习目标,如下图所示:



  • 我的理解:


  1. Eureka client 每隔三十秒发送一次心跳到 Eureka server,这就是续约;

  2. Eureka client 续约的目的是告诉 Eureka server 自己还活着;

  3. Eureka server 若 90 秒内未收到心跳,就从自己的服务列表中剔除该 Eureka client;

  4. 建议不要改变心跳间隔,因为 Eureka server 是通过心跳来判断 Eureka client 是否正常;

源码分析

  • 首先回顾 com.netflix.discovery.DiscoveryClient 类的 initScheduledTasks 方法,Eureka client 在启动的时侯都会执行此方法,如下方所示,已经略去了周期性更新服务列表相关的代码:


private void initScheduledTasks() {        //略去获取服务列表相关代码        ...
if (clientConfig.shouldRegisterWithEureka()) { //心跳间隔 int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs(); //周期性任务执行超时的时候,会将超时时间翻倍,但不会超过最大时间,这个最大时间是由expBackOffBound决定的 int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound(); logger.info("Starting heartbeat executor: " + "renew interval is: " + renewalIntervalInSecs);
//启动心跳的周期性任务 scheduler.schedule( new TimedSupervisorTask( "heartbeat", scheduler, heartbeatExecutor, renewalIntervalInSecs, TimeUnit.SECONDS, expBackOffBound, new HeartbeatThread() ), renewalIntervalInSecs, TimeUnit.SECONDS); //略去服务注册相关代码 ...
复制代码


  • 上述代码可以看出,续租逻辑在 HeartbeatThread 实例中,交给 TimedSupervisorTask 实例进行周期性调用,有关 TimedSupervisorTask 的功能和细节,请参考《Eureka的TimedSupervisorTask类(自动调节间隔的周期性任务)》

  • HeartbeatThread 类中,通过调用 renew 方法实现续租,如下代码所示,方法注释已说明是 Restfult 请求来实现的,对应 Eureka server 的返回信息 httpResponse,除了检查返回码是否等于 200 就没有任何作用了,想想也是如此,30 秒一次的心跳,不论是请求还是响应都应该尽量简洁,降低服务器和网络的压力:


/**     * Renew with the eureka service by making the appropriate REST call     */    boolean renew() {        EurekaHttpResponse<InstanceInfo> httpResponse;        try {            //发Restful请求,即心跳            httpResponse = eurekaTransport.registrationClient.sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, null);            logger.debug("{} - Heartbeat status: {}", PREFIX + appPathIdentifier, httpResponse.getStatusCode());            //404错误会触发注册逻辑            if (httpResponse.getStatusCode() == 404) {                REREGISTER_COUNTER.increment();                logger.info("{} - Re-registering apps/{}", PREFIX + appPathIdentifier, instanceInfo.getAppName());                return register();            }            //返回码200表示心跳成功            return httpResponse.getStatusCode() == 200;        } catch (Throwable e) {            logger.error("{} - was unable to send heartbeat!", PREFIX + appPathIdentifier, e);            return false;        }    }
复制代码


  • 继续展开上面代码段中的 eurekaTransport.registrationClient.sendHeartBeat 方法,源码在 EurekaHttpClientDecorator 类中:


@Override    public EurekaHttpResponse<InstanceInfo> sendHeartBeat(final String appName,                                                          final String id,                                                          final InstanceInfo info,                                                          final InstanceStatus overriddenStatus) {        return execute(new RequestExecutor<InstanceInfo>() {            @Override            public EurekaHttpResponse<InstanceInfo> execute(EurekaHttpClient delegate) {                //网络处理委托给代理类完成                return delegate.sendHeartBeat(appName, id, info, overriddenStatus);            }
@Override public RequestType getRequestType() { //请求类型为心跳 return RequestType.SendHeartBeat; } }); }
复制代码


  • 继续展开 delegate.sendHeartBeat,多层调用一路展开,最终由 JerseyApplicationClient 类来完成操作,对应源码在父类 AbstractJerseyEurekaHttpClient 中,如下所示,主要工作是利用 jersey 库的 Restful Api 将自身的信息 PUT 到 Eureka server,注意:这里不是 POST,也不是 GET,而是 PUT:


    @Override    public EurekaHttpResponse<InstanceInfo> sendHeartBeat(String appName, String id, InstanceInfo info, InstanceStatus overriddenStatus) {        String urlPath = "apps/" + appName + '/' + id;        ClientResponse response = null;        try {            //请求参数有两个:Eureka client自身状态、自身关键信息(状态、元数据等)最后一次变化的时间            WebResource webResource = jerseyClient.resource(serviceUrl)                    .path(urlPath)                    .queryParam("status", info.getStatus().toString())                    .queryParam("lastDirtyTimestamp", info.getLastDirtyTimestamp().toString());            if (overriddenStatus != null) {                webResource = webResource.queryParam("overriddenstatus", overriddenStatus.name());            }            Builder requestBuilder = webResource.getRequestBuilder();            addExtraHeaders(requestBuilder);            //注意:这里不是POST,也不是GET,而是PUT            response = requestBuilder.put(ClientResponse.class);            EurekaHttpResponseBuilder<InstanceInfo> eurekaResponseBuilder = anEurekaHttpResponse(response.getStatus(), InstanceInfo.class).headers(headersOf(response));            if (response.hasEntity()) {                eurekaResponseBuilder.entity(response.getEntity(InstanceInfo.class));            }            return eurekaResponseBuilder.build();        } finally {            if (logger.isDebugEnabled()) {                logger.debug("Jersey HTTP PUT {}/{}; statusCode={}", serviceUrl, urlPath, response == null ? "N/A" : response.getStatus());            }            if (response != null) {                response.close();            }        }    }
复制代码


  • 至此,Eureka client 向服务续租的源码就分析完毕了,过程相对简单,DiscoveryClient、TimedSupervisorTask、JerseyApplicationClient 等实例各司其职,定时发送 PUT 请求到 Eureka server;

欢迎关注 InfoQ:程序员欣宸

学习路上,你不孤单,欣宸原创一路相伴...

发布于: 刚刚阅读数: 3
用户头像

搜索"程序员欣宸",一起畅游Java宇宙 2018.04.19 加入

前腾讯、前阿里员工,从事Java后台工作,对Docker和Kubernetes充满热爱,所有文章均为作者原创,个人Github:https://github.com/zq2599/blog_demos

评论

发布
暂无评论
Spring Cloud源码分析之Eureka篇第七章:续约_Java_程序员欣宸_InfoQ写作社区