Eurka是由Netflix开发的一套具有服务治理功能的组件,Spring Cloud在其基础上进行了二次封装,将其融入到了Spring Cloud微服务体系中。服务治理在任何一个微服务框架中都应是一个基础且重要的功能。
Eureka的服务端和客户端
Eureka主要有两个重要的组成部分:Eureka Server和Eureka Client。顾名思义,Eureka Server,又称服务注册中心,其维护了一个ConcurrentHashMap对象registry,为一个双层map结构(ConcurrentHashMap<String, Map<String, Lease
Eureka Server
在一个基础的Spring Boot项目中,在pom.xml文件导入以下依赖:
1 | <dependency> |
然后在该应用的主类中加上@EnableEurekaServer注解,即可开启一个Eureka Server。如果需要对端口等行为进行自定义,可以在application.properties中配置如下:
1 | spring.application.name=eureka-server |
其中eureka.client.serviceUrl.defaultZone一般为本机hostname+端口+/eureka/,本机已搭建了一个高可用的Eureka Server集群,分别有peer和peer1两个节点,两个节点的defaultZone互为对方地址。启动这两个节点,访问http://peer1:1002/eureka/ 或者 http://peer:1001/eureka/ ,可以见到如下界面:
图中可以看出,本机已启动了两个Eureka Server节点,两个Eureka Client节点,以及一个服务消费者节点。先看Eureka Server节点。打开spring-cloud-netflix-eureka的jar包(以2.1.1版本为例),我们可以看到如下的目录结构:
可以看到Eureka Server定义了5个事件,其中三个与Eureka Client相关,分别是EurekaInstanceCanceledEvent(服务的取消)、EurekaInstanceRegisteredEvent(服务的注册)、EurekaInstanceRenewedEvent(服务的续约),EurekaRegistryAvailableEvent和EurekaServerStartedEvent则与Eureka Server自身启动和注册相关。
查看InstanceRegistry类,可以找到跟服务注册、取消、续约相关的代码片段如下:
1 |
|
其中,服务注册和取消的时候,都会先通过Spring Context发布一个事件,通知网络中的其他Eureka Server节点,然后再执行相应的注册或取消动作。服务续约则会从已存储的服务列表中找到appName与serverId一致的那个服务实例,发布续约事件,然后执行续约动作。以注册动作为例,根据super.register(info, leaseDuration, isReplication)找到eureka-core包中的AbstractInstanceRegistry类,可以看到register动作的函数体为:
1 | public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) { |
该函数中,Eureka Server维护了一个上文中提到的ConcurrentHashMap对象registry,以及recentRegisteredQueue和recentlyChangedQueue两个序列。在register动作发生时,Eureka Server首先会去检查registry对象,查看需要注册的InstanceInfo是否存在,如果存在,则修改对应的状态、时间等相关信息;如果不存在,则增加,并在recentRegisteredQueue和recentlyChangedQueue中登记。
服务取消动作和续约动作分别对应该类中的internalCancel和renew函数。
Eureka Client
在微服务体系中,除去Eureka Server之外的服务,都具有Eureka Client的属性,因为它们要么是服务提供者,要么是服务消费者,或者两者都是。
服务提供者
在pom.xml中导入以下依赖:
1 | <dependency> |
并在应用主类中添加@EnableDiscoveryClient注解,即可开启一个Eureka Client。如果需要对端口等行为进行自定义,可以在application.properties中配置如下:
1 | spring.application.name=eureka-client |
作为服务提供者,则需要提供接口供服务消费者来消费。本例中以rest请求的方式定义一个最简单的接口,示例如下:
1 | package com.tpx.ms.controller; |
通过注入DiscoveryClient对象,可以得到这个Eureka Client的相关信息。打开spring-cloud-netflix-eureka-client的jar包,可以看到如下的目录结构:
图中可以看到spring-cloud-netflix-eureka-client只是Spring Cloud的封装,图下的位置里还有一个eureka-client的jar包。查看DiscoveryClient类的构造方法,代码如下:
1 |
|
代码有部分精简,从这个函数可以看出,在Eureka Client初始化的过程中,会先对一系列参数进行检查,其中最重要的两个参数为fetchRegistry和registerWithEureka,默认为ture。参数检查完毕之后,会执行scheduleServerEndpointTask函数,该方法会根据参数给EurekaTransport对象赋值,用于传输到Eureka Server端;然后执行initScheduledTasks函数,部分代码如下:
1 | private void initScheduledTasks() { |
该函数中有两个重要的if分支,按顺序分别对应服务获取、服务注册(续约)功能,需要注意的是,服务获取只有cacheRefresh这个定时任务,而服务注册(续约)则拥有heartbeat定时任务、InstanceInfoReplicator、StatusChangeListener等功能。在InstanceInfoReplicator实例中,有一个定时任务会执行register函数,该函数的作用是将client自身的instanceinfo以rest请求的方式发送给Server端,从而完成注册。heartbeat定时任务则利用心跳机制来进行服务续约。
服务消费者
新建服务提供者的步骤也可用于新建服务消费者,不同的是,我们需要消费服务提供者提供的dc接口。在Spring Boot项目中定义一个interface用于访问dc接口,代码如下:
1 | package com.tpx.ms.restinterface; |
然后再定义一个Controller用于调用该interface,代码如下:
1 | package com.tpx.ms.controller; |
经过以上对服务提供者的分析,可以得知,严格意义上的服务消费者,其实只需要开启服务获取的功能即可。在initScheduledTasks函数中,第一个服务获取的if分支中提供了一个cacheRefresh的定时任务,该任务会定时获取Eureka Server端的服务提供者列表,存储在本地并定期刷新。通过这个机制,我们可以实现客户端负载均衡。
其他
更多关于Eureka Server和Eureka Client的配置相关信息,可以查看EurekaServerConfigBean和EurekaClientConfigBean的源码;
Eureka Client配置时,还有关于Region和Zone的配置,本文不再展开;
各节点的/info和/heath信息需配合spring-boot-starter-actuator使用。