图解+源码讲解 Eureka Client 服务注册流程

1,567 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第5天,点击查看活动详情

Eureka Client 服务注册流程

决心是成功的开始 相关文章
eureka-server 项目结构分析
图解+源码讲解 Eureka Server 启动流程分析
图解+源码讲解 Eureka Client 启动流程分析
图解+源码讲解 Eureka Server 注册表缓存逻辑
图解+源码讲解 Eureka Client 拉取注册表流程
图解+源码讲解 Eureka Client 服务注册流程
图解+源码讲解 Eureka Client 心跳机制流程
图解+源码讲解 Eureka Client 下线流程分析
图解+源码讲解 Eureka Server 服务剔除逻辑
图解+源码讲解 Eureka Server 集群注册表同步机制

注册核心流程图

image.png

从哪里开始分析

    在客户端初始化的时候 clientConfig.shouldEnforceRegistrationAtInit() 初始化的时候是否开启强制注册,这个值默认是false所以不会进行注册的,是在后续的30s发送心跳机制任务中的renew()方法中进行注册的

if (clientConfig.shouldRegisterWithEureka() && 
    // clientConfig.shouldEnforceRegistrationAtInit() 默认是false
    clientConfig.shouldEnforceRegistrationAtInit()) {
    if (!register()) {
        throw new IllegalStateException("Registration error at startup. Invalid server response.");
    }
}

核心逻辑

    通过renew()发送心跳的时候返回找不到实例信息,所以再进行注册操作,通过 eurekaTransport.registrationClient 客户端进行发送心跳请求操作

1. 通过心跳定时任务发起注册

boolean renew() {
EurekaHttpResponse<InstanceInfo> httpResponse;
    httpResponse = eurekaTransport.registrationClient.
        sendHeartBeat(instanceInfo.getAppName(), 
                      instanceInfo.getId(), instanceInfo, null);
    if (httpResponse.getStatusCode() == Status.NOT_FOUND.getStatusCode()) {
        REREGISTER_COUNTER.increment();
        long timestamp = instanceInfo.setIsDirtyWithTime();
        boolean success = register(); // 注册实例
        if (success) {
            instanceInfo.unsetIsDirty(timestamp);
        }
        return success;
    }
    return httpResponse.getStatusCode() == Status.OK.getStatusCode();
}

    通过 register() 方法进行实例注册,通过 eurekaTransport.registrationClient 进行注册方法访问,访问的是AbstractJersey2EurekaHttpClient的 register 方法

boolean register() throws Throwable {
    EurekaHttpResponse<Void> httpResponse;
    httpResponse = eurekaTransport.registrationClient.register(instanceInfo);
    ....
}

    通过 jersey2 框架访问到了 eureka-core 项目下的 AbstractInstanceRegistry 的register方法

public void register(InstanceInfo registrant, int leaseDuration,
                     boolean isReplication) {
    /**
     * 通过服务名字从本地的 gMap 中获取一个服务实例信息
     */
    Map<String, Lease<InstanceInfo>> gMap = registry.get(registrant.getAppName());
    if (gMap == null) {
        // 如果本地gMap中没有当前要注册的实例的话创建一个 gNewMap 
        // 也就是 registry 中的一个服务实例
        final ConcurrentHashMap<String, Lease<InstanceInfo>> gNewMap = 
            new ConcurrentHashMap<String, Lease<InstanceInfo>>();
        // 将当前服务名字为key并且服务名字,value就是当前要注册的 gNewMap
        gMap = registry.putIfAbsent(registrant.getAppName(), gNewMap);
        if (gMap == null) {
            gMap = gNewMap;
        }
    }
    // 说白了上面就是创建了一个gMap中的一个实例
    gMap.put(registrant.getId(), lease);
}

2. 将当前实例放入到队列中

    这个注册队列和改变队列是在服务初始化的时候创建的,通过这个最近改变的队列可以监听最近改变的状态,新注册的队列和下线的队列主要是为了在页面中显示的,最近改变的队列就是为了在获取增量注册实例信息的时候使用的

/**
 * 将当前实例放入到最近注册的队列
 */
recentRegisteredQueue.add(new Pair<Long, String>(System.currentTimeMillis(),
             registrant.getAppName() + "(" + registrant.getId() + ")"));
// 设置类型为添加类型
registrant.setActionType(ActionType.ADDED);
/**
 * 添加也属于改变所以创建一个最近的改变对象【RecentlyChangedItem】放入到最近的改变队列中,
 * 时间戳是当前的系统时间
 */
recentlyChangedQueue.add(new RecentlyChangedItem(lease));

3. 无效本地缓存

/**
 * 当有新实例过来的时候无效当前读写缓存
 */
invalidateCache(registrant.getAppName(), registrant.getVIPAddress(),
                registrant.getSecureVipAddress());

    无效本地缓存操作就是通过 ResponseCacheImpl 的 invalidate 方法将其进行缓存中的一些key进行从缓存中移除,移除读写缓存中的key值

public void invalidate(Key... keys) {
    for (Key key : keys) {
        // 移除读写缓存中的key
        readWriteCacheMap.invalidate(key);
        Collection<Key> keysWithRegions = regionSpecificKeys.get(key);
        if (null != keysWithRegions && !keysWithRegions.isEmpty()) {
            for (Key keysWithRegion : keysWithRegions) {
                readWriteCacheMap.invalidate(keysWithRegion);
            }
        }
    }
}

小结

  1. 通过心跳发起注册
  2. 将当前实例放入到注册队列与最近改变队列中
  3. 无效服务端本地的缓存