Eureka source code analysis implementation mechanism

Eureka is an open source Netflix to provide service registry and discovery products

  • github address
    https://github.com/Netflix/eureka

Eureka provides a complete Service Registry Service Discovery and realization.

  • Eureka registry deployed in more room Chart

  • Eureka Server: provide service registry and found that synchronizes data between multiple Eureka Server, do the same state (eventual consistency)

  • Service Provider: Service Provider, will register their services to Eureka, so that the service consumer to find

  • Service Consumer: consumer services, get a list of registered services from Eureka, thereby Consumer Services

1 self-protection mechanism

Play a protective role in the presence of network partitions between Eureka Client and Eureka Server, the server and the client has a corresponding implementation.

Assuming that in some specific cases (such as network failure), Eureka Client and Eureka Server can not communicate, can not be initiated at this time Eureka Client registration and renewal requests to Eureka Server, it may result in Eureka Server service instance registry a large number of leases expiring in danger of being eliminated, but at this time of Eureka Client may be in a healthy state (acceptable service access), if the direct service instance registry large number of expired leases excluded is clearly unreasonable, self eureka protection mechanisms to improve the service availability.

When the self-protection mechanism is triggered, Eureka no longer be removed from the registration list for a long time because the service did not receive a heartbeat should expire, information and inquiry service can still accept new service registration request, that is, other functions are normal. Under this thinking, if eureka process node A trigger mechanism of self-protection, the new service is registered and then reply to the network, other peer nodes to receive new information service node A, data synchronization process is to peer network anomalies retry , that is able to guarantee eventual consistency.

Principle 2 service discovery

Will be (asynchronous) data between eureka server clusters can be deployed, multiple node synchronization to ensure data consistency ultimately, Eureka Server as a service registry out of the box, available features include: service registration, receive services heartbeat , excluding service, service off the assembly line and so on.

Eureka Server is also a Eureka Client, when not prohibited Eureka Server client behavior, it will configure it to other Eureka Server file pull registry, service registration and send heartbeat and other operations.

eureka server and the terminal through appName instanceInfoId to uniquely distinguish a service instance

  • Examples of information stored in the service following the Map
  • The first layer is a key appName
  • The second layer is a key instanceInfoId

3 Registration Service

Information Services will (InstanceInfo) when sent to Service Provider start eureka server, eureka server after receiving written to the registry in

  • The default expiration time of service registration
    com.netflix.eureka.lease.Lease

  • After InstanceInfo written to the local Registry, and synchronization to other peer nodes, correspond to the following methodcom.netflix.eureka.registry.PeerAwareInstanceRegistryImpl

    /**
     * 将所有eureka操作复制到对等eureka节点,
     * 但复制到该节点的流量除外。
     */
    private void replicateToPeers(Action action, String appName, String id,
                                  InstanceInfo info /* 可选 */,
                                  InstanceStatus newStatus /* 可选 */, boolean isReplication) {
        Stopwatch tracer = action.getTimer().start();
        try {
            if (isReplication) {
                numberOfReplicationsLastMin.increment();
            }

            // 如果已经是副本,则不要再次复制,因为这将创建有毒的复制
            if (peerEurekaNodes == Collections.EMPTY_LIST || isReplication) {
                return;
            }

            for (final PeerEurekaNode node : peerEurekaNodes.getPeerEurekaNodes()) {
               
                // 如果URL代表此主机,请不要复制到您自己。
                if (peerEurekaNodes.isThisMyUrl(node.getServiceUrl())) {
                    continue;
                }
                replicateInstanceActionsToPeers(action, appName, id, info, newStatus, node);
            }
        } finally {
            tracer.stop();
        }
    }

4 written to the local registry

Service Information (InstanceInfo) stored in the Lease, written into the local registry corresponding method
com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl

    /**
     * 将由该节点接收的InstanceInfo的注册信息发送到由此类表示的对等节点。
     *
     * @param info
     *            发送到该实例的任何实例的InstanceInfo
     */
    public void register(final InstanceInfo info) throws Exception {
        long expiryTime = System.currentTimeMillis() + getLeaseRenewalOf(info);
        batchingDispatcher.process(
                taskId("register", info),
                new InstanceReplicationTask(targetHost, Action.Register, info, null, true) {
                    public EurekaHttpResponse<Void> execute() {
                        return replicationClient.register(info);
                    }
                },
                expiryTime
        );
    }

