使用Eureka进行微服务系统的服务治理

时间:2022-09-29 14:59:29

Spring Cloud算是分布式系统的一系列工具框架集合包。基于提供的这些集合包,可以快速的构建分布式系统。

Netflix是Spring Cloud中的重要组件。其中涵盖了一些开箱即用的分布式服务治理能力,诸如服务管理注册(Eureka)熔断器(Hystrix)智能路由(Zuul)客户端负载均衡(Ribbon)等等。

本章主要记录下Netflix中Eureka服务注册管理相关的概念。

使用Eureka进行微服务系统的服务治理

Eureka服务管理

Eureka由两个组件组成:Eureka服务器Eureka客户端。Eureka服务器用作服务注册服务器。Eureka客户端是一个java客户端,用来简化与服务器的交互、作为轮询负载均衡器,并提供服务的故障切换支持。Netflix在其生产环境中使用的是另外的客户端,它提供基于流量、资源利用率以及出错状态的加权负载均衡。

服务管理的一个大概模型如下所示:

使用Eureka进行微服务系统的服务治理

从结构图上可以看出有一下我们所构建的工程中有三种角色:

  1. Eureka Server: 服务注册中心,负责服务列表的注册维护查询等功能;

  2. Service Provider: 服务提供方,同时也是一个Eureka Client,负责将所提供的服务向Eureka Server进行注册、续约和注销等操作。注册时所提供的主要数据包括服务名机器ip端口号域名等,从而能够使服务消费方能够找到;

  3. Service Consumer: 服务消费方,同时也是一个Eureka Client,同样也会向Eureka Server注册本身所提供的服务。

  1. Service Provider(服务提供方)Service Consumer(服务消费方)并不是一个严格的概念,往往服务消费方也是一个服务提供方,同时服务提供方也可能会调用其它服务方所提供的服务。但是在微服务构建时最好还是遵守业务层级之间的划分,尽量避免服务之间的循环依赖

  2. Eureka的视角上看,除了注册中心属于server节点,service providerservice consumer均为client节点。

使用Eureka进行微服务系统的服务治理

Eureka服务端(注册中心)

单节点

pom.xml文件中添加对应依赖信息:

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka-server</artifactId>
            <version>1.4.6.RELEASE</version>
        </dependency>
    </dependencies>

配置application配置文件信息:

server:
  port: 8761

eureka:
  instance:
    hostname: localhost
  client:
    healthcheck:
      enabled: true
    registerWithEureka: false # 作为服务器,禁止将其自身注册到server上去
    fetchRegistry: false      # 是否需要从Eureka Server上拉取注册信息到本地
    serviceUrl:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ # 设置与Eureka Server交互的地址,查询服务和注册服务都需要依赖这个地址。默认是http://localhost:8761/eureka ;多个地址可使用 , 分隔。

上述几个关键配置项的含义说明如下:

  • eureka.client.register-with-eureka:表示是否将自己注册到Eureka Server,默认为true。
  • eureka.client.fetch-registry:表示是否从Eureka Server获取注册信息,默认为true。
  • eureka.client.serviceUrl.defaultZone:设置与Eureka Server交互的地址,查询服务和注册服务都需要依赖这个地址。默认是http://localhost:8761/eureka,多个地址可使用,分隔。

按照spring boot的要求指定application类:

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

使用Eureka进行微服务系统的服务治理

在浏览器中,输入http://localhost:8761/可以查看到eureka的管理界面,可以看到里面的服务列表等信息(示例中没有注册任何服务,因此看到列表是空的),如下:

使用Eureka进行微服务系统的服务治理

使用Eureka进行微服务系统的服务治理

保障高可用性实现

注册中心这么关键的服务,如果是单点话,遇到故障就是毁灭性的。

在一个分布式系统中,服务注册中心是最重要的基础部分,理应随时处于可以提供服务的状态。为了维持其可用性,使用集群是很好的解决方案。

在上面单个节点示例的基础上,修改application.yml,配置2个server节点的属性信息,并且两个eureka server节点相互注册

---
spring:
  application:
    name: spring-cloud-eureka1 # 注册到注册中心的服务名称
  profiles: peer1
server:
  port: 8761
eureka:
  instance:
    hostname: peer1
  client:
    healthcheck:
      enabled: true
      registerWithEureka: true # 集群高可用场景,作为服务器,将其自身注册到server上去
    fetchRegistry: true        # 是否需要从Eureka Server上拉取注册信息到本地
    serviceUrl:
      defaultZone: http://${eureka.instance.hostname}:8762/eureka/

---
spring:
  application:
    name: spring-cloud-eureka2  # 注册到注册中心的服务名称
  profiles: peer2
server:
  port: 8762
eureka:
  instance:
    hostname: peer2
  client:
    healthcheck:
      enabled: true
      registerWithEureka: true # 集群高可用场景,作为服务器,将其自身注册到server上去
    fetchRegistry: true        # 是否需要从Eureka Server上拉取注册信息到本地
    serviceUrl:
      defaultZone: http://${eureka.instance.hostname}:8761/eureka/

