3、SpringCloud Netflix,服务发现之Eureka详解

专栏收录该内容

SpringCloud Netflix

与各种Netflix OSS组件(Eureka、Hystrix、Zuul、Archaius等)集成。

参考文档:https://spring.io/projects/spring-cloud-netflix


Spring Cloud Netflix通过自动配置和绑定到Spring环境和其他Spring编程模型习惯用法,为Spring Boot应用程序提供Netflix OSS集成。通过一些简单的注释,您可以在应用程序中快速启用和配置常见模式,并使用经过实战测试的Netflix组件构建大型分布式系统。提供的模式包括服务发现(Eureka)。



特点

服务发现(Service Discovery):可以注册Eureka实例,客户端可以使用Spring托管bean发现实例

服务发现(Service Discovery):可以使用声明性Java配置创建嵌入式Eureka服务器



入门

只要有Spring Cloud Netflix和Eureka Core依赖,任何带有@EnableEurekaClient 注解的Spring Boot应用程序都会尝试关联Eureka服务器 http://localhost:8761(eureka.client.serviceUrl.defaultZone的默认值)

@SpringBootApplication
@EnableEurekaClient
@RestController
public class Application {

  @RequestMapping("/")
  public String home() {
    return "Hello World";
  }

  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }

}

要运行自己的服务器,请使用spring-cloudstarter netflix eureka服务器依赖项和@EnableEurekaServer。




服务发现(Service Discovery)

参考:https://docs.spring.io/spring-cloud-netflix/docs/current/reference/html


服务发现是基于微服务的体系结构的关键原则之一。

尝试手动配置每个客户端或某种形式的约定可能很难做到,而且可能很脆弱。Eureka是Netflix服务发现服务器和客户端。可以将服务器配置和部署为高度可用,每个服务器都将注册服务的状态复制到其他服务器。



Eureka Server

如何包含Eureka服务端?

group ID of org.springframework.cloud

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

Maven示例

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

如果您的项目已经使用Thymelaf作为模板引擎,Eureka服务器的Freemark模板可能无法正确加载。在这种情况下,需要手动配置模板加载器:

application.yml

spring:
  freemarker:
    template-loader-path: classpath:/templates/
    prefer-file-system-access: false


以下示例显示了最小Eureka服务器:

@SpringBootApplication
// 此注解代表Eureka服务器
@EnableEurekaServer
public class Application {

    public static void main(String[] args) {
        // 代码是官方教程内的,自己尝试后发现没有 web(boolean) 的函数
        // new SpringApplicationBuilder(Application.class).web(true).run(args);
        // 使用以下代码替代
        SpringApplication.run(Application.class, args);
    }

}


