【系统架构理论】一篇文章精通:Spring Cloud Netflix Eureka

是官方文档的总结

http://spring.io/projects/spring-cloud-netflix#overview

讲解基于2.0.2版本官方文档

https://cloud.spring.io/spring-cloud-static/spring-cloud-netflix/2.0.2.RELEASE/single/spring-cloud-netflix.html

Netflix提供了以下功能:

  • 服务发现:可以注册Eureka实例,客户端可以使用Spring的beans来发现这些实例
  • 服务发现:可以通过声明式Java配置来创建一个嵌入式Eureka服务器
  • 断路器:可以通过简单的注解修饰方法来创建一个断路器客户端
  • 断路器:使用声明式Java配置可以嵌入一个Hystrix仪表盘
  • 客户端负载均衡器:Ribbon
  • 外部配置:从Spring环境到Archaius的桥梁(允许使用SpringBoot约定对Netflix组件进行本地配置)
  • 路由器和过滤器:Zuul过滤器的自动重新配置,和用于反向创建代理的配置方法的简单约定。

一、服务发现:Eureka客户端

服务发现是基于微服务的体系结构的核心原则之一。尝试手工配置每个客户端或某种形式的约定可能很困难,而且很脆弱。Eureka是Netflix服务发现服务器和客户端。可以将服务器配置和部署为高度可用,每个服务器都将注册服务的状态复制给其他服务器。

1.1、包含依赖

group ID:org.springframework.cloud

artifact ID:spring-cloud-starter-netflix-eureka-client

具体版本可以查看http://spring.io/projects/spring-cloud,查看对应Spring Cloud版本匹配的Eureka客户端版本

1.2、注册到Eureka中

当客户端向Eureka注册时,它会提供关于自己的元数据,例如主机、端口、健康指示符URL、主页和其他详细信息。Eureka从属于服务的每个实例接收心跳消息。如果心跳在可配置的时间表上失败,则通常从注册表中删除该实例。

@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);
}
}

使用Spring Boot应用,只要classpath中包含了spring-cloud-starter-netflix-eureka-client,应用就会自动注册到Eureka服务器

要求通过配置来指向Eureka服务器

eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/

defaultZone:当客户端没有明确指向一个Eureka服务器时,会采用这个默认URL。

默认应用程序名称(即服务ID)、虚拟主机和非安全端口(从环境获取)分别为${Spring.application.name}、${Spring.application.name}和${server.port}。

当classpath中包含了spring-cloud-starter-netflix-eureka-client时,使这个应用既是一个Eureka实例(把自己注册到Eureka服务器中),也是一个Eureka客户端(他可以查询Eureka服务器定位其它服务)。

实例行为可以通过eureka.instance.*的属性来配置,也可以通过设置spring.application.name(作为Eureka服务的ID或VIP)并作为实例行为的默认值。

更多配置属性的信息,查看这2个Bean:EurekaInstanceConfigBean、EurekaClientConfigBean

https://github.com/spring-cloud/spring-cloud-netflix/tree/master/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/EurekaInstanceConfigBean.java

https://github.com/spring-cloud/spring-cloud-netflix/blob/master/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/EurekaClientConfigBean.java

禁止使用Eureka客户端,可以设置eureka.client.enabled为false

1.3、通过Eureka服务器的验证