上面的配置中,配置了2个profile属性段,名称分别为peer1和peer2,且两个节点的eureka.client.serviceUrl.defaultZone属性值相互配置为对方的地址。

特别注意

对于集群高可用方案中,必须要配置registerWithEurekafetchRegistry都为true,多个Server节点之间才会同步各自的注册数据信息

使用Eureka进行微服务系统的服务治理

如何本地启动两个SpringBoot进程
编译jar然后cmd启动

通过mvn clean package命令编译并生成jar包,然后通过如下命令,分别启动2个server进程:

java -jar com.vzn.localtest.springcloud.eureka-server-1.0-SNAPSHOT.jar --spring.profiles.active=peer1
java -jar com.vzn.localtest.springcloud.eureka-server-1.0-SNAPSHOT.jar --spring.profiles.active=peer2

上述命令中,通过spring.profiles.active属性,指定进程运行的时候,加载哪个application属性(前面已经写明,通过application.yml方式将多个server的配置写在了一个文件里,启动进程的时候需要指明分别使用那个配置块)

通过Idea直接启动

在Idea工具中,右上角应用名称选择的地方,下拉,选择edit configurations并点击:

使用Eureka进行微服务系统的服务治理

在打开的窗口中,左上角点击+新增一个启动项设置,指定Main Class对应的启动类,并在Program Arguments一栏中填入--spring.profiles.active=peer1,同样的方式可以继续新增启动项,Program Arguments一栏中填入--spring.profiles.active=peer2,如下所示:

使用Eureka进行微服务系统的服务治理

配置完成,启动的时候,选择对应的启动项,直接点击右侧的绿色启动按钮即可,如下:

使用Eureka进行微服务系统的服务治理

查看效果

此处示例中配置的是hostname的方式,调测的时候,在host文件中,加上:

127.0.0.1 peer1
127.0.0.1 peer2

在浏览器中输入http://127.0.0.1:8761/http://127.0.0.1:8762/,可以发现peer1和peer2两个进程已经相互注册了:

使用Eureka进行微服务系统的服务治理

使用Eureka进行微服务系统的服务治理

通过上述简单的示例可以看出,Eureka通过互相注册的方式来实现高可用的部署,只需要将Eureke Server配置其他可用的serviceUrl就能实现高可用部署。

在生产中可能需要三台或者大于三台的注册中心来保证服务的稳定性,配置的原理其实都一样,将注册中心分别指向其它的注册中心。Eureka会在有关联的各个节点之间进行数据同步,保证集群高可用。

Eureka客户端(服务提供方服务消费方)

实现方式

与Eureka Server实现原理相似,只是使用的是@EnableEurekaClient注解,如下示例代码的第2行所示。

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

在一个常规的SpringBoot应用的Application启动类上添加@EnableEurekaClient标识,并在application.properties(或者其它形式的比如xml\yml等格式)配置文件中添加上Eureka相关的配置,即可实现将自身注册到Eureka Server注册中心去。

将应用注册到注册中心的时候,可以在application配置文件中配置对应的service name值,单个service name值可以被多个进程同时注册,表示此服务具有多个集群节点

如下所示:

---
spring:
  application:
    name: eureka-client-provier1
  profiles: peer1
server:
  port: 8763
eureka:
  instance:
    hostname: peer1
  client:
    healthcheck:
      enabled: true
    fetchRegistry: false
    serviceUrl:
      defaultZone: http://${eureka.instance.hostname}:8762/eureka/

---
spring:
  application:
    name: eureka-client-provier2
  profiles: peer2
server:
  port: 8764
eureka:
  instance:
    hostname: peer2
  client:
    healthcheck:
      enabled: true
    fetchRegistry: false
    serviceUrl:
      defaultZone: http://${eureka.instance.hostname}:8761/eureka/

---
spring:
  application:
    name: eureka-client-consumer3
  profiles: peer3
server:
  port: 8765
eureka:
  instance:
    hostname: peer2
  client:
    healthcheck:
      enabled: true
    fetchRegistry: false
    serviceUrl:
      defaultZone: http://${eureka.instance.hostname}:8761/eureka/

启动Client进程,然后到Server对应的管理界面查看,可以看到已经注册到管理中心了,注册名称对应配置文件中的spring.application.name值:

使用Eureka进行微服务系统的服务治理

使用Eureka进行微服务系统的服务治理

实现负载均衡调用服务

结合Ribbon负载均衡器,通过service name的方式访问对应的service provider应用,可以实现客户端的请求在service name对应的多个注册的进程之间负载均衡

实现负载均衡的方式较为简单,在RestTemplate的注入初始化的地方,添加上@LoadBalanced注解即可实现:

@Configuration
public class EurekaConfiguration {