该服务器有一个主页,带有UI和HTTP API端点,用于/euroka/*下的正常Eureka功能。

以下链接有一些Eureka背景阅读:flux capacitorgoogle group discussion.


小提示

由于Gradle的依赖性解析规则和缺少父bom特性,依赖于spring云启动器netflix eureka服务器可能会导致应用程序启动失败。要解决此问题,请添加Spring Boot Gradle插件并导入Spring云启动器父bom,如下所示:

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}"
  }
}

defaultOpenForTrafficCount及其对EurekaServer预热时间的影响

Netflix Eureka的 waitTimeInMsWhenSyncEmpty 设置一开始在Spring Cloud Eureka服务器中没有考虑。为了启用预热时间,请将 eureka.server.defaultOpenForTrafficCount 设置为0。



高可用性、区域和地区

Eureka服务器没有后端存储,但注册表中的服务实例都必须发送心跳以保持其注册最新(因此这可以在内存中完成)。客户机还具有Eureka注册的内存缓存(因此他们不必为每个服务请求都去注册表)。


默认情况下,每个Eureka服务器也是Eureka客户端,需要(至少一个)服务URL来定位对等端。如果您不提供它,服务将运行并工作,但它会给您的日志带来很多无法向对等方注册的噪音。



独立模式

这两个缓存(客户端和服务器)和心跳的结合使得一个独立的Eureka服务器能够很好地应对故障,只要有某种监视器或弹性运行时(如CloudFoundry)保持其运行。在独立模式下,您可能更喜欢关闭客户端行为,这样它就不会一直尝试和无法到达对等端。以下示例显示了如何关闭客户端行为:

application.yml (独立Eureka Server)

server:
  port: 8761

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

application.properties

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

请注意,serviceUrl 指向与本地实例相同的主机。

eureka.client.registerWithEureka:false 和 fetchRegistry:false 来表明自己是一个eureka server.


运行项目

运行控制台输出大致如下

Spring Cloud Netflix Eureka


然后可以通过浏览器访问设置的serviceUrl地址进入eureka server界面,例如上面的示例地址为 http://localhost:8761

Spring Eureka


同伴意识(Peer Awareness)

通过运行多个实例并要求它们彼此注册,Eureka可以变得更具弹性和可用性。事实上,这是默认行为,因此要使其正常工作,只需向对等方添加有效的serviceUrl,如下例所示:

application.yml (Two Peer Aware Eureka Servers)

---
spring:
  profiles: peer1
eureka:
  instance:
    hostname: peer1
  client:
    serviceUrl:
      defaultZone: https://peer2/eureka/

---
spring:
  profiles: peer2
eureka:
  instance:
    hostname: peer2
  client:
    serviceUrl:
      defaultZone: https://peer1/eureka/

在上面的示例中,我们有一个YAML文件,通过在不同的Spring配置文件中运行,它可以用于在两个主机(peer1和peer2)上运行同一个服务器。您可以使用此配置通过操作/etc/hosts解析主机名来测试单个主机上的对等感知(在生产环境中这样做没有多大价值)。事实上,如果您运行的机器知道自己的主机名(默认情况下,使用java.net.InetAddress查找),则不需要eureka.instance.hostname。


您可以将多个对等体添加到系统中,只要它们都通过至少一个边缘彼此连接,它们就可以在自己之间同步注册。如果对等体在物理上是分开的(在一个数据中心内或多个数据中心之间),那么原则上,系统可以在“分裂大脑”类型的故障中幸存下来。您可以将多个对等体添加到系统中,只要它们都彼此直接连接,它们就会同步注册。


application.yml (Three Peer Aware Eureka Servers)

eureka:
  client:
    serviceUrl:
      defaultZone: https://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

何时首选IP地址

在某些情况下,Eureka最好公布服务的IP地址,而不是主机名。将 eureka.instance.preferIpAddress 设置为 true ,当应用程序向eureka注册时,将使用其IP地址而不是主机名。


小提示:

如果Java无法确定主机名,则将IP地址发送给Eureka。设置主机名的唯一明确方法是设置eureka.instance.hostname属性。可以使用环境变量在运行时设置主机名 — 例如,eureka.instance.hostname=${HOST_NAME}.


保护Eureka服务器

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

@EnableWebSecurity
class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().ignoringAntMatchers("/eureka/**");
        super.configure(http);
    }
}

JDK 11支持

Eureka服务器所依赖的JAXB模块在JDK 11中被删除。如果您打算在运行Eureka服务器时使用JDK 11,则必须在POM或Gradle文件中包含这些依赖项。


<dependency>
    <groupId>org.glassfish.jaxb</groupId>
    <artifactId>jaxb-runtime</artifactId>
</dependency>


Eureka Clients


如何包含Eureka客户端?

要在项目中包含Eureka客户端,请使用 group ID为 org.springframework.cloud 和artifact ID为 spring-cloud-starter-netflix-eureka-client

有关使用当前SpringCloudRelease Train设置构建系统的详细信息,请参阅SpringCloudProject页面。


Maven示例

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>


注册 Eureka

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


以下示例显示了一个最小的Eureka客户端应用程序:

@SpringBootApplication
@RestController
public class Application {

    @RequestMapping("/")
    public String home() {
        return "Hello world";
    }

    public static void main(String[] args) {
		// 代码是官方教程内的,自己尝试后发现没有 web(boolean) 的函数
        // new SpringApplicationBuilder(Application.class).web(true).run(args);
        // 使用以下代码替代
        SpringApplication.run(Application.class, args);
    }

}

注意,上面的示例显示了一个普通的Spring Boot应用程序。通过在类路径上使用spring-cloud-starter-netflix-eureka-client客户端,您的应用程序将自动向eureka服务器注册。需要配置才能找到Eureka服务器,如下例所示:

application.yml

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

application.properties

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

在上面的示例中,defaultZone是一个魔术字符串回退值,它为任何不表示偏好的客户端提供服务URL(换句话说,它是一个有用的默认值)。

注意:defaultZone区分大小写


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


在类路径上安装 spring-cloud-starter-netflix-eureka-client,使应用程序既成为一个eureka“实例”(即它自己注册),又成为一个“客户端”(它可以查询注册表以查找其他服务)。实例行为由 eureka.instance.* 配置键驱动,但如果您确保应用程序具有 spring.application.name 值(这是eureka服务ID或VIP的默认值),则默认值也可以。

后续服务与服务之间相互调用一般都是使用 spring.application.name 的值


有关可配置选项的更多详细信息,请参见 EurekaInstanceConfigBeanEurekaClientConfigBean


要禁用Eureka Discovery Client,可以将 Eureka.Client.enabled 设置为 false 。当spring.cloud.Discovery.enabled 设置为 false 时,Eureka Discovery Client也将被禁用。


当前不支持将Spring Cloud Netflix Eureka服务器的版本指定为路径参数。这意味着不能在上下文路径(eurekaServerURLContext)中设置版本。相反,您可以在服务器URL中包含版本(例如,可以设置defaultZone:localhost:8761/eureka/v2)。



使用Eureka服务器进行身份验证

如果其中一个 eureka.client.serviceUrl.defaultZone URL中嵌入了凭据(curl样式,如下所示:user:password@localhost:8761/eureka)。对于更复杂的需求,您可以创建一个DiscoveryClientOptionalArgs 类型的 @Bean,并将 ClientFilter 实例注入其中,所有这些都应用于从客户端到服务器的调用。


当Eureka服务器需要客户端证书进行身份验证时,可以通过财产配置客户端证书和信任存储,如下例所示:

application.yml

eureka:
  client:
    tls:
      enabled: true
      key-store: <path-of-key-store>
      key-store-type: PKCS12
      key-store-password: <key-store-password>
      key-password: <key-password>
      trust-store: <path-of-trust-store>
      trust-store-type: PKCS12
      trust-store-password: <trust-store-password>

eureka.client.tls.enabled 需要为true才能启用eureka客户端tls。当省略 eureka.client.tls.trust-store 时,将使用JVM默认信任存储。eureka.client.tls.key-store-typeeureka.client.tls.trust-store-type 的默认值为PKCS12。如果省略密码财产,则假定密码为空。


由于Eureka的限制,不可能支持每台服务器的基本身份验证凭据,因此只使用找到的第一组凭据。

如果您想要自定义Eureka HTTP客户端使用的RestTemplate,您可能需要创建EurekaClientHttpRequestFactorySupplier 的bean,并提供自己的逻辑来生成 ClientHttpRequestFactory 实例。

Eureka HTTP客户端使用的RestTemplate的所有默认超时相关财产都设置为3分钟(与Apache HC5默认RequestConfigSocketConfig 保持一致)。因此,要指定超时值,必须使用eureka.client.rest-template-timeout中的财产直接指定该值。(所有超时财产均以毫秒为单位。)

application.yml

eureka:
  client:
    rest-template-timeout:
      connect-timeout: 5000
      connect-request-timeout: 8000
      socket-timeout: 10000


状态页和运行状况指示器

Eureka实例的状态页和健康指示器分别默认为 /info/health ,这是Spring Boot Actuator应用程序中有用端点的默认位置。如果您使用非默认的上下文路径或servlet路径(例如 server.servletPath=/custom),即使对于执行器应用程序,也需要更改这些设置。以下示例显示了两个设置的默认值:

application.yml

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

这些链接显示在客户端使用的元数据中,并在某些场景中用于决定是否向应用程序发送请求,因此如果它们是准确的,将非常有用。

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



注册安全应用程序

如果您的应用程序希望通过HTTPS进行联系,可以在EurekaInstanceConfigBean中设置两个标志:

  • eureka.instance.[nonSecurePortEnabled]=[false]
  • eureka.instance.[securePortEnabled]=[true]

这样做会使Eureka发布实例信息,显示出对安全通信的明确偏好。对于这样配置的服务,Spring Cloud DiscoveryClient始终返回以https开头的URI。类似地,当以这种方式配置服务时,Eureka(本机)实例信息具有安全的健康检查URL。


由于Eureka内部的工作方式,它仍然为状态和主页发布一个非安全URL,除非您也显式覆盖这些URL。您可以使用占位符来配置eureka实例URL,如下例所示:

application.yml

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

(注意,${eureka.hostname} 是一个本机占位符,仅在eureka的更高版本中可用 — 例如,通过使用${eureka.instance.hostName})


如果您的应用程序在代理后面运行,并且SSL终止在代理中(例如,如果您在Cloud Foundry或其他平台上作为服务运行),那么您需要确保代理“转发”的标头被应用程序拦截和处理。如果嵌入在Spring Boot应用程序中的Tomcat容器对“X-Forwarded-*”标头进行了显式配置,则会自动发生这种情况。应用程序自身呈现的链接错误(主机、端口或协议错误)表明您的配置错误。



Eureka的健康检查

默认情况下,Eureka使用客户端心跳来确定客户端是否启动。除非另有规定,否则Discovery Client不会根据Spring Boot Actuator传播应用程序的当前健康检查状态。因此,在成功注册后,Eureka总是宣布应用程序处于“UP”状态。可以通过启用Eureka健康检查来更改此行为,从而将应用程序状态传播到Eureka。因此,每个其他应用程序都不会向处于“UP”状态之外的应用程序发送流量。以下示例显示如何为客户端启用运行状况检查:

application.yml

eureka:
  client:
    healthcheck:
      enabled: true

eureka.client.healthcheck.enabled=true 只应在 application.yml 中设置。在 bootstrap.yml 中设定值会产生不希望的副作用,例如在eureka中以 UNKNOWN 状态注册。


如果您需要对健康检查进行更多控制,请考虑实现您自己的 com.netflix.appinfo.HealthCheckHandler



Eureka实例和客户端元数据

值得花一点时间了解Eureka元数据的工作原理,这样您就可以在您的平台上以合理的方式使用它。主机名、IP地址、端口号、状态页和健康检查等信息有标准元数据。这些信息发布在服务注册表中,并由客户端以直接的方式联系服务。可以将其他元数据添加到 eureka.instance.metadataMap 中的实例注册中,并且可以在远程客户端中访问该元数据。通常,附加元数据不会改变客户端的行为,除非客户端知道元数据的含义。有一些特殊情况,如本文稍后所述,SpringCloud已经为元数据映射分配了意义。



在Cloud Foundry上使用Eureka

CloudFoundry有一个全局路由器,因此同一应用程序的所有实例都具有相同的主机名(具有类似架构的其他PaaS解决方案具有相同的布局)。这不一定是使用Eureka的障碍。但是,如果您使用路由器(建议使用或强制使用,取决于平台的设置方式),则需要明确设置主机名和端口号(安全或非安全),以便它们使用路由器。您可能还希望使用实例元数据,以便可以区分客户端上的实例(例如,在自定义负载平衡器中)。默认情况下,eureka.instance.instanceIdvcap.application.instance_id,如下例所示:

application.yml

eureka:
  instance:
    hostname: ${vcap.application.uris[0]}
    nonSecurePort: 80

根据在CloudFoundry实例中设置安全规则的方式,您可能能够注册并使用主机VM的IP地址进行直接服务到服务调用。此功能在Pivotal Web Services (PWS).上尚不可用。



在AWS上使用Eureka

如果计划将应用程序部署到AWS云,则必须将Eureka实例配置为支持AWS。您可以通过如下方式自定义EurekaInstanceConfigBean 来实现:

@Bean
@Profile("!default")
public EurekaInstanceConfigBean eurekaInstanceConfig(InetUtils inetUtils) {
  EurekaInstanceConfigBean bean = new EurekaInstanceConfigBean(inetUtils);
  AmazonInfo info = AmazonInfo.Builder.newBuilder().autoBuild("eureka");
  bean.setDataCenterInfo(info);
  return bean;
}


更改Eureka实例ID

一个普通的Netflix Eureka实例注册的ID等于其主机名(即每个主机只有一个服务)。Spring Cloud Eureka提供了一个合理的默认值,其定义如下:

${spring.cloud.client.hostname}:${spring.application.name}:${spring.application.instance_id:${server.port}}

例如:myhost:myappname:8080

通过使用Spring Cloud,可以通过在eureka.instance.instanceId中提供唯一标识符来覆盖该值,如下例所示:

application.yml

eureka:
  instance:
    instanceId: ${spring.application.name}:${vcap.application.instance_id:${spring.application.instance_id:${random.value}}}

使用上面示例中显示的元数据和部署在本地主机上的多个服务实例,随机值将插入其中以使实例唯一。在Cloud Foundry中,vcap.application.instance_id 在Spring Boot应用程序中自动填充,因此不需要随机值。



使用EurekaClient

一旦您有了作为发现客户端的应用程序,就可以使用它从Eureka服务器发现服务实例。一种方法是使用本机com.netflix.discovery.EurekaClient(与Spring Cloud DiscoveryClient相反),如下例所示:

@Autowired
private EurekaClient discoveryClient;

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

不要在 @PostConstruct 方法或 @Scheduled 方法中使用 EurekaClient(或尚未启动ApplicationContext的任何地方)。它在一个SmartLifecycle中初始化(阶段=0),因此您最早可以依赖它在另一个具有更高阶段的SmartLifecycle中可用。


EurekaClient与Jersey

默认情况下,EurekaClient使用Spring的RestTemplate进行HTTP通信。如果希望改用Jersey,则需要将Jersey依赖项添加到类路径中。以下示例显示了需要添加的依赖项:

<dependency>
    <groupId>com.sun.jersey</groupId>
    <artifactId>jersey-client</artifactId>
</dependency>
<dependency>
    <groupId>com.sun.jersey</groupId>
    <artifactId>jersey-core</artifactId>
</dependency>
<dependency>
    <groupId>com.sun.jersey.contribs</groupId>
    <artifactId>jersey-apache-client4</artifactId>
</dependency>


本地Netflix EurekaClient的替代方案

您不需要使用原始Netflix EurekaClient。此外,在某种包装器后面使用它通常更方便。Spring Cloud通过逻辑Eureka服务标识符(VIP)而不是物理URL支持Feign(REST客户端构建器)和Spring RestTemplate。


您还可以使用 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;
}


为什么注册服务如此缓慢?

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



区域

如果您已将Eureka客户端部署到多个区域,则在尝试其他区域中的服务之前,您可能更希望这些客户端在同一区域中使用服务。要进行设置,您需要正确配置Eureka客户端。


首先,您需要确保每个区域都部署了Eureka服务器,并且它们是彼此的对等服务器。有关详细信息,请参见分区和区域部分。


接下来,您需要告诉Eureka您的服务所在的区域。您可以通过使用metadataMap属性来做到这一点。例如,如果服务1同时部署到区域1和区域2,则需要在服务1中设置以下Eureka财产:

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客户端

默认情况下,EurekaClientbean是可刷新的,这意味着可以更改和刷新Eureka客户端财产。当刷新发生时,客户端将从Eureka服务器注销,并且可能会有一段短暂的时间,给定服务的所有实例都不可用。消除这种情况的一种方法是禁用刷新Eureka客户端的功能。为此,设置 eureka.client.refresh.enable=false



将Eureka与Spring Cloud LoadBalancer一起使用

我们为Spring Cloud LoadBalancer ZonePreferencesServiceInstanceListSupplier提供支持。Eureka实例元数据(Eureka.instance.metadataMap.gone)中的区域值用于设置用于按区域过滤服务实例的spring云负载平衡器区域属性的值。

如果缺少该标志,并且spring.cloud.looadbalancer.eureka.approximateZoneFromHostname标志设置为true,则可以使用服务器主机名中的域名作为区域的代理。

如果没有其他区域数据源,则根据客户端配置(与实例配置相反)进行猜测。我们使用eureka.client.availabilityZones,它是从区域名称到区域列表的映射,并为实例自己的区域拉出第一个区域(即eureka.client区域,为与本地Netflix兼容,默认为“us-east-1”)。


AOT和本机映像支持

Spring Cloud Netflix Eureka客户端集成支持Spring AOT转换和本地图像,但仅在禁用刷新模式的情况下。

如果要在AOT或本机映像模式下运行Eureka客户端,请确保将spring.cloud.refresh.enabled设置为false



配置属性大全


要查看所有Spring Cloud Netflix相关配置财产的列表,请查看附录页面




Eureka 总结+使用


服务注册中心

注册中心记录各服务单元提供的服务,包括地址、端口、版本等


新建一个SpringBoot项目,引入SpringCloud和Eureka Server的依赖

Maven

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.0.2</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>DemoServer</name>
	<description>Eureka Server</description>
	<properties>
		<java.version>17</java.version>
	    <spring.cloud-version>2022.0.1</spring.cloud-version>
	</properties>
	
	<dependenicyManagement>
	    <dependencies>
	        <dependency>
	            <groupId>org.springframework.cloud</groupId>
	            <artifactId>spring-cloud-dependencies</artifactId>
	            <version>${spring.cloud-version}</version>
	            <type>pom</type>
	            <scope>import</scope>
	        </dependency>
	    </dependencies>
	</dependencyManagement>
	
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>

        <!-- eureka server -->
	   	<dependency>
	        <groupId>org.springframework.cloud</groupId>
	        <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
	    </dependency>
        <!-- JDK11支持 -->
	    <dependency>
		    <groupId>org.glassfish.jaxb</groupId>
		    <artifactId>jaxb-runtime</artifactId>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

接下来进行配置

application.properties

server.port=8761
eureka.instance.hostname=localhost
# 是否将自己注册到服务中心
eureka.client.registerWithEureka=false
# 是否检索服务
eureka.client.fetchRegistry=false
# 服务中心地址
eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/

启动类加上 @EnableEurekaServer 注解启用服务中心

@SpringBootApplication
@EnableEurekaServer
public class ServerApplication {

	public static void main(String[] args) {
		SpringApplication.run(ServerApplication.class, args);
	}

}


高可用

考虑到出故障的情况,服务注册中心瘫痪将会造成整个系统瘫痪,因此需要保证服务注册中心的高可用

在 Eureka 中,所有节点既是服务的提供者也是服务的消费者,服务注册中心也不例外。


构建服务注册中心集群

Eureka Server的同步遵循着一个非常简单的原则:只要有一条边将节点连接,就可以进行信息传播与同步


创建两个 Boot,引入 Eureka Server,作为服务注册中心的节点,使其互相连接(加上之前的就三个连接),配置如下

Server1

application.properties

server.port=8761
eureka.instance.hostname=server1
# 是否将自己注册到服务中心,搭建集群则需要,默认true
#eureka.client.registerWithEureka=false
# 是否检索服务,搭建集群则需要,默认true
#eureka.client.fetchRegistry=false
# 首选IP地址
eureka.instance.preferIpAddress=true
# 禁用自我保护模式
eureka.server.enableSelfPreservation=false
# 服务中心地址,多个使用英文逗号(,)分隔,连接其他两个中心
eureka.client.serviceUrl.defaultZone=http://server2:8762/eureka/,http://server3:8763/eureka/

Server2

server.port=8762
eureka.instance.hostname=server2
eureka.instance.preferIpAddress=true
eureka.server.enableSelfPreservation=false
eureka.client.serviceUrl.defaultZone=http://server1:8761/eureka/,http://server3:8763/eureka/

Server3

server.port=8763
eureka.instance.hostname=server3
eureka.instance.preferIpAddress=true
eureka.server.enableSelfPreservation=false
eureka.client.serviceUrl.defaultZone=http://server1:8761/eureka/,http://server2:8762/eureka/


因为hostname为主机名,需要更改 hosts,将上面三个主机名都指向到本机,增加如下

127.0.0.1			server1
127.0.0.1			server2
127.0.0.1			server3

defaultZone指定的地址没有连接上则会一直尝试重连

进入 http://server1:8761 界面如下

Eureka


使用 spring.application.name 指定程序名称,后续服务之间的调用一般使用指定的名称,未指定则默认 UNKNOWN,如上图所示

例如有两个服务名称都为 Test,当其中一个服务挂了后,依然可以正常使用



失效剔除

当服务进行正常关闭操作时,它会触发一个服务下线的REST请求给Eureka Server,告诉服务注册中心:“我要下线 了”。服务中心接受到请求之后,将该服务置为下线状态。

有些时候,我们的服务实例可能非正常下线,为了从服务列表中将这些无法提供服务的实例剔除,Eureka Server在启动的时候会创建一个定时任务,每隔一段时间 (默认为60秒) 将当前清单中超时 (默认为90秒)没有续约的服务剔除,这个操作被称为失效剔除。

可以通过 eureka.server.eviction-interval-timer-in-ms 参数进行修改,单位是毫秒。


自我保护

服务注册到Eureka Server后,会维护一个心跳连接

Eureka Server 运行期间会统计心跳失败的比例在15分钟以之内是否低于85%,在生产环境下,因为网络延迟等原因,心跳失败比例可能超标,此时把服务剔除列表并不妥当

开启自我保护,如果出现低于的情况,Eureka Server 会将当前实例注册信息保护起来,让这些实例不会过期。这样做会使客户端很容易拿到实际已经不存在的服务实例,会出现调用失败的情况。因此客户端要有容错机制,比如请求重试、断路器。


可以通过 eureka.server.enableSelfPreservation 来设置是否开启自我保护,默认开启



服务提供者(provider)

提供接口给其他服务,被其他服务调用的服务,是EurekaClient

创建一个新的项目,和之前一样,增加eureka-client依赖即可

Maven

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.0.2</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>demo</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>17</java.version>
	    <spring.cloud-version>2022.0.1</spring.cloud-version>
	</properties>
	
	<dependencyManagement>
	    <dependencies>
	        <dependency>
	            <groupId>org.springframework.cloud</groupId>
	            <artifactId>spring-cloud-dependencies</artifactId>
	            <version>${spring.cloud-version}</version>
	            <type>pom</type>
	            <scope>import</scope>
	        </dependency>
	    </dependencies>
	</dependencyManagement>
	
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		
	    <dependency>
	        <groupId>org.springframework.cloud</groupId>
	        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
	    </dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>


配置如下

application.properties

# 是否注册到服务中心,默认true
# eureka.client.registerWithEureka=true
# 指定名称,方便后面服务之间的调用使用
spring.application.name=EurekaClientProvider
server.port=8081
# 服务注册中心地址,有多个则加多个,逗号分隔
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/

Application

@SpringBootApplication
// EnableDiscoveryClient 与 EnableEurekaClient 功能一致,前者可用于其他注册中心,后者只适合Eureka注册中心
// 新版推荐使用EnableDiscoveryClient
// 从Spring Cloud Edgware开始,这两个注解可以省略,只要添加client依赖并进行配置即可
// @EnableDiscoveryClient
// @EnableEurekaClient
public class EurekaClientProviderApplication {

	public static void main(String[] args) {
		SpringApplication.run(EurekaClientProviderApplication.class, args);
	}

}

提供一个获取当前服务名称和端口的接口

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 信息获取.
 * @author Shendi
 */