Lease unity in memory of ConcurrentHashMap, the service registration process, the first to add a read lock, and then determine whether the Lease already exists from the registry, if it exists compare lastDirtyTimestamp timestamp, whichever is the largest service information and avoid data coverage happen. Use InstanceInfo create a new InstanceInfo:

if (existingLastDirtyTimestamp > registrationLastDirtyTimestamp) {
    // 已存在Lease则比较时间戳,取二者最大值
    registrant = existingLease.getHolder();
}
Lease<InstanceInfo> lease = new Lease<InstanceInfo>(registrant, leaseDuration);
if (existingLease != null) {
    // 已存在Lease则取上次up时间戳
    lease.setServiceUpTimestamp(existingLease.getServiceUpTimestamp());
}

public Lease(T r, int durationInSecs) {
    holder = r;
    registrationTimestamp = System.currentTimeMillis(); // 当前时间
    lastUpdateTimestamp = registrationTimestamp;
    duration = (durationInSecs * 1000);
}

By reading the lock and registry reads and writes are not atomic, then in fact possible concurrent data coverage occurs, it not occur if the data cover a problem! At first glance you would think that dirty data is not a problem with it?

换个角度想,脏数据就一定有问题么?
其实针对这个问题,eureka的处理方式是没有问题的,该方法并发时,针对InstanceInfo Lease的构造,二者的信息是基本一致的,因为registrationTimestamp取的就是当前时间,所以并发的数据不会产生问题。

同步给其他peer

InstanceInfo写入到本地registry之后,然后同步给其他peer节点,对应方法

replicateToPeers如果当前节点接收到的InstanceInfo本身就是另一个节点同步来的,则不会继续同步给其他节点,避免形成“广播效应”;InstanceInfo同步时会排除当前节点。

  • InstanceInfo的状态有以下几种
    com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl

默认情况下同步操作时批量异步执行的,同步请求首先缓存到Map中,key为requestType+appName+id,然后由发送线程将请求发送到peer节点。

Peer之间的状态是采用异步的方式同步的,所以节点间的状态不一定一致,不过基本能保证最终状态是一致的。
结合服务发现的场景,实际上也并不需要节点间的状态强一致。在一段时间内(比如30秒),节点A比节点B多一个服务实例或少一个服务实例,在业务上也是完全可以接受的(Service Consumer侧一般也会实现错误重试和负载均衡机制)。所以按照CAP理论,Eureka的选择就是放弃C,选择AP。

如果同步过程中,出现了异常怎么办呢,这时会根据异常信息做对应的处理,如果是读取超时或者网络连接异常,则稍后重试;如果其他异常则打印错误日志不再后续处理。

服务续约

Renew(服务续约)操作由Service Provider定期调用,类似于heartbeat。主要是用来告诉Eureka Server Service Provider还活着,避免服务被剔除掉。

renew接口实现方式和register基本一致:首先更新自身状态,再同步到其它Peer,服务续约也就是把过期时间设置为当前时间加上duration值。

注意:服务注册如果InstanceInfo不存在则加入,存在则更新;而服务预约只是进行更新,如果InstanceInfo不存在直接返回false。

服务下线

Cancel(服务下线)一般在Service Provider shutdown的时候调用,用来把自身的服务从Eureka Server中删除,以防客户端调用不存在的服务,eureka从本地”删除“(设置为删除状态)之后会同步给其他peer,对应方法com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl#cancel

@Override
public boolean cancel(final String appName, final String id,
                      final boolean isReplication) {
    if (super.cancel(appName, id, isReplication)) {
        replicateToPeers(Action.Cancel, appName, id, null, null, isReplication);
        synchronized (lock) {
            if (this.expectedNumberOfRenewsPerMin > 0) {
                // 由于客户端想取消它,所以降低阈值(1持续30秒,2持续1分钟)
                this.expectedNumberOfRenewsPerMin = this.expectedNumberOfRenewsPerMin - 2;
                this.numberOfRenewsPerMinThreshold =
                        (int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold());
            }
        }
        return true;
    }
    return false;
}

服务失效剔除

Eureka Server中有一个EvictionTask,用于检查服务是否失效。

Eviction(失效服务剔除)用来定期(默认为每60秒)在Eureka Server检测失效的服务,检测标准就是超过一定时间没有Renew的服务。默认失效时间为90秒,也就是如果有服务超过90秒没有向Eureka Server发起Renew请求的话,就会被当做失效服务剔除掉。

失效时间

可以通过eureka.instance.leaseExpirationDurationInSeconds进行配置
org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean#leaseExpirationDurationInSeconds

