服务失效剔除
Eureka Server中有一个EvictionTask,用于检查服务是否失效。
Eviction(失效服务剔除)用来定期(默认为每60秒)在Eureka Server检测失效的服务,检测标准就是超过一定时间没有Renew的服务。
默认失效时间为90秒,也就是如果有服务超过90秒没有向Eureka Server发起Renew请求的话,就会被当做失效服务剔除掉。
失效时间可以通过eureka.instance.leaseExpirationDurationInSeconds进行配置,定期扫描时间可以通过eureka.server.evictionIntervalTimerInMs进行配置。
服务剔除#evict方法中有很多限制,都是为了保证Eureka Server的可用性:
比如自我保护时期不能进行服务剔除操作、过期操作是分批进行、服务剔除是随机逐个剔除,剔除均匀分布在所有应用中,防止在同一时间内同一服务集群中的服务全部过期被剔除,以致大量剔除发生时,在未进行自我保护前促使了程序的崩溃。
eureka server/client流程
服务信息拉取
Eureka consumer服务信息的拉取分为全量式拉取和增量式拉取,
eureka consumer启动时进行全量拉取,运行过程中由定时任务进行增量式拉取,
如果网络出现异常,可能导致先拉取的数据被旧数据覆盖(比如上一次拉取线程获取结果较慢,数据已更新情况下使用返回结果再次更新,导致数据版本落后),产生脏数据。
对此,eureka通过类型AtomicLong的fetchRegistryGeneration对数据版本进行跟踪,版本不一致则表示此次拉取到的数据已过期。
fetchRegistryGeneration过程是在拉取数据之前,执行fetchRegistryGeneration.get获取当前版本号,获取到数据之后,通过fetchRegistryGeneration.compareAndSet来判断当前版本号是否已更新。
注意:如果增量式更新出现意外,会再次进行一次全量拉取更新。
Eureka server的伸缩容
Eureka Server在启动后会调用EurekaClientConfig.getEurekaServerServiceUrls来获取所有的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逻辑在方法AbstractJerseyEurekaHttpClient.register中,Service Provider会依次注册到配置的Eureka Server Url上,如果注册出现异常,则会继续注册其他的url。
Renew操作会在Service Provider端定期发起,用来通知Eureka Server自己还活着。这里instance.leaseRenewalIntervalInSeconds属性表示Renew频率。默认是30秒,也就是每30秒会向Eureka Server发起Renew操作。
这部分逻辑在HeartbeatThread类中。在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端的容错机制来保证。
微服务架构中最核心的部分是服务治理,服务治理最基础的组件是注册中心。随着微服务架构的发展,出现了很多微服务架构的解决方案,其中包括我们熟知的 Dubbo 和 Spring Cloud。
关于注册中心的解决方案,dubbo 支持了 Zookeeper、Redis、Multicast 和 Simple,官方推荐 Zookeeper。Spring Cloud 支持了 Zookeeper、Consul 和 Eureka,官方推荐 Eureka。
两者之所以推荐不同的实现方式,原因在于组件的特点以及适用场景不同。简单来说:
- ZK 的设计原则是 CP,即强一致性和分区容错性。他保证数据的强一致性,但舍弃了可用性,如果出现网络问题可能会影响 ZK 的选举,导致 ZK 注册中心的不可用。
- Eureka 的设计原则是 AP,即可用性和分区容错性。他保证了注册中心的可用性,但舍弃了数据一致性,各节点上的数据有可能是不一致的(会最终一致)。
Eureka 采用纯 Java 实现,除实现了注册中心基本的服务注册和发现之外,极大的满足注册中心的可用性,即使只有一台服务可用,也可以保证注册中心的可用性。