在eureka.client.serviceUrl.defaultZone的路径中嵌入一个凭证(http://user:password@localhost:8761/eureka),会自动为你提供一个HTTP basic认证。

如果需要更加复杂的需求,可以创建一个DiscoveryClientOptionalArgs的Bean,并为他注入一个ClientFilter实例,用于客户端请求服务器时使用。

由于Eureka中的限制,不可能支持每个服务器的basic认证凭据,因此只使用了找到的第一组。

1.4、状态页面和健康指标

默认路径是Spring Boot中Actuator下的/info和/health地址,

如果你使用非默认上下文路径或非默认servlet路径,则需要修改他们。

eureka:
instance:
statusPageUrlPath: ${server.servletPath}/info
healthCheckUrlPath: ${server.servletPath}/health

在Dalston版本中,还要求在更改管理上下文路径时设置状态和健康检查URL。从Edgware开始,这一要求就被删除了。

1.5、注册一个安全的应用

如果应用需要通过HTTPS传输,可以设置2个属性

eureka.instance.[nonSecurePortEnabled]=[false]

eureka.instance.[securePortEnabled]=[true]

设置了之后,Eureka客户端会返回https的地址。Eureka的实例信息也会有一个安全的健康检查URL

由于Eureka的设计,status页、home页并不会因为这个设置变成安全链接,需要手动更改。

eureka:
instance:
statusPageUrl: https://${eureka.hostname}/info
healthCheckUrl: https://${eureka.hostname}/health
homePageUrl: https://${eureka.hostname}/

${eureka.hostname}是一个本地占位符,最新版本的Eureka才支持;也可以使用Spring占位符${eureka.instance.hostName}

If your application runs behind a proxy, and the SSL termination is in the proxy (for example, if you run in Cloud Foundry or other platforms as a service), then you need to ensure that the proxy “forwarded” headers are intercepted and handled by the application. If the Tomcat container embedded in a Spring Boot application has explicit configuration for the 'X-Forwarded-\*` headers, this happens automatically. The links rendered by your app to itself being wrong (the wrong host, port, or protocol) is a sign that you got this configuration wrong.

1.6、Eureka健康检查

默认情况下,Eureka使用心跳机制决定一个客户端是否在线。

除非特殊设置,Eureka客户端不会通过Actuator来传递应用的健康信息。

可以开启Eureka健康检查,

。。待补充

1.7、Eureka实例和客户端的元数据

使用eureka.instance.metadataMap可以添加元数据,这些元数据可以在远程客户端中获取。

一般元数据不会改变客户端的行为,除了那些客户端能识别到这些元数据的含义,有些元数据Spring Cloud已经在使用。

这个是用于部署在云平台,然后可以获取平台的一些数据?

1.8、使用Eureka客户端

Eureka客户端,可以通过Eureka服务器来发现其他服务的实例。

可以使用com.netflix.discovery.EurekaClient

@Autowired
private EurekaClient discoveryClient; public String serviceUrl() {
InstanceInfo instance = discoveryClient.getNextServerFromEureka("STORES", false);
return instance.getHomePageUrl();
}

Do not use the EurekaClient in a @PostConstruct method or in a @Scheduledmethod (or anywhere where the ApplicationContext might not be started yet). It is initialized in a SmartLifecycle (with phase=0), so the earliest you can rely on it being available is in another SmartLifecycle with a higher phase.

1.8.1、禁用Jersey

默认情况下Eureka客户端使用Jersey进行HTTP通信。

可以通过排除依赖禁用他,然后Spring Cloud会通过Spring RestTemplate来进行HTTP通信。

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<exclusions>
<exclusion>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-client</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-core</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jersey.contribs</groupId>
<artifactId>jersey-apache-client4</artifactId>
</exclusion>
</exclusions>
</dependency>

1.9、本地Netflix Eureka客户端的替代方案

一般不会使用上面的EurekaClient来获取真正的物理地址。

Spring Cloud提供了一个Feign和Spring RestTemplate,可以通过Eureka服务的识别码(如服务ID或VIP)来访问服务,而不是通过物理地址。

如果要用一个固定服务器物理地址列表来配置Ribbon,可以通过设置<client>.ribbon.listOfServers,使用一个逗号分隔的物理地址(或hostnames),<client>是客户端的ID

也可以使用org.springframework.cloud.client.discovery.DiscoveryClient,包含针对服务发现客户端(不仅是Netflix)的一些API

@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;
}

1.10、为什么注册一个服务这么慢?

Being an instance also involves a periodic heartbeat to the registry (through the client’s serviceUrl) with a default duration of 30 seconds. A service is not available for discovery by clients until the instance, the server, and the client all have the same metadata in their local cache (so it could take 3 heartbeats). You can change the period by setting eureka.instance.leaseRenewalIntervalInSeconds. Setting it to a value of less than 30 speeds up the process of getting clients connected to other services. In production, it is probably better to stick with the default, because of internal computations in the server that make assumptions about the lease renewal period.

作为一个实例,还涉及到注册表的周期性心跳(通过客户端的serviceUrl),默认持续时间为30秒。直到实例、服务器和客户端在其本地缓存中都有相同的元数据(因此可能需要3次心跳),客户端才能发现服务。可以通过设置eureka.instance.leaseRenewalIntervalInSeconds.更改期间将其设置为小于30的值可以加快将客户端连接到其他服务的过程。在生产中,可能最好还是坚持默认的做法,因为服务器中的内部计算会对租约续订期做出假设。

1.11、分区

如果你的Eureka客户端分开了几个区域,并且希望调用服务时,先在相同区域调用服务,然后才调用其它区域的服务。

这就需要进行正确的配置。

首先,要吧Eureka服务器部署到每一个区域,并且他们之间是对等服务器

然后,你需要告诉Eureka服务器,你的服务是在哪一个区域,可以通过修改metadataMap属性来实现