/**
 * 指示从接收到最后一个心跳以来, eureka server等待的时间(以秒为单位),
 * 它可以通过禁止到该实例的流量从其视图中删除该实例。
 * 将此值设置得太长可能意味着即使实例未处于活动状态,也可以将流量路由到该实例。 
 * 将该值设置得太小可能意味着该实例可能由于网络临时故障而无法进行通信。
 * 此值应设置为至少比leaseRenewalIntervalInSeconds中指定的值高。
 */
private int leaseExpirationDurationInSeconds = 90;

定期扫描时间

可以通过

eureka.server.evictionIntervalTimerInMs

进行配置。
org/springframework/cloud/netflix/eureka/server/EurekaServerConfigBean.java

服务剔除#evict方法中有很多限制,都是为了保证Eureka Server的可用性:比如自我保护时期不能进行服务剔除操作、过期操作是分批进行、服务剔除是随机逐个剔除,剔除均匀分布在所有应用中,防止在同一时间内同一服务集群中的服务全部过期被剔除,以致大量剔除发生时,在未进行自我保护前促使了程序的崩溃。

eureka server/client流程

服务信息拉取

Eureka consumer服务信息的拉取分为

  • 全量式拉取
    eureka consumer启动时

  • 增量式拉取
    运行过程中由定时任务进行增量式拉取

如果网络出现异常,可能导致先拉取的数据被旧数据覆盖(比如上一次拉取线程获取结果较慢,数据已更新情况下使用返回结果再次更新,导致数据版本落后),产生脏数据。

对此,eureka通过
com.netflix.eureka.registry.RemoteRegionRegistry#fetchRegistryGeneration
单调增加的生成计数器,以确保陈旧的线程不会将注册表重置为旧版本

对数据版本进行跟踪,版本不一致则表示此次拉取到的数据已过期。

  • 在拉取数据之前,执行获取当前版本号

  • 获取到数据之后,通过
    来判断当前版本号是否已更新。

注意:如果增量式更新出现意外,会再次进行一次全量拉取更新。

Eureka server的伸缩容

Eureka Server怎么知道有多少Peer的呢?

Eureka Server在启动后会调用
com.netflix.discovery.EurekaClientConfig#getEurekaServerServiceUrls


获取与eureka服务器通信的标准URL列表。
通常,服务器{@link java.net.URL}携带协议,主机,端口,上下文和版本信息(如果有)。
Example: http://ec2-256-156-243-129.compute-1.amazonaws.com:7001/eureka/v2/
更改将在运行时在下一个由getEurekaServiceUrlPollIntervalSeconds()指定的服务URL刷新周期生效。

来获取所有的Peer节点,并且会定期更新。定期更新频率可以通过eureka.server.peerEurekaNodesUpdateIntervalMs配置。

这个方法的默认实现是从配置文件读取,所以如果Eureka Server节点相对固定的话,可以通过在配置文件中配置来实现。如果希望能更灵活的控制Eureka Server节点,比如动态扩容/缩容,那么可以override getEurekaServerServiceUrls方法,提供自己的实现,比如我们的项目中会通过数据库读取Eureka Server列表。

eureka server启动时把自己当做是Service Consumer从其它Peer Eureka获取所有服务的注册信息。然后对每个服务信息,在自己这里执行Register,isReplication=true,从而完成初始化。

Service Provider

Service Provider启动时首先时注册到Eureka Service上,这样其他消费者才能进行服务调用
除了在启动时之外,只要实例状态信息有变化,也会注册到Eureka Service。

需要确保配置

eureka.client.registerWithEureka=true

register逻辑在
com.netflix.discovery.shared.transport.jersey.AbstractJerseyEurekaHttpClient#register

@Override
public EurekaHttpResponse<Void> register(InstanceInfo info) {
    String urlPath = "apps/" + info.getAppName();
    ClientResponse response = null;
    try {
        Builder resourceBuilder = jerseyClient.resource(serviceUrl).path(urlPath).getRequestBuilder();
        addExtraHeaders(resourceBuilder);
        response = resourceBuilder
                .header("Accept-Encoding", "gzip")
                .type(MediaType.APPLICATION_JSON_TYPE)
                .accept(MediaType.APPLICATION_JSON)
                .post(ClientResponse.class, info);
        return anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build();
    } finally {
        if (logger.isDebugEnabled()) {
            logger.debug("Jersey HTTP POST {}/{} with instance {}; statusCode={}", serviceUrl, urlPath, info.getId(),
                    response == null ? "N/A" : response.getStatus());
        }
        if (response != null) {
            response.close();
        }
    }
}