@RestController
@RequestMapping("/info")
public class InfoControl {

	@Value("${spring.application.name}") String appName;
	@Value("${server.port}") String port;
	
	@GetMapping
	public String info() {
		return "name=" + appName + ", port=" + port;
	}
	
}

启动服务后,注册中心输出如下日志

Registered instance EUREKACLIENTPROVIDER/xxx:EurekaClientProvider:8081 with status UP (replication=false)

访问网页,内容如下

服务中心网页


ps:显示的名称为全大写,可以使用 - 来分隔名称提高可读性



服务消费者(consumer)

调用其他服务提供的接口的服务,是EurekaClient


新建一个boot项目,引入的依赖与提供者相同


配置如下

application.properties

# 是否注册到服务中心,默认true
# eureka.client.registerWithEureka=true
# 是否检索服务,默认true
# eureka.client.fetchRegistry=true
spring.application.name=eureka-client-consumer
server.port=8080
# 服务注册中心地址,有多个则加多个,逗号分隔
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/

RestTemplate 是 Spring 提供的用于访问 Rest 服务的客户端,RestTemplate 提供了多种便捷访问远程 Http 服务的方法,能够大大提高客户端的编写效率。

通过配置 RestTemplate 来调用接口

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class BeanConfiguration {

	@Bean
    // 加上LoadBalanced,每次调用的时候会按照策略进行负载均衡(可以直接指定服务名称,没加上则会找不到服务)
    @LoadBalanced
	public RestTemplate getRestTemplate() { return new RestTemplate(); }
	
}

通过 RestTemplate 对象调用其他服务提供的接口

如下

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

/**
 * @author Shendi
 */
@RestController
@RequestMapping("/test")
public class TestControl {

	@Autowired private RestTemplate rt;
	
	/** 服务提供者的名称地址 */
	private String nameUrl = "http://EurekaClientProvider";
	
	@GetMapping
	public String test() {
		// 调用提供者提供的info接口,getForObject是以get请求,还有postForObject...
        // 第二个参数指定获取到的返回值类型,没特别需求使用String就可以了
		String result = rt.getForObject(nameUrl + "/info", String.class);
		return "call ok: " + result;
	}
	
}

访问消费者接口,结果如下

结果




END

本文链接:https://sdpro.top/blog/html/article/1020.html

♥ 赞助 ♥

尽管去做,或许最终的结果不尽人意,但你不付出,他不付出,那怎会进步呢?