    @Bean
    @LoadBalanced
    RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

注意在访问service的时候,需要使用service name的方式去访问,才能达到负载均衡的效果。

Eureka的自我保护与心跳机

测试demo中,发现client进程停止后,server注册列表中依旧存在此client的信息,了解了下,是和Eureka的自我保护以及心跳等有关系。

Eureka自我保护机制

有的时候,Eureka Server的页面中,会出现如下图所示的红色字体描述:

使用Eureka进行微服务系统的服务治理

这是因为Eureka开启了自我保护机制。

按照通常的认知,如果Eureka Server在一定时间内没有接收到某个微服务实例的心跳时,EurekaServer应该会注销该实例(默认90s)。但是,假设微服务进程正常运行,只是网络故障等原因,导致服务与注册中心之间的网络不通,心跳没有正常送达(其实这个时候服务是正常可访问的),如果注册中心将其移除掉,会导致consumer无法再发现此服务,可能会导致业务上面的问题。

鉴于上述场景可能的问题,Eureka Server提供了自我保护机制,会在此种场景下禁止将注册的service移除,来防止了此类问题的出现。当网络恢复的时候,会自动退出自我保护机制。

当然,也可以在配置文件中关闭Server的自我保护机制,配置项如下:


# 关闭Eureka服务自我保护
eureka.server.enable-self-preservation=false

# 服务端每隔n秒刷新依次服务列表,将无效服务剔除
eureka.server.eviction-interval-timer-in-ms=5000

关闭后,界面上会有对应的提示说明:

使用Eureka进行微服务系统的服务治理

再次尝试client先启动注册到Server之后,再停止Client进程,等待一段时间之后刷新Server界面,会发现Server的注册列表中,自动移除了已经停止进程的client的注册信息:

使用Eureka进行微服务系统的服务治理

客户端心跳

Eureka使用客户端心跳维持更新注册状态信息。默认情况下eureka.instance.leaseRenewalIntervalInSeconds值为30,如果有需要的话,可以修改此值来加快服务状态变更的速度(最好保持30s别动)。

添加client端的配置信息如下:

leaseRenewalIntervalInSeconds: 10    # 客户端往服务器端发送心跳的间隔时间,默认是30s,修改小一些可以加快被发现速度
leaseExpirationDurationInSeconds: 60 # 客户端多久不发消息给注册中心,注册中心直接将客户端的注册信息移除。默认90s

当一个服务的注册状态发生变更的时候,需要3个心跳周期,才能保证客户端、服务器端、本地三者的元数据全部更新一致。

二者关联

<font color="red">当修改了client对应的心跳默认配置的时候,会打破server的自我保护机制</font>,即当心跳超过自定义的时间之后,服务器会直接移除此client的注册信息。

使用Eureka进行微服务系统的服务治理

Eureka Server与Client交互的全流程

  1. EurekaServer 提供服务发现的能力,各个微服务启动时,会向EurekaServer注册自己的信息(例如:ip、端口、微服务名称等),EurekaServer会存储这些信息;
  2. EurekaClient是一个Java客户端,用于简化与EurekaServer的交互;
  3. 微服务启东后,会定期性(默认30s)的向EurekaServer发送心跳以续约自己的“租期”;
  4. 如果EurekaServer在一定时间内未接收某个微服务实例的心跳,EurekaServer将会注销该实例(默认90s);
  5. 默认情况下,EurekaServer同时也是EurekaClient。多个EurekaServer实例,互相之间通过复制的方式,来实现服务注册表中数据的同步;
  6. EurekaClient也会缓存服务注册表中的信息。

过程问题记录

启动报错:

12:23:15.304 [main] ERROR org.springframework.boot.SpringApplication - Application run failed
java.lang.NoSuchMethodError: org.springframework.boot.builder.SpringApplicationBuilder.<init>([Ljava/lang/Object;)V
	at org.springframework.cloud.bootstrap.BootstrapApplicationListener.bootstrapServiceContext(BootstrapApplicationListener.java:161)
	at org.springframework.cloud.bootstrap.BootstrapApplicationListener.onApplicationEvent(BootstrapApplicationListener.java:102)
	at org.springframework.cloud.bootstrap.BootstrapApplicationListener.onApplicationEvent(BootstrapApplicationListener.java:68)
	at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:172)
	at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:165)
	at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:139)
	at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:127)
	at org.springframework.boot.context.event.EventPublishingRunListener.environmentPrepared(EventPublishingRunListener.java:75)

原因:

由于Spring Boot和Spring Cloud之间的版本不匹配导致的,修改pom.xml中两个的版本相匹配,即可。

使用Eureka进行微服务系统的服务治理

我是悟道,聊技术、又不仅仅聊技术~

如果觉得有用,请点个关注,也可以关注下我的公众号【架构悟道】,获取更及时的更新。

期待与你一起探讨,一起成长为更好的自己。

使用Eureka进行微服务系统的服务治理