Service Provider会依次注册到配置的Eureka Server Url上,如果注册出现异常,则会继续注册其他的url。

Renew操作会在Service Provider端定期发起,用来通知Eureka Server自己还活着。 这里instance.leaseRenewalIntervalInSeconds属性表示Renew频率。


指示eureka客户端需要多长时间(以秒为单位)将心跳发送到eureka服务器,以指示其仍处于活动状态。 如果在leaseExpirationDurationInSeconds中指定的时间段内未收到心跳,则eureka服务器将通过禁止访问该实例的流量从该实例的视图中删除该实例。

请注意,如果实例实现HealthCheckCallback,然后决定使其自身不可用,则该实例仍无法获得流量。

默认是30秒,也就是每30秒会向Eureka Server发起Renew操作。这部分逻辑在HeartbeatThread类中
com.netflix.discovery.DiscoveryClient.HeartbeatThread
在给定时间间隔内更新租约的心跳任务。

com.netflix.discovery.DiscoveryClient#renew
通过进行适当的REST调用来使用eureka服务进行续订

boolean renew() {
    EurekaHttpResponse<InstanceInfo> httpResponse;
    try {
        httpResponse = eurekaTransport.registrationClient.sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, null);
        logger.debug(PREFIX + "{} - Heartbeat status: {}", appPathIdentifier, httpResponse.getStatusCode());
        if (httpResponse.getStatusCode() == 404) {
            REREGISTER_COUNTER.increment();
            logger.info(PREFIX + "{} - Re-registering apps/{}", 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(PREFIX + "{} - was unable to send heartbeat!", appPathIdentifier, e);
        return false;
    }
}

在Service Provider服务shutdown的时候,需要及时通知Eureka Server把自己剔除,从而避免客户端调用已经下线的服务,逻辑本身比较简单,通过对方法标记@PreDestroy,从而在服务shutdown的时候会被触发。

Service Consumer

Service Consumer这块的实现相对就简单一些,因为它只涉及到从Eureka Server获取服务列表和更新服务列表。

Service Consumer在启动时会从Eureka Server获取所有服务列表,并在本地缓存。

需要注意的是,需要确保配置

eureka.client.shouldFetchRegistry=true

由于在本地有一份Service Registries缓存,所以需要定期更新,定期更新频率可以通过

eureka.client.registryFetchIntervalSeconds

配置。

总结

为什么要用eureka呢,因为分布式开发架构中,任何单点的服务都不能保证不会中断,因此需要服务发现机制,某个节点中断后,服务消费者能及时感知到保证服务高可用。从eureka的设计与实现上来说还是容易理解的,SpringCloud将它集成在自己的子项目spring-cloud-netflix中,实现SpringCloud的服务发现功能。

注册中心除了用eureka之外,还有zookeeper、consul、nacos等解决方案,他们实现原理不同,各自适用于不同的场景,可按需使用。

Eureka比ZooKeeper相比优势

Zookeeper保证CP

当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟以前的注册信息,但不能接受服务直接down掉不可用。也就是说,服务注册功能对可用性的要求要高于一致性。但是zk会出现这样一种情况,当master节点因为网络故障与其他节点失去联系时,剩余节点会重新进行leader选举。问题在于,选举leader的时间太长,30 ~ 120s, 且选举期间整个zk集群都是不可用的,这就导致在选举期间注册服务瘫痪。在云部署的环境下,因网络问题使得zk集群失去master节点是较大概率会发生的事,虽然服务能够最终恢复,但是漫长的选举时间导致的注册长期不可用是不能容忍的。

Eureka保证AP

Eureka看明白了这一点,因此在设计时就优先保证可用性。Eureka各个节点都是平等的,几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务。而Eureka的客户端在向某个Eureka注册或时如果发现连接失败,则会自动切换至其它节点,只要有一台Eureka还在,就能保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)。除此之外,Eureka还有一种自我保护机制,如果在15分钟内超过85%的节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障。

eureka有哪些不足

eureka consumer本身有缓存,服务状态更新滞后,最常见的状况就是,服务下线了但是服务消费者还未及时感知,此时调用到已下线服务会导致请求失败,只能依靠consumer端的容错机制来保证。

参考

发布了330 篇原创文章 · 获赞 150 · 访问量 14万+

Guess you like

Origin blog.csdn.net/qq_33589510/article/details/103965439