Su的技术博客

  • 首页
  • Java
  • MySQL
  • DDD
  • 事故复盘
  • 架构方案
  • Other
  • 工具
  • 打赏
  • 关于
  1. 首页
  2. Java
  3. 正文
                           

【原创】Eureka源码剖析之四:服务续约

2020-01-23 31点热度 0人点赞 0条评论

 

这里主要看下Eureka关于服务续约的源码,其实大致跟服务注册流程类似,不过更多细节和逻辑还是很大不同的。

 

〓Eureka Client
// 在DiscoveryClient里有renew方法,大概知道其是服务续约的入口。renew使用http的方式发送心跳给服务端,如果服务端返回404,说明是某些原因造成服务是没有注册成功,那么就会再次调用register注册接口进行注册。
@Singleton
public class DiscoveryClient implements EurekaClient {

    ...
    boolean renew() {
        EurekaHttpResponse<InstanceInfo> httpResponse;
        try {
            httpResponse = eurekaTransport.registrationClient.sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, null);
            logger.debug("{} - Heartbeat status: {}", PREFIX + appPathIdentifier, httpResponse.getStatusCode());
            if (httpResponse.getStatusCode() == 404) {
                REREGISTER_COUNTER.increment();
                logger.info("{} - Re-registering apps/{}", PREFIX + appPathIdentifier, instanceInfo.getAppName());
                long timestamp = instanceInfo.setIsDirtyWithTime();
                boolean success = register();
                if (success) {
                    instanceInfo.unsetIsDirty(timestamp);
                }
                return success;
            }
            return httpResponse.getStatusCode() == 200;
        } catch (Throwable e) {
            logger.error("{} - was unable to send heartbeat!", PREFIX + appPathIdentifier, e);
            return false;
        }
    }
    ...
}

// 这里使用Jersey put的方式发送心跳sendHeartBeat
public class JerseyReplicationClient extends AbstractJerseyEurekaHttpClient implements HttpReplicationClient {
    ...
    /**
     * Compared to regular heartbeat, in the replication channel the server may return a more up to date
     * instance copy.
     */
    @Override
    public EurekaHttpResponse<InstanceInfo> sendHeartBeat(String appName, String id, InstanceInfo info, InstanceStatus overriddenStatus) {
        String urlPath = "apps/" + appName + '/' + id;
        ClientResponse response = null;
        try {
            WebResource webResource = jerseyClient.getClient().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);
            response = requestBuilder.accept(MediaType.APPLICATION_JSON_TYPE).put(ClientResponse.class);
            InstanceInfo infoFromPeer = null;
            if (response.getStatus() == Status.CONFLICT.getStatusCode() && response.hasEntity()) {
                infoFromPeer = response.getEntity(InstanceInfo.class);
            }
            return anEurekaHttpResponse(response.getStatus(), infoFromPeer).type(MediaType.APPLICATION_JSON_TYPE).build();
        } finally {
            if (logger.isDebugEnabled()) {
                logger.debug("[heartbeat] Jersey HTTP PUT {}; statusCode={}", urlPath, response == null ? "N/A" : response.getStatus());
            }
            if (response != null) {
                response.close();
            }
        }
    }
    ...
}

 

接下来再看哪里调用了它:
 /**
 * The heartbeat task that renews the lease in the given intervals.
 * 初始化定时任务时,定时调度心跳线程进行续约服务的调用
 */
private class HeartbeatThread implements Runnable {

    public void run() {
        if (renew()) {
            lastSuccessfulHeartbeatTimestamp = System.currentTimeMillis();
        }
    }
}

 // Heartbeat timer
scheduler.schedule(
        new TimedSupervisorTask(
                "heartbeat",
                scheduler,
                heartbeatExecutor,
                renewalIntervalInSecs,
                TimeUnit.SECONDS,
                expBackOffBound,
                new HeartbeatThread()
        ),
        renewalIntervalInSecs, TimeUnit.SECONDS);

 // default size of 2 - 1 each for heartbeat and cacheRefresh
scheduler = Executors.newScheduledThreadPool(2,
    new ThreadFactoryBuilder()
            .setNameFormat("DiscoveryClient-%d")
            .setDaemon(true)
            .build());

 

跟踪了上面的源码我们知道:
服务续约是用定时任务做心跳HeartbeatThread来实现,scheduler定时器使用了两个线程,1个是给刷新缓存服务,1个是给服务续约。
LEASE_RENEWAL_INTERVAL_SECONDS = 30,续约服务是定时30秒进行服务续约。
接下来,再看下服务端源码:
〓Eureka Server
@Singleton
public class PeerAwareInstanceRegistryImpl extends AbstractInstanceRegistry implements PeerAwareInstanceRegistry {
    ...
    public boolean renew(final String appName, final String id, final boolean isReplication) {
        if (super.renew(appName, id, isReplication)) {
            replicateToPeers(Action.Heartbeat, appName, id, null, null, isReplication);
            return true;
        }
        return false;
    }
    ...
}