//Service 1 in Zone 1
eureka.instance.metadataMap.zone = zone1
eureka.client.preferSameZoneEureka = true
//Service 1 in Zone 2
eureka.instance.metadataMap.zone = zone2
eureka.client.preferSameZoneEureka = true

二、Eureka服务器

2.1、如何包含Eureka服务器

group ID:org.springframework.cloud

artifact ID:spring-cloud-starter-netflix-eureka-server

具体版本可以查看http://spring.io/projects/spring-cloud,查看对应Spring Cloud版本匹配的Eureka客户端版本

2.2、运行一个Eureka服务器

@SpringBootApplication
@EnableEurekaServer
public class Application { public static void main(String[] args) {
new SpringApplicationBuilder(Application.class).web(true).run(args);
} }

服务器有一个首页和一些HTTP API端点在/eureka/*下

有几个Eureka的讨论网站?

https://github.com/cfregly/fluxcapacitor/wiki/NetflixOSS-FAQ#eureka-service-discovery-load-balancer

https://groups.google.com/forum/?fromgroups#!topic/eureka_netflix/g3p2r7gHnN0

Due to Gradle’s dependency resolution rules and the lack of a parent bom feature, depending on spring-cloud-starter-netflix-eureka-server can cause failures on application startup. To remedy this issue, add the Spring Boot Gradle plugin and import the Spring cloud starter parent bom as follows:

build.gradle. 

buildscript {
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:{spring-boot-docs-version}")
}
} apply plugin: "spring-boot" dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:{spring-cloud-version}"
}
}

2.3、高可用,分区与区域(High Availability, Zones and Regions)

Eureka没有后端存储,但是服务实例必须保持发送心跳消息来保持他们的注册是最新的(在内存中实现)。

客户端也有一个对Eureka注册服务的内存缓存,这使得不用每一次调用服务,都去查找一次注册的服务。

默认情况下,每个Eureka服务器也是Eureka客户端,需要(至少一个)服务URL来定位对等服务器。如果你不提供,服务也能运行,只是在日志中会增加大量无法向服务器注册的信息。

2.4、单例模式

通过2个缓存(服务器、客户端)以及心跳机制,使得只要提供一些监控或者运行时弹性(如Cloud Foundry)保持一个Eureka服务器持续运行,他就有很强的抗错误弹性。

在单例模式下,你可以考虑关闭掉客户端行为,就不会一直尝试注册到不存在的服务器路径失败

关闭掉客户端

server:
port: 8761 eureka:
instance:
hostname: localhost
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

注意defaultZone指向本地Eureka实例

2.5、同伴意识

Eureka可以通过运行多个实例,并互相注册,来实现更高可用高弹性。

实际上,这是一个默认行为,只需要指定一个可用的serviceUrl给同伴即可。

---
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文件,通过在不同的Spring profiles中运行该文件,可以在两个主机(peer 1和peer 2)上运行相同的服务器。您可以使用此配置来通过操作/etc/host来解析主机名来测试单个主机上的对等感知(在生产中这样做没有多大价值)。实际上,如果在知道自己主机名的机器上运行(默认情况下,使用java.net.InetAddress查找),则不需要eureka.instance.hostname。

你可以添加多个同伴到一个系统中,只要他们互相通过至少一个连接而相连,他们之间的注册实例就会互相同步。如果这些同伴是物理隔离的,那么这个系统就有能力抵抗脑裂(split-brain)失败。

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

2.6、什么时候使用IP地址

在某些情况下,Eureka最好是公布服务的IP地址,而不是主机名。

设置eureka.instance.preferIpAddress为true,程序注册到Eureka中的时候,会使用IP地址二不是hostname

如果主机名不能由Java确定,则将IP地址发送给Eureka。惟一明确设置主机名的方法是设置eureka.instance.hostname属性。可以通过使用环境变量(例如eureka.instance.hostname=${host_name})在运行时设置主机名。

2.7、保护Eureka服务器

您只需通过spring-boot-starter-security将Spring Security添加到服务器的类路径中,就可以保护您的Eureka服务器。默认情况下,当Spring Security在类路径上时,它将要求在向应用程序的每个请求中发送一个有效的CSRF令牌。Eureka客户端通常不会拥有有效的跨站点请求伪造(CSRF)令牌,您需要对/eureka/*端点禁用此要求。例如:

@EnableWebSecurity
class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().ignoringAntMatchers("/eureka/**");
super.configure(http);
}
}
上一篇:ES6 类(Class)基本用法和静态属性+方法详解


下一篇:UVa 120 (构造) Stacks of Flapjacks