Eureka:作为注册中心,提供服务注册和服务发现的功能,整体上分为两个主体:Eureka Client 和 Eureka Server。
- Eureka Client 会向Eureka Server注册服务信息,并进行服务续约。Eureka Client刚启动时会从Eureka Server中全量获取服务信息,在运行过程中启动定时任务进行增量获取,如果网络不稳定,可能导致先拉取的数据被旧数据覆盖(比如上一次拉取线程获取结果较慢,数据已更新情况下使用返回结果再次更新,导致数据版本落后),产生脏数据。对此,eureka通过类型AtomicLong的fetchRegistryGeneration对数据版本进行跟踪,版本不一致则表示此次拉取到的数据已过期。fetchRegistryGeneration过程是在拉取数据之前,执行fetchRegistryGeneration.get获取当前版本号,获取到数据之后,通过fetchRegistryGeneration.compareAndSet来判断当前版本号是否已更新。如果增量式更新出现意外,会再次进行一次全量拉取更新。
- 服务发现,注册原理:
a) eureka server端通过appName和instanceInfoId来唯一区分一个服务实例,服务实例信息是保存在哪里呢?其实就是一个Map中:
private final ConcurrentHashMap<String, Map<String, Lease>> registry = new ConcurrentHashMap<String, Map<String, Lease>>();
b) 服务信息(InstanceInfo)保存在Lease中, ,Lease统一保存在内存的ConcurrentHashMap中,在服务注册过程中,首先加个读锁,然后从registry中判断该Lease是否已存在,如果已存在则比较lastDirtyTimestamp时间戳,取二者最大的服务信息,避免发生数据覆盖。 - Eureka集群:
a) 分布式系统中有一个重要理论:CAP。该理论提到了分布式系统中的3个特性:
• Consistency 数据一致性
分布式系统中,数据会存在多个副本中,有一些问题会导致写入数据时,一部分副本成功、一部分副本失败,造成数据不一致。
满足一致性就要求对数据的更新操作成功后,多副本的数据必须保持一致。
• Availability 可用性
在任何时候客户端对集群进行读写操作时,请求能够正常响应。
• Partition Tolerance 分区容忍性
发生通信故障时,集群被分割为多个无法通信的分区时,集群仍然可用。
CAP 理论指出:这3个特性不可能同时满足,最多满足2个。
P 是客观存在的,不可绕过,那么就是选择 C 还是选择 A。
ZooKeeper 选择了 C,就是尽可能的保证数据一致性,某些情况下可以牺牲可用性。
Eureka 则选择了 A,所以 Eureka 具有高可用性,在任何时候,服务消费者都能正常获取服务列表,但不保证数据的强一致性,消费者可能会拿到过期的服务列表。
b) 数据复制方式:分布式系统的数据在多个副本之间的复制方式,主要有:
• 主从复制
就是 Master-Slave 模式,有一个主副本,其他为从副本,所有写操作都提交到主副本,再由主副本更新到其他从副本。
写压力都集中在主副本上,是系统的瓶颈,从副本可以分担读请求。
• 对等复制
就是 Peer to Peer 模式,副本间不分主从,任何副本都可以接收写操作,然后每个副本间互相进行数据更新。
对等复制模式,任何副本都可以接收写请求,不存在写压力瓶颈,但各个副本间数据同步时可能产生数据冲突。
Eureka 采用的就是 Peer to Peer 模式。
c) 数据同步:Eureka Server 启动后,会通过 Eureka Client 请求其他 Eureka Server 节点中的一个节点,获取注册的服务信息,然后复制到其他 peer 节点。Eureka Server 每当自己的信息变更后,例如 Client 向自己发起注册、续约、注销请求, 就会把自己的最新信息通知给其他 Eureka Server,保持数据同步。这种同步方式会出现两个问题:
i. 数据同步死循环:如果自己的信息变更是另一个Eureka Server同步过来的,这是再同步回去的话就出现数据同步死循环了。Eureka Server 在执行复制操作的时候,使用 HEADER_REPLICATION 这个 http header 来区分普通应用实例的正常请求,说明这是一个复制请求,这样其他 peer 节点收到请求时,就不会再对其进行复制操作,从而避免死循环。
ii. 数据冲突,比如 server A 向 server B 发起同步请求,如果 A 的数据比 B 的还旧,B 不可能接受 A 的数据,那么 B 是如何知道 A 的数据是旧的呢?这时 A 又应该怎么办呢?数据的新旧一般是通过版本号来定义的,Eureka 是通过 lastDirtyTimestamp 这个类似版本号的属性来实现的。
4. Eureka客户端的实现,通过java的源码可发现其主要是通过http协议和注册中心REST请求通讯从而维护了一套客户端注册,心跳,断开等操作。因为SpringCloud是基于SpringBoot进行开发的,所以一些老的Java web项目和一些其他语言项目没办法通过内置的注解进行服务注册,需要自己掉Eureka的接口进行实现。
5. 自我保护机制:如果在15分钟内超过85%的节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,此时会出现以下几种情况:
- Eureka不再从注册列表中移除因为长时间没收到心跳而应该过期的服务
- Eureka仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上(即保证当前节点依然可用)
- 当网络稳定时,当前实例新的注册信息会被同步到其它节点中