服务发现在微服务架构中是最重要的宗旨之一。尝试手动配置每个客户端或者以某种公约形式配置客户端将会比较困难和容易奔溃。Eureka是Netflix服务发现服务器和客户端。服务器可配置和发布成高可用的服务–每个服务节点都可将其服务注册状态广播到其他服务节点。
使用groupID为org.springframework.cloud,artifactID为spring-cloud-starter-netflix-eureka-client的starter就可将EurekaClient引入到你的项目中。点击查看详情。
当一个客户端使用Eureka注册,Eureka将提供它的元数据-如host、port、健康状况url、首页和其他详细信息。Eureka接收属于各自服务实例的心跳消息。如果心跳检查失败,且超过了设置的时间,服务实例将会从注册中心移除。
下面是一个迷你的Eureka Client应用例子:
@SpringBootApplication
@RestController
public class Application {
@RequestMapping("/")
public String home() {
return "Hello world";
}
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class).web(true).run(args);
}
}
注意上面的例子是一个普通的SpringBoot应用程序。如果有spring-cloud-starter-netflix-eureka-client在classpath中,你的应用程序会自动注册到Eureka服务器中。需要配置才能找到Eureka服务器,配置的例子如下:
application.yml
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
在前面的示例中,“defaultzone”是一个神奇的字符串回退值,它为任何不表示首选项的客户端提供服务URL(换句话说,它是一个有用的默认值)。
默认的应用程序名称(也就是服务id),虚拟主机,和不安全的端口(从环境中获取)分别是 s p r i n g . a p p l i c a t i o n . n a m e , {spring.application.name}, spring.application.name,{spring.application.name} 和 ${server.port}。
在classpath中有spring-cloud-starter-netflix-eureka-client将使该应用程序有Eureka实例(就是说,它会注册它自己)和客户端(可发现在注册中心的其他服务)两个功能。实例的行为被名为eureka.instance.*的配置驱动,如果你确保你的应用程序中有spring.application.name的配置值(默认的Eureka服务ID或者VIP),Eureka实例的默认行为将会执行的很好。
更多的详细配置选项可参考EurekaInstanceConfigBean和EurekaClientConfigBean。
禁用Eureka Discovery Client, 你可以将eureka.client.enabled设置成false
。
如果Eureka客户端某一个eureka.client.serviceUrl.defaultZone的url中含有认证信息(如:http://user:password@localhost:8761/eureka),基于HTTP的认证会自动嵌入到Eureka客户端中。如果有更复杂的需求,你可以创建一个DiscoveryClientOptionalArgs类型的@Bean,并将它注入到一个从Eureka客户端访问Eureka服务器都会访问的ClientFilter实例中。
注意:
因为Eureka的限制,不可能支持每个Eureka服务器都有基本的认证,因此只使用找到的第一组。
Eureka实例的状态页和健康指标页面默认访问路径分别为/info
和/health
,这是在Spring Boot Actuator应用中很有用的两个终端。不管是为了使用Actuator应用还是为了使用自定义的context path或者Servlet path(比如:server.servletPath=/custom),你都需要改变这两个路径,下面的例子展示了这两个设置的默认值:
application.yml
eureka:
instance:
statusPageUrlPath: ${server.servletPath}/info
healthCheckUrlPath: ${server.servletPath}/health
这些链接出现被客户端消费的元数据中,在某些场景下这将决定是否将请求发送到你的服务器中,所以如果你清晰的设置了的话将会很有用。
注意:
在Dalston版本中,当修改管理上下文时,必须设置状态和健康检查url。从Edgware版本开始,这就不再是必须的了。
如果你的应用通过HTTPS来访问,你可以在EurekaInstanceConfig中设置两个标志:
eureka.instance.[nonSecurePortEnabled]=[false]
eureka.instance.[securePortEnabled]=[true]
这么做将会使Eureka发布实例信息时显示明确的安全偏好。Spring cloud服务发现客户端一直通过这样设置服务来返回https URI。同样的,当服务使用这种方式设置时,Eureka实例信息将会拥有一个安全的健康检查URL。
由于Eureka内部的工作方式,它仍然为状态页面和主页发布一个不安全的URL,除非你明确覆盖这些URL。你可以使用占位符的方式来这是Eureka实例相关URLs,如下所示:
application.yml
eureka:
instance:
statusPageUrl: https://${eureka.hostname}/info
healthCheckUrl: https://${eureka.hostname}/health
homePageUrl: https://${eureka.hostname}/
(注意 e u r e k a . h o s t n a m e 是 一 个 本 地 的 占 位 符 , 只 在 最 新 的 E u r e k a 版 本 中 有 效 。 你 同 样 可 以 使 用 S p r i n g 的 占 位 符 来 实 现 同 样 的 事 情 , 比 如 {eureka.hostname}是一个本地的占位符,只在最新的Eureka版本中有效。你同样可以使用Spring的占位符来实现同样的事情,比如 eureka.hostname是一个本地的占位符,只在最新的Eureka版本中有效。你同样可以使用Spring的占位符来实现同样的事情,比如{eureka.instance.hostName}.)
如果你的应用运行在代理服务之后,并且SSL终止在代理服务器中(比如说你的应用作为服务运行在Cloud Foundry或者别的平台上。),这时你必须确保代理中的“forwarded”头部被应用拦截和处理了。如果嵌入Spring boot应用中的Tomcat容器配置了关于’X-Forwarded-*`头部的显式配置,这将会自动拦截和处理。应用程序呈现给自身的链接是错误的(错误的主机、端口或协议),这表明您的配置是错误的。
默认情况下,Eureka使用客户端的心跳检查来判断客户端是否存活。除非另有设置,发现客户端不会扩散当前的应用程序的健康检查状态到每个SpringBoot Actuator。
所以,应用成功注册之后,Eureka通常宣告应用程序为成功启动状态。这种行为可以通过启用Eureka的健康检查来改变,这将会导致扩散应用程序的状态到Eureka中。因此,每个其他应用程序都不会将流量发送到除“up”之外的其他状态的应用程序。下面的示例演示如何为客户端启用健康检查:
application.yml
eureka:
client:
healthcheck:
enabled: true
注意:eureka.client.healthcheck.enabled=true 只能被设置在 application.yml配置文件中.设置在bootstarp.yml中的话将导致不良副作用,如注册到Eureka服务器中的状态为UNKONWN.
如果你想要拥有健康检查更多的控制权,请考虑实现你自己的com.netflix.appinfo.HealthCheckHandler。
值得花点时间理解Eureka元数据是怎么工作的,因为你将在你的平台中找到一种合适的使用方式使用Eureka元数据。这里有标准的元数据信息比如主机名,ip地址,端口号,状态页,和健康检查。这些东西发布在服务注册表中并且被连接到服务的客户端直截了当的使用。额外的元数据可以在注册实例中的eureka.instance.metadataMap增加。一般来说 ,额外增加的元数据不会对客户端的行为造成影响,除非客户端知道元数据的含义。有一些特例,将在稍后的文档中介绍,SpringCloud已经对其赋与了特定的含义。
Cloud Foundry有一个全局的路由,所以同一个应用的所有实例都拥有相同的主机名(其他的PaaS解决方案也有类似的架构和相同的约定)。 这不一定是使用Eureka的障碍。 然而,如果你使用路由(建议性的,或者是强制的,取决于你的平台的设置方式),你需要特别的设置主机名和端口号(安全的或者不安全的)。你或许也想使用实例的元数据从而能区分客户端的各个实例(比如,自定义的负载均衡)。默认情况下,eureka.instance.instanceIdHowever就是vcap.application.instance_id,如下所示:
application.yml
eureka:
instance:
hostname: ${vcap.application.uris[0]}
nonSecurePort: 80
取决于在Cloud Foundry中的安全规则设置方式,你可能可以注册并使用虚拟机的IP地址来直接进行服务到服务的调用。此功能在Pivotal Web Services(PWS)上尚不可用。
如果应用打算发布到AWS中,Eureka实例必须设置成AWS-aware。你也可以通过自定义如下EurekaInstanceConfigBean方式:
@Bean
@Profile("!default")
public EurekaInstanceConfigBean eurekaInstanceConfig(InetUtils inetUtils) {
EurekaInstanceConfigBean b = new EurekaInstanceConfigBean(inetUtils);
AmazonInfo info = AmazonInfo.Builder.newBuilder().autoBuild("eureka");
b.setDataCenterInfo(info);
return b;
}
一个普通的Netflix Eureka注册的实例id和主机名是一样的(当然是在每个主机中只注册一个服务的情况下)。Spring Cloud提供了一个合理的默认值,如下所示:
s p r i n g . c l o u d . c l i e n t . h o s t n a m e : {spring.cloud.client.hostname}: spring.cloud.client.hostname:{spring.application.name}:KaTeX parse error: Expected '}', got 'EOF' at end of input: …on.instance_id:{server.port}}}
具体的例子: myhost:myappname:8080.
使用SpringCloud,你可以提供一个唯一的标识符来覆盖eureka.instance.insteanceId,如下所示:
eureka:
instance:
instanceId: ${spring.application.name}:${vcap.application.instance_id:${spring.application.instance_id:${random.value}}}
使用前面示例中显示的元数据和部署在本地主机上的多个服务实例,将在其中插入随机值以使实例唯一。在Cloud Foundry中,vcap.application.instance_id在Spring Boot应用中会被自动填充,因此随机值不是必要的。
一旦你有一个服务发现客户端应用,你可以用它从Eureka服务器中发现服务实例。一种实现方式是使用本地的com.netflix.discovery.EurekaClient(而不是SpringCloud的DiscoveryClient),如下例子所示:
@Autowired
private EurekaClient discoveryClient;
public String serviceUrl() {
InstanceInfo instance = discoveryClient.getNextServerFromEureka("STORES", false);
return instance.getHomePageUrl();
}
注意:
不要在使用注解@PostConstruct或者@Scheduled的方法上使用EurekaClient(或者可能没启动的应用程序上下文中)。它是在SmartLifecycle(phase=0)中初始化的,因此最早可以依赖它的是另一个具有更高phase的SmartLifecycle。
默认情况下,EurekaClient使用Jersy来进行HTTP通信。如果你不想依赖Jersey,你可以将它从你的依赖中排除。Spring Cloud自动配置了基于Spring RestTemplate的转发客户端。下面的例子展示了如何排除Jersey依赖:
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
com.sun.jersey
jersey-client
com.sun.jersey
jersey-core
com.sun.jersey.contribs
jersey-apache-client4
你不不许需要未经封装的Netflix EurekaClient,当然,经过某种包装后它通常变得更加方便。SpringCloud通过逻辑的Eureka服务标识符来替换物理的URLs来支持Fegin(一个Rest client builder)和Spring RestTemplate。通过设置Ribbon物理服务器的固定列表,你可以设置.ribbon.listOfServers为一个逗号分隔的物理地址(或主机名)列表,其种是客户端的id。
你也可以使用org.springframework.cloud.client.discovery.DiscoveryClient,它提供了简单的API(不特定指Netflix)来发现客户端,如下所示:
@Autowired
private DiscoveryClient discoveryClient;
public String serviceUrl() {
List<ServiceInstance> list = discoveryClient.getInstances("STORES");
if (list != null && list.size() > 0 ) {
return list.get(0).getUri();
}
return null;
}
做为一个实例当然有一个到注册中心的周期性心跳检查(通过客户端的服务url),默认间隔是30秒。服务直到实例,服务器,和客户端在各自的本地缓存中拥有相同的元数据才会有效(所以要花费3个心跳的时间)。你可以设置eureka.instance.leaseRenewalIntervalInSeconds来改变心跳的间隔。设置小于30秒的心跳间隔将减少服务发现的时间。在生产环境中,最好还是使用默认的心跳间隔,因为服务器中的内部计算对租约续约期进行了假设。
如果你把Eureka客户端发布到了多个分区中,再另一个分区中尝试调用服务之前,你可能更喜欢客户端使用同一个分区中的服务。要达到这样的效果,你需要正确的配置你的Eureka客户端。
首先,你需要确保你有Eureka服务发布到各个分区中,并且他们彼此是对等的。查看关于分区的说明获取更多信息。
接下来,你需要告诉Eureka你的服务在哪个分区中。你可以通过metadataMap属性来做这件事情。比如service1同时发布到了Zone1和Zone2中,你需要在Service1中配置如下所示的Eureka配置文件:
application.yml
server:
port: 8761
eureka:
instance:
hostname: localhost
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
注意serviceUrl指向的主机与本地实例相同。
本部分描述了如何设置Eureka Server
使用group ID为org.springframework.cloud和artifact ID为spring-cloud-starter-netflix-eureka-server的starter依赖即可将Eureka server引入到你的项目中。可查看Spring Cloud Project Page查看更详细的设置信息。
注意:在你的项目中如果已经使用了Thymeleaf作为模板引擎,Eureka server的Freemarker模板有可能不能被正确装载。这种情况下你可以手动配置模板加载器:
application.yml
spring:
freemarker:
template-loader-path: classpath:/templates/
prefer-file-system-access: false
下面是一个极简的EurekaServer例子:
@SpringBootApplication
@EnableEurekaServer
public class Application {
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class).web(true).run(args);
}
}
该服务有一个有界面的主页和在路径/eureka/*
下有一个一般功能的HTTP API终端。
Eureka服务器没有后端存储,但是在注册中心的服务实例都会通过发送心跳来保持最新状态(所以这可以在内存中完成)。客户端也有一份在内存中缓存的Eureka注册中心信息(所以它们不必每次请求服务都通过注册中心)。
默认情况下,每个Eureka服务端或者Eureka客户端都必须(至少一个)通过URL来定位其它兄弟节点。如果你没有提供该URL,服务也可以运行和工作,但是日志中会充斥大量无法注册兄弟节点的信息。
可查看Ribbon客户端支持了解更详细的信息。
两个缓存(客户机和服务器)和心跳的结合使得一个独立的Eureka服务器能够很好地抵御故障,只要有某种类型的监视器或者弹性运行(比如Cloud Foundry),它就可以保持运行。在独立模式中,你可能更喜欢关掉客户端的行为,从而使其不重试或者将自身错误发送至兄弟节点。下面的例子展示了如何关掉客户端机器的行为:
application.yml(Standalone Mode)
server:
port: 8761
eureka:
instance:
hostname: localhost
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
注意serviceUrl指向的是本地实例的同一台主机。
Eureka实际上可以做得更弹性和高可用,通过云心多个实例,并且让他们互相注册。实际上,这是默认的行为方式,所以你需要增加一个有效的serviceUrl到各个兄弟节点,使其能有效工作。如下所示:
**application.yml (Two Peer Aware Eureka Servers). **
---
spring:
profiles: peer1
eureka:
instance:
hostname: peer1
client:
serviceUrl:
defaultZone: http://peer2/eureka/
---
spring:
profiles: peer2
eureka:
instance:
hostname: peer2
client:
serviceUrl:
defaultZone: http://peer1/eureka/
在上面的例子中,这个yaml文件可以在两台服务器中(peer1和peer2)使用不同的Spring Profile运行同一个Eureka服务。通过修改/etc/host文件解决hostname问题后你可以使用这个配置文件在同一台服务器上(在生产环境中这样做没有多大价值)测试集群模式。实际上,如果你运行在一台主机中,eureka.instance.hostname不是必须的,因为Eureka知道自己的hostname(默认情况下,是使用java.net.InetAddress来查找的)。
你可以在系统中增加多个节点,只要他们都至少通过一个边界彼此连接,他们之间同步注册信息。如果各兄弟节点被物理隔离(在一个数据中心或者多个数据中心),然后,原则上,该系统可以在“脑裂”类型的故障中存活下来。你可以增加多个节点到系统中,只要它们直接的连接彼此,他们会彼此间同步注册信息:
**application.yml (Three Peer Aware Eureka Servers). **
eureka:
client:
serviceUrl:
defaultZone: http://peer1/eureka/,http://peer2/eureka/,http://peer3/eureka/
---
spring:
profiles: peer1
eureka:
instance:
hostname: peer1
---
spring:
profiles: peer2
eureka:
instance:
hostname: peer2
---
spring:
profiles: peer3
eureka:
instance:
hostname: peer3
在某些场景中,为了让Eureka更好的广播ip地址而不是hostname,可设置eureka.instance.preferIpAddress=true,当应用被注册到Eureka中时,使用的是IP地址而不是hostname。
注:如果java不能确定主机名,IP地址将被发送到Eureka中。唯一明确设置主机名的方法是设置eureka.instance.hostname属性。你可以使用环境变量在运行时设置主机名,如:
eureka.instance.hostname=${HOST_NAME}.
你可以通过引入spring-boot-starter-security将Spring Security加到你的服务的classpath中让你的EurekaServer更安全。默认情况下,如果Spring Security在classpath中,在请求各个应用时必须要有一个有效的CSRF 令牌。Eureka客户端通常不会拥有有效的CSRF令牌,您需要禁用/Eureka/**端点的此要求。例:
@EnableWebSecurity
class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().ignoringAntMatchers("/eureka/**");
super.configure(http);
}
}
查看更多关于CSRF信息可查看Spring security文档
点击Spring cloud 仓库查看Eureka server的例子。
Eureka Server依赖的JAXB模块在JDK11中被删除掉了。如果你用JDK11运行Eureka Server必须引入以下依赖项:
org.glassfish.jaxb
jaxb-runtime
参考文档:https://cloud.spring.io/spring-cloud-netflix/multi/multi_spring-cloud-netflix.html