Eureka学习笔记

Eureka学习笔记

服务端

模块设计

  • Resources :这部分对外暴露了一系列的 Restful 接口。Eureka Client 的注册、心跳、获取服务列表等操作都需要调用这些接口。另外,其他的 Server 在同步 Registry 时也需要调用这些接口。
  • Controller :这里提供了几个 web 接口,主要用在 Eureka Server 本身的 Dashboard 页面, 从页面上可以查看到当前注册了的服务,以及每个服务下各个实例的状态。
  • PeerAwareInstanceRegistry :这里面记录了当前所以注册了的服务实例。当这些注册信息发生变化时,`PeerAwareInstanceRegistry 还要负责把这些变化同步到其他的 Server。
  • PeerEurekaNodes :这里维护了集群里所有 Eureka Server 节点的信息,PeerAwareInstanceRegistry 在同步时需要从这里获取其他 Server 的信息。同时它还负责定时检查配置来发现是否有 Eureka Server 节点新增或删除。
  • HttpReplicationClient :这是 PeerAwareInstanceRegistry 向其他 Server 同步时发送请求的 http client。
Resources

Eureka Service 的 Resource 是指处理 Eureka Server 提供的各类 RESTful API 的类,主要负责与客户端和其他 Eureka Server 节点交互。包含以下几个重要的重要组成

  1. ApplicationsResource:提供全量或增量注册表的获取接口,用于服务发现。
  2. ApplicationResource:管理特定应用的操作,如实例注册和删除。
  3. InstanceResource:处理单个实例的操作,如续约、状态更新和详细信息获取。
  4. PeerReplicationResource:处理 Eureka Server 节点之间的数据复制请求,保证注册表一致性。
  5. HealthCheckHandler:定义实例健康检查逻辑,决定实例的状态更新。
  6. EurekaServerResource:提供当前 Eureka Server 的状态和配置信息。
  7. ClustersResource:管理和查看 Eureka 集群的整体状态和节点信息。
  8. ServerCodecs:定义注册表数据的编码和解码规则,支持 JSON 和 XML 格式。
AbstractInstanceRegistry
register

其内部维护了一个如下的Map结构,key为应用标识,value同样是一个Map,后者的key为单个应用实例的标识,value为单个实例的详细信息(被Lease包装了实例的租期信息)

java 复制代码
private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry
        = new ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>();
InstanceInfo

维护了引用实例的详细信息,主要包含

  • instanceId:实例的唯一标识符,用于区分不同的服务实例。
  • appName:服务的逻辑名称,便于服务分组和发现。
  • vipAddress:虚拟 IP 地址,用于逻辑路由和客户端服务发现。
  • status :实例的当前运行状态(如 UPDOWN)。
  • lastDirtyTimestamp:实例信息最后一次被修改的时间戳,用于数据冲突判断。
  • lastUpdatedTimestamp:实例信息在服务端最后更新的时间戳,用于跟踪本地状态。
  • metadata:实例的元数据,存储额外的键值对信息供路由或分组使用。
  • hostName:实例的主机名,用于标识和访问实例。
  • ipAddr:实例的 IP 地址,用于实例的物理定位。
  • leaseInfo:与服务租约相关的信息,如续约间隔和过期时间。
  • isDirty:标识实例信息是否处于"脏"状态,用于触发更新。
  • actionType:标记实例操作类型(添加、修改、删除)以便同步。

其中lastDirtyTimestamplastUpdatedTimestamp看起来很相似实际上二者并不相同

二者对比如下

属性 定义 用途 设置时机
lastUpdatedTimestamp 服务端记录实例信息最后更新时间 服务端内部维护,用于跟踪当前实例的更新情况 Eureka Server 自动设置
lastDirtyTimestamp 客户端记录实例信息最后一次被修改时间戳 数据冲突判断与同步传播,表示数据的新旧状态 Eureka Client 或服务端同步时设置
Lease
java 复制代码
public class Lease<T> {
   // 注册、下线、续期
    enum Action {
        Register, Cancel, Renew
    };
    // 默认驱逐时间
    public static final int DEFAULT_DURATION_IN_SECS = 90;
    // 持有的应用实例的引用
    private T holder;
    // 被驱逐时间
    private long evictionTimestamp;
    // 服务注册时间
    private long registrationTimestamp;
    // 服务完全可用时间
    private long serviceUpTimestamp;
    // 最后续租时间
    private volatile long lastUpdateTimestamp;
}
驱逐机制

我们可以看到每个实例都被Lease包装,其中就记录着实例的注册时间registrationTimestamp ,以及最后续租时间lastUpdateTimestamp ,当实例没有在驱逐超时时间内完成续租将会被驱逐,后者由定时任务EvictionTask来来完成

java 复制代码
class EvictionTask extends TimerTask {

    private final AtomicLong lastExecutionNanosRef = new AtomicLong(0l);

    @Override
    public void run() {
        try {
            long compensationTimeMs = getCompensationTimeMs();
            logger.info("Running the evict task with compensationTime {}ms", compensationTimeMs);
            evict(compensationTimeMs);
        } catch (Throwable e) {
            logger.error("Could not run the evict task", e);
        }
    }
}
PeerEurekaNodes

PeerEurekaNodes 是一个整体管理类,内部包含多个 PeerEurekaNode 实例,每个实例代表一个集群节点。

其中一个重要的功能便是定时同步集群的节点信息,这个任务由其start()法方法中的定时器完成

java 复制代码
public void start() {
		...
        updatePeerEurekaNodes(resolvePeerUrls());
    	...
        taskExecutor.scheduleWithFixedDelay(
                peersUpdateTask,
                serverConfig.getPeerEurekaNodesUpdateIntervalMs(),
                serverConfig.getPeerEurekaNodesUpdateIntervalMs(),
                TimeUnit.MILLISECONDS
        );
    	...
}

一致性设计

Eureka 是一个AP系统,本身没有采用任何一种强一致性协议来保证系统整体的强一致性,根据CAP定理,一致性、可以用、分区可容忍性三者不可兼得,Eureka选择放弃强注一致性,来保证即使出现网络分区的情况下,只要客户端可连接上任意一个客户端就可以正常运行。

不同的注册中心产品在一致性的选择上各有千秋,常见的几款产品特性如下

  1. Eureka(AP系统)
  • 牺牲一致性,没有使用严格的一致性协议。

  • 最终一致性:Eureka并不要求所有副本在每个时刻都保持一致。在分区恢复时,Eureka会通过心跳和租约机制确保数据最终一致。

  • 适用场景:当系统需要高可用性,并且可以容忍短时间的一致性问题时,Eureka非常合适。

    1. Consul(CP系统)
  • 提供强一致性,并使用Raft协议来保证分布式系统的一致性。

  • 强一致性:Consul会确保在发生故障或网络分区时,系统的一致性优先。它会牺牲系统的可用性,确保所有的节点看到一致的数据。

  • 适用场景:当你需要确保服务注册中心中的数据在所有节点之间保持强一致性时,Consul是一个较好的选择。

  1. Zookeeper(CP系统)
  • 提供强一致性,并使用ZAB协议(Zookeeper Atomic Broadcast)来保证一致性。

  • 强一致性:Zookeeper通过分布式共识协议确保数据一致性,因此它在面对网络分区时会牺牲部分可用性。它适用于需要强一致性和协调的应用。

  • 适用场景:在需要强一致性的分布式系统中(例如,分布式锁、配置管理等),Zookeeper是一个合适的选项。

    1. Nacos(AP/CP系统,灵活配置)
  • 灵活的可选一致性策略:Nacos可以根据配置调整一致性级别,支持AP模式和CP模式。

  • 最终一致性或强一致性:可以通过配置Raft协议来实现一致性保障。默认情况下,Nacos更多的是追求可用性和分区容忍性,但它也支持强一致性模式。

  • 适用场景:Nacos适用于需要灵活选择一致性策略的场景,既支持最终一致性,也能在特定场景下实现强一致性。

  1. Etcd(CP系统)
  • 提供强一致性,并使用Raft协议保证数据一致性。
  • 强一致性:Etcd优先考虑一致性,在网络分区或节点故障时,它会保持一致性,并且牺牲部分可用性。
  • 适用场景:当分布式系统对一致性要求非常高时,Etcd是非常合适的。它常用于服务发现、配置管理和分布式协调等场景。

同步数据设计

数据同步的关键技术要点
  • REST API:Eureka集群通过HTTP REST API进行节点间的通信与数据同步。每个Eureka节点向其他节点推送自己更新的服务信息。
  • PeerAwareReplication:该机制是Eureka集群的核心,负责将服务注册信息在节点之间进行复制。每个节点知道其他节点的存在,并能够主动或被动地同步服务数据。
  • 心跳和租约机制:服务实例通过发送心跳请求来续约租约,确保服务实例不会因未能及时续约而被误删。租约过期的信息也会在节点间同步。
  • 最终一致性:Eureka采用最终一致性模型,而不是强一致性。在网络分区或节点故障时,系统可以继续运行,但在恢复后,系统会进行数据同步,直到一致性最终达成。
主动同步
触发条件

Eureka节点会在服务注册、续约、注销等事件发生时主动将变化的信息推送到其他节点。

PeerAwareInstanceRegistryImpl#replicateToPeers()

java 复制代码
private void replicateToPeers(Action action, String appName, String id,
                              InstanceInfo info /* optional */,
                              InstanceStatus newStatus /* optional */, boolean isReplication) {
    Stopwatch tracer = action.getTimer().start();
    try {
        // 是否是一个复制请求
        if (isReplication) {
            numberOfReplicationsLastMin.increment();
        }
        ...
        for (final PeerEurekaNode node : peerEurekaNodes.getPeerEurekaNodes()) {
            // 是否来自自身,如何是则跳过
            if (peerEurekaNodes.isThisMyUrl(node.getServiceUrl())) {
                continue;
            }
            // 复制同步到对等节点
            replicateInstanceActionsToPeers(action, appName, id, info, newStatus, node);
        }
    } finally {
        tracer.stop();
    }
}
java 复制代码
private void replicateInstanceActionsToPeers(Action action, String appName,
                                             String id, InstanceInfo info, InstanceStatus newStatus,
                                             PeerEurekaNode node) {
    	...
        switch (action) {
            case Cancel:
                node.cancel(appName, id);
                break;
            case Heartbeat:
                InstanceStatus overriddenStatus = overriddenInstanceStatusMap.get(id);
                infoFromRegistry = getInstanceByAppAndId(appName, id, false);
                node.heartbeat(appName, id, infoFromRegistry, overriddenStatus, false);
                break;
            case Register:
                node.register(info);
                break;
        ...
}
工作流程
被动同步
触发条件

被动同步机制通常在以下情况下触发:

  • 节点启动时:Eureka节点启动后,需要与其他节点同步当前服务注册表。
  • 节点恢复时:节点在网络分区、宕机等故障恢复后,为确保数据完整性,会从其他节点拉取最新数据。
工作流程


DiscoveryClient#getAndStoreFullRegistry()

java 复制代码
private void getAndStoreFullRegistry() throws Throwable {
    long currentUpdateGeneration = fetchRegistryGeneration.get();

    logger.info("Getting all instance registry info from the eureka server");
	// 从其他Eureka Service 获取Applications
    Applications apps = null;
    EurekaHttpResponse<Applications> httpResponse = clientConfig.getRegistryRefreshSingleVipAddress() == null
            ? eurekaTransport.queryClient.getApplications(remoteRegionsRef.get())
            : eurekaTransport.queryClient.getVip(clientConfig.getRegistryRefreshSingleVipAddress(), remoteRegionsRef.get());
    if (httpResponse.getStatusCode() == Status.OK.getStatusCode()) {
        apps = httpResponse.getEntity();
    }
	...
	localRegionApps.set(this.filterAndShuffle(apps));
}
java 复制代码
private Applications filterAndShuffle(Applications apps) {
    if (apps != null) {
        ...
        if (isFetchingRemoteRegionRegistries()) {
            Map<String, Applications> remoteRegionVsApps = new ConcurrentHashMap<String, Applications>();
            apps.shuffleAndIndexInstances(remoteRegionVsApps, clientConfig, instanceRegionChecker);
            for (Applications applications : remoteRegionVsApps.values()) {
                applications.shuffleInstances(clientConfig.shouldFilterOnlyUpInstances());
            }
        }
        ...
    }
}

我们可以看到之类有一个shuffle的操作,即对应用服务的实例做一个随机的打算主要是为了使得每个服务接收的流量更为均匀,同时也是避免新服务启动时出现雪崩现象。

相关推荐
吃着火锅x唱着歌1 小时前
PHP7内核剖析 学习笔记 第三章 数据类型
android·笔记·学习
keira6741 小时前
【21天学习AI底层概念】day5 机器学习的三大类型不能解决哪些问题?
人工智能·学习·机器学习
快乐飒男3 小时前
C语言基础13(内存管理)
c语言·开发语言·学习
A黄俊辉A3 小时前
electron学习笔记(一)
笔记·学习·electron
.生产的驴4 小时前
Docker Compose 多应用部署 一键部署
java·运维·后端·spring cloud·docker·容器·gateway
驰驰的老爸4 小时前
modbus学习笔记
笔记·学习
灵封~4 小时前
Python机器视觉的学习
python·学习·计算机视觉
S hh4 小时前
【C++】模板机制
开发语言·c++·学习·算法
xiegwei5 小时前
spring cloud contract http实例
spring·http·spring cloud