// 从这里看出服务端接收客户端的参数一致,也说明这里是接收客户端put请求的续约服务。服务端校验了dirty时间戳,并且将服务端的状态进行覆盖重写。
@Produces({"application/xml", "application/json"})
public class InstanceResource {
    ...
    @PUT
    public Response renewLease(
            @HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication,
            @QueryParam("overriddenstatus") String overriddenStatus,
            @QueryParam("status") String status,
            @QueryParam("lastDirtyTimestamp") String lastDirtyTimestamp) {
        boolean isFromReplicaNode = "true".equals(isReplication);
        boolean isSuccess = registry.renew(app.getName(), id, isFromReplicaNode);

        // Not found in the registry, immediately ask for a register
        if (!isSuccess) {
            logger.warn("Not Found (Renew): {} - {}", app.getName(), id);
            return Response.status(Status.NOT_FOUND).build();
        }
        // Check if we need to sync based on dirty time stamp, the client
        // instance might have changed some value
        Response response = null;
        if (lastDirtyTimestamp != null && serverConfig.shouldSyncWhenTimestampDiffers()) {
            response = this.validateDirtyTimestamp(Long.valueOf(lastDirtyTimestamp), isFromReplicaNode);
            // Store the overridden status since the validation found out the node that replicates wins
            if (response.getStatus() == Response.Status.NOT_FOUND.getStatusCode()
                    && (overriddenStatus != null)
                    && !(InstanceStatus.UNKNOWN.name().equals(overriddenStatus))
                    && isFromReplicaNode) {
                registry.storeOverriddenStatusIfRequired(app.getAppName(), id, InstanceStatus.valueOf(overriddenStatus));
            }
        } else {
            response = Response.ok().build();
        }
        logger.debug("Found (Renew): {} - {}; reply status={}" + app.getName(), id, response.getStatus());
        return response;
    }
    ...
}

 

总结

1)Eureka client启动定时任务作为心跳服务即续约服务,每30秒发送心跳http put请求给服务端;
2)Eureka server接收到客户端的请求会校验dirty时间戳(其实这个应该是为了避免时间钟回拨等问题造成时间不一致),之后会将服务端的状态进行覆盖重写进而更新。而服务端在启动的时候同时也有剔除任务,在客户端没有定时续约超过LEASE_EXPIRATION_DURATION_SECONDS = 90秒,则会进行自动剔除下线。
 


 

本文仅供学习!所有权归属原作者。侵删!文章来源: 搬运工来架构

标签: 原创 开源框架 源码 eureka 服务续约 注册中心
最后更新:2023-02-25

Cocodroid

专注Java后端,分享技术。

打赏 点赞
< 上一篇
下一篇 >

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

最新 热点 推荐
最新 热点 推荐
殷浩详解DDD 第四讲:领域层设计规范 既生@Resource,何生@Autowired? Go整洁架构实践 接口优化的常见方案实战总结 QQ音乐高可用架构体系 构建一个布隆过滤器 —— Building a Bloom filter
殷浩详解DDD 第四讲:领域层设计规范Redis为什么这么快?构建一个布隆过滤器 —— Building a Bloom filterQQ音乐高可用架构体系接口优化的常见方案实战总结Go整洁架构实践
笔记 | JVM内存区域结构:一计两栈一堆一区 DevOps 流水线 CI 成倍提速方案 笔记 | 面试官问我:TCP与UDP的区别 iOS请求访问文件网关服务图片接口异常问题的解决 笔记 | 网络编程基础:TCP如何保证可靠性 JAVA字节码增强解密(下)

@Autowired (1) @Resource (1) API网关 (1) ddd (6) DP (1) ElasticSearch (1) eureka (7) go (1) HTTP (1) IDEA (1) iOS (1) Java (8) JSR (1) QQ音乐 (1) repository (1) Spring (1) SQL优化 (1) 代理 (1) 依赖注入 (1) 同城双活 (1) 垃圾回收 (1) 定时任务 (1) 容灾 (1) 布隆过滤器 (1) 异地双活 (1) 接口优化 (1) 故障转移 (1) 数据库 (2) 整洁架构 (1) 文件网关 (1) 方案 (2) 服务续约 (1) 注册中心 (7) 流水账 (1) 流量 (1) 第五 (1) 线上案例 (1) 线上问题 (2) 缓存 (1) 缓存击穿 (1) 编译 (3) 网络 (3) 聊聊 (1) 订单 (1) 设计规范 (1) 详解 (1) 连接池 (1) 限流 (1) 领域驱动设计 (4) 高可用 (1)

COPYRIGHT © 2014-2023 verysu.com . ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang

粤ICP备15033072号-2