Spring Cloud系列(三) 服务注册与发现Spring Cloud Eureka(Finchley.RC2版本)

野性酷女 2022-05-23 05:18 191阅读 0赞

Spring Cloud Netflix

该项目是Spring Cloud的子项目之一,主要内容是对Netflix公司一系列开源产品的包装,它为Spring Boot应用提供了自配置的Netflix OSS整合。通过一些简单的注解,开发者就可以快速的在应用中配置一下常用模块并构建庞大的分布式系统。它主要提供的模块包括:服务发现(Eureka),断路器(Hystrix),智能路由(Zuul),客户端负载均衡(Ribbon)等

Spring Cloud Eureka

Spring Cloud Eureka 是 Spring Cloud Netflix 组件的一部分。它基于Netflix Eureka做了二次封装,来实现微服务架构中的服务治理功能,即服务注册和发现等功能。Eureka Server 作为服务注册功能的服务器,它是服务注册中心。而系统中的其他微服务,作为 Eureka 的客户端连接到 Eureka Server,并维持心跳连接。这样系统的维护人员就可以通过 Eureka Server 来监控系统中各个微服务是否正常运行。Spring Cloud 的一些其他模块(比如Zuul)就可以通过 Eureka Server 来发现系统中的其他微服务,并执行相关的逻辑。

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

为什么要使用服务治理

在一个微服务系统中,可能其中的小型服务几十个以上,服务之间互相依赖调用,如果没有服务治理的话,每一个小型服务都需要维护一个依赖的服务列表,包括ip、端口等,如果其中一个服务修改了, 可能所有依赖它的服务都需要重新修改,这个工程量是很大的,而且这么多服务还要考虑各种命名冲突等问题。如果有了服务治理功能,我们就可以通过它提供的服务注册和服务发现机制来实现对微服务应用实例的自动化管理。

服务注册

在服务治理框架中,通常都会构建一个注册中心,每个服务向注册中心登记自己提供的服务,将主机、端口号、版本号、通信协议等附加信息告知注册中心,注册中心按服务分类组织服务清单。当这些服务启动并向注册中心注册自己的服务后,注册中心就会维护一个服务清单,还以心跳的方式去监测清单中的服务是否可用,若不可用则从服务清单剔除,达到排除故障的效果。

服务发现

由于在服务治理框架下运行,服务间的调用不在通过指定具体的实例地址来实现,而是通过向服务名发起请求调用实现。所以服务调用方并不清楚服务提供方的实际位置。因此,服务调用方会向注册中心咨询服务,获取可以被调用的服务实例清单,以实现对具体实例服务的访问,而且这里默认采用了轮询的方式实现客户端负载均衡。

2018060218504762

上图简要描述了Eureka的基本架构,由服务端和客户端组成,客户端可以拆成两种角色—服务提供者和服务消费者。

  1. Eureka Server(服务端):服务注册中心,提供服务注册和发现
  2. Service Provider(客户端):服务提供者,将自身服务注册到Eureka,为服务消费者提供服务,可以是Spring Boot应用也可以是其他技术平台且遵循Eureka通信机制的应用。
  3. Service Consumer(客户端):服务消费者,从Eureka获取注册服务列表,从而能够消费服务,可以通过Ribbon或者Feign实现消费。

一般情况下,服务客户端既是服务提供者也是服务消费者。

搭建服务注册中心

创建Spring Boot项目起名eureka-server-vFinchley.RC2(这么起名主要是想直观的区分Spring Cloud版本,勿喷(⇀‸↼‶)),选择eureka-server依赖

20180602194929272

20180602194938877

点击finish,创建应用后的pom文件。

  1. <parent>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-parent</artifactId>
  4. <version>2.0.2.RELEASE</version>
  5. <relativePath/> <!-- lookup parent from repository -->
  6. </parent>
  7. <properties>
  8. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  9. <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  10. <java.version>1.8</java.version>
  11. <spring-cloud.version>Finchley.RC2</spring-cloud.version>
  12. </properties>
  13. <dependencies>
  14. <dependency>
  15. <groupId>org.springframework.boot</groupId>
  16. <artifactId>spring-boot-starter-web</artifactId>
  17. </dependency>
  18. <dependency>
  19. <groupId>org.springframework.cloud</groupId>
  20. <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
  21. </dependency>
  22. <dependency>
  23. <groupId>org.springframework.boot</groupId>
  24. <artifactId>spring-boot-starter-test</artifactId>
  25. <scope>test</scope>
  26. </dependency>
  27. </dependencies>
  28. <dependencyManagement>
  29. <dependencies>
  30. <dependency>
  31. <groupId>org.springframework.cloud</groupId>
  32. <artifactId>spring-cloud-dependencies</artifactId>
  33. <version>${spring-cloud.version}</version>
  34. <type>pom</type>
  35. <scope>import</scope>
  36. </dependency>
  37. </dependencies>
  38. </dependencyManagement>
  39. <build>
  40. <plugins>
  41. <plugin>
  42. <groupId>org.springframework.boot</groupId>
  43. <artifactId>spring-boot-maven-plugin</artifactId>
  44. </plugin>
  45. </plugins>
  46. </build>
  47. <repositories>
  48. <repository>
  49. <id>spring-milestones</id>
  50. <name>Spring Milestones</name>
  51. <url>https://repo.spring.io/milestone</url>
  52. <snapshots>
  53. <enabled>false</enabled>
  54. </snapshots>
  55. </repository>
  56. </repositories>

此时直接启动应用会报错,我们需要配置一下服务注册中心。

在启动类中添加注解@EnableEurekaServer启动一个服务注册中心提供给其他应用进行对话。

  1. @EnableEurekaServer
  2. @SpringBootApplication
  3. public class Application {
  4. public static void main(String[] args) {
  5. SpringApplication.run(Application.class, args);
  6. }
  7. }

在默认设置下,该服务注册中心也会将自己作为客户端来尝试注册它自己,所以我们需要禁用它的客户端注册行为,只需要在application.yml中增加如下配置:

  1. server:
  2. port: 1111
  3. eureka:
  4. instance:
  5. hostname: localhost
  6. client:
  7. register-with-eureka: false #不向注册中心注册自己
  8. fetch-registry: false #是否从Eureka Server获取注册信息,默认为true
  9. service-url:
  10. defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ #map类型 设置与Eureka Server交互的地址,查询服务和注册服务都要依赖它

启动应用访问http://localhost:1111/,页面显示

20180602205501611

此时是没有可用服务的,接下来我们创建服务的客户端———服务提供者。

创建Spring Boot项目命名eureka-client-vFinchley.Rc2,并添加依赖eureka-discovery

20180602210106387

在启动类中加上@EnableDiscoveryClient注解,该注解能激活Eureka中的DiscoveryClient实现。

  1. @EnableDiscoveryClient
  2. @SpringBootApplication
  3. public class Application {
  4. public static void main(String[] args) {
  5. SpringApplication.run(Application.class, args);
  6. }
  7. }

在添加一个REST API提供服务。

  1. @RestController
  2. public class HelloController {
  3. @GetMapping("hello")
  4. public String index(){
  5. return "hello";
  6. }
  7. }

修改配置文件,指定服务注册中心位置。

  1. spring:
  2. application:
  3. name: hello-service #为服务命名
  4. server:
  5. port: 2222
  6. eureka:
  7. client:
  8. service-url:
  9. defaultZone: http://localhost:1111/eureka/ #指定服务注册中心位置

启动应用,再次刷新http://localhost:1111/发现多了HELLO-SERVICE服务

20180602211211437

高可用注册中心

在微服务架构的分布式环境中,我们要考虑发生故障的情况。上面的例子只是一个单节点的注册中心,如果它发生故障宕机了,就会导致整个微服务系统挂掉,所以我们需要构建一个高可用的注册中心。Eureka Server在设计上就考虑到了高可用问题,在Eureka的服务治理设计中,每一个服务提供者也是服务消费者,服务注册中心也不例外。在上面的例子我们就设置了两个参数

  1. eureka.client.register-with-eureka: 表示是否将自己注册到Eureka Server,默认为true。
  2. eureka.client.fetch-registry :表示是否从Eureka Server获取注册信息,默认为true。

Eureka Server的高可用实际就是将自己作为服务像其他服务注册中心注册自己,这样就可以实现一组互相注册的服务注册中心,以实现服务清单同步,达到集群高可用的效果。接下来我们实现一个三节点的服务注册中心。为了和单节点区分开,我重新创建一个应用。步骤和创建单节点的服务端一样,起名eureka-server-cluster-vFinchley.Rc2。然后删掉application.properties文件(个人比较喜欢使用yml文件),创建application-peer1.yml,application-peer2.yml,application-peer3.yml。

application-peer1.yml

  1. spring:
  2. application:
  3. name: eureka-server
  4. server:
  5. port: 1111
  6. eureka:
  7. instance:
  8. hostname: peer1
  9. client:
  10. service-url:
  11. defaultZone: http://peer2:1112/eureka/,http://peer3:1113/eureka/ #map类型 多个逗号隔开 指向peer2和peer3

application-peer2.yml

  1. spring:
  2. application:
  3. name: eureka-server
  4. server:
  5. port: 1112
  6. eureka:
  7. instance:
  8. hostname: peer2
  9. client:
  10. service-url:
  11. defaultZone: http://peer1:1111/eureka/,http://peer3:1113/eureka/ #map类型 多个逗号隔开 指向peer1和peer3

application-peer3.yml

  1. spring:
  2. application:
  3. name: eureka-server
  4. server:
  5. port: 1113
  6. eureka:
  7. instance:
  8. hostname: peer3
  9. client:
  10. service-url:
  11. defaultZone: http://peer1:1111/eureka/,http://peer2:1112/eureka/ #map类型 多个逗号隔开 指向peer1和peer2

20180605171213978

Linux系统在/etc/hosts文件末尾添加对peer1、peer2、peer3的转换,windows系统位置C:\Windows\System32\drivers\etc

  1. 127.0.0.1 peer1
  2. 127.0.0.1 peer2
  3. 127.0.0.1 peer3

将应用打成jar包,然后分别以peer1、peer2、peer3运行jar

  1. java -jar eureka-server-cluster-vFinchley.Rc2-vFinchley.RC2.jar --spring.profiles.active=peer1
  2. java -jar eureka-server-cluster-vFinchley.Rc2-vFinchley.RC2.jar --spring.profiles.active=peer2
  3. java -jar eureka-server-cluster-vFinchley.Rc2-vFinchley.RC2.jar --spring.profiles.active=peer3

分别打开http://localhost:1111/,http://localhost:1112/,http://localhost:1113/发现都有了另外的两个EUREKA-SERVER服务

20180605172056594

20180605172132802

20180605172209642

我们再创建一个用来注册到服务注册中心集群的客户端。创建一个应用,起名eureka-client-cluster-vFinchley.Rc2,步骤和上面创建单节点的客户端一样。

修改application.yml,指定服务注册地址为多个

  1. #应用名称
  2. spring:
  3. application:
  4. name: hello-service #为服务命名
  5. server:
  6. port: 2222
  7. eureka:
  8. client:
  9. service-url:
  10. defaultZone: http://peer1:1111/eureka/,http://peer2:1112/eureka/,http://peer3:1113/eureka/ #指定服务注册中心位置,多个逗号隔开

然后把单节点的HelloController复制过来,启动应用,访问http://localhost:2222/hello

20180605173403585

而且打开http://localhost:1111/,http://localhost:1112/,http://localhost:1113/也会发现分别多了HELLO-SERVICE服务。

现在我们关闭一个服务注册中心,发现http://localhost:2222/hello仍然可以访问。访问注册中心页面,发现挂掉的服务也进入到unavailable-replicas行了。

20180614161712146

服务消费者

服务消费者主要完成两个目标,发现服务以及消费服务。其中服务的发现功能由Eureka的客户端来完成,服务的消费功能由Ribbon来完成。Ribbon是基于HTTP和TCP的客户端负载均衡器。Ribbon可以在通过客户端中配置的ribbonServerList服务端列表去轮询访问以达到均衡负载的作用。当Ribbon与Eureka联合使用时,ribbonServerList会被DiscoveryEnabledNIWSServerList重写,扩展成从Eureka注册中心中获取服务端列表。同时它也会用NIWSDiscoveryPing来取代IPing,它将职责委托给Eureka来确定服务端是否已经启动。

创建一个Spring Boot项目,命名ribbon-consumer-vFinchley.Rc2,勾选如图三个依赖

70

pom文件如下

  1. <parent>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-parent</artifactId>
  4. <version>2.0.2.RELEASE</version>
  5. <relativePath/> <!-- lookup parent from repository -->
  6. </parent>
  7. <properties>
  8. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  9. <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  10. <java.version>1.8</java.version>
  11. <spring-cloud.version>Finchley.RC2</spring-cloud.version>
  12. </properties>
  13. <dependencies>
  14. <dependency>
  15. <groupId>org.springframework.boot</groupId>
  16. <artifactId>spring-boot-starter-web</artifactId>
  17. </dependency>
  18. <dependency>
  19. <groupId>org.springframework.cloud</groupId>
  20. <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  21. </dependency>
  22. <dependency>
  23. <groupId>org.springframework.cloud</groupId>
  24. <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
  25. </dependency>
  26. <dependency>
  27. <groupId>org.springframework.boot</groupId>
  28. <artifactId>spring-boot-starter-test</artifactId>
  29. <scope>test</scope>
  30. </dependency>
  31. </dependencies>
  32. <dependencyManagement>
  33. <dependencies>
  34. <dependency>
  35. <groupId>org.springframework.cloud</groupId>
  36. <artifactId>spring-cloud-dependencies</artifactId>
  37. <version>${spring-cloud.version}</version>
  38. <type>pom</type>
  39. <scope>import</scope>
  40. </dependency>
  41. </dependencies>
  42. </dependencyManagement>
  43. <build>
  44. <plugins>
  45. <plugin>
  46. <groupId>org.springframework.boot</groupId>
  47. <artifactId>spring-boot-maven-plugin</artifactId>
  48. </plugin>
  49. </plugins>
  50. </build>
  51. <repositories>
  52. <repository>
  53. <id>spring-milestones</id>
  54. <name>Spring Milestones</name>
  55. <url>https://repo.spring.io/milestone</url>
  56. <snapshots>
  57. <enabled>false</enabled>
  58. </snapshots>
  59. </repository>
  60. </repositories>

在应用主类中,通过@EnableDiscoveryClient注解来添加发现服务能力。创建RestTemplate实例,并通过@LoadBalanced注解开启均衡负载能力。

  1. @SpringBootApplication
  2. @EnableDiscoveryClient
  3. public class Application {
  4. @Bean
  5. @LoadBalanced
  6. RestTemplate restTemplate() {
  7. return new RestTemplate();
  8. }
  9. public static void main(String[] args) {
  10. SpringApplication.run(Application.class, args);
  11. }
  12. }

修改配置文件,注意defaultZone必须与上面的提供服务的HELLO-SERVICE服务的defaultZone一样,否则是找不到HELLO-SERVICE服务的。

  1. #应用名称
  2. spring:
  3. application:
  4. name: ribbon-consumer #为服务命名
  5. server:
  6. port: 3333
  7. eureka:
  8. client:
  9. service-url:
  10. defaultZone: http://localhost:1111/eureka/ #指定服务注册中心位置

创建ConsumerController来消费HELLO-SERVICE提供的服务

  1. @RestController
  2. public class ConsumerController {
  3. @Autowired
  4. private RestTemplate restTemplate;
  5. @GetMapping("consumer")
  6. public String consumer(){
  7. return restTemplate.getForEntity("http://HELLO-SERVICE/hello", String.class).getBody();
  8. }
  9. }

注意这里访问的地址并不是一个具体的地址,而是服务名,在服务治理框架中,这是一个十分重要的特性。

然后分别启动单节点服务端eureka-server-vFinchley.RC2、单节点服务提供者eureka-client-vFinchley.RC2、服务消费者ribbon-consumer-vFinchley.Rc2。

启动方式:服务端和服务消费者可以直接用eclipse启动,服务提供者需要打成jar包分别指定端口启动多个实例,因为一会测客户端负载均衡还要用到。

  1. java -jar eureka-client-vFinchley.Rc2-vFinchley.Rc2.jar --server.port=8080
  2. java -jar eureka-client-vFinchley.Rc2-vFinchley.Rc2.jar --server.port=8081

服务列表出现了HELLO-SERVICE和RIBBON-CONSUMER,并且HELLO-SERVICE服务有两个实例

70 1

调用服务消费者的接口http://localhost:3333/consumer

70 2

我们已经成功调用了HELLO-SERVICE的服务并返回结果,如果你多次调用会发现Ribbon是采用轮询的方式调用两个服务提供者。我们通过Ribbon在客户端已经实现了对服务调用的均衡负载。关于Ribbon的使用我在后面会详细介绍。

总结

现在,服务注册中心、服务提供者、服务消费者都介绍到了,总结一下它们各自的重要通信行为。

服务提供者

  • 服务注册:服务提供者在启动后会通过REST请求的方式将自己注册到服务注册中心,并附带自身的一些元数据。服务注册中心接收到这些元数据后会把它们存储到一个双层结构Map中,第一层的key值是服务名,第二层是具体服务的实例名。在服务注册时注意eureka.client,register-with-eureka=true 如果是false不会注册。
  • 服务同步:如果具有多个服务注册中心,其中一个服务中心接受到服务提供者的注册信息后会转发给其他的注册中心,所以服务列表是同步的,可以通过任意一个服务注册中心获取服务列表。
  • 服务续约:服务注册后,服务提供者会维护一个心跳来告诉服务注册中心自己还在正常运行,以避免服务注册中心将自己在服务列表剔除。

两个重要的属性

  1. eureka.instance.lease-expiration-duration-in-seconds=90 定义服务失效的时间,默认90s
  2. eureka.instance. lease-renewal-interval-in-seconds=30 定义服务续约任务的调用时间,默认30s

服务消费者

  1. 获取服务:启动服务消费者时,会自动发送REST请求去服务注册中心获取注册的服务清单,为了性能考虑,Eureka Server会返回一份可读的服务清单给服务消费者,并且每30s更新一次缓存清单。必须确保eureka.client.fetch-registry=true没有被修改成false,否则无法在服务注册中心获取服务清单,若想修改缓存清单的更新时间可以通过eureka.client.registry-fetch-interval-seconds=30,该参数默认30s.
  2. 服务调用:服务消费者获取服务清单后,通过服务名可以获得具体提供服务的实例名和该实例的元数据信息,根据这些元数据信息,服务消费者可以自己决定调用哪个服务实例,Ribbon默认是采用轮询的方式进行客户端负载均衡。对于访问实例的选择,简单介绍一下,Eureka中有Region和Zone的概念,一个Region可以包含多个Zone,每个客户端都会被注册到一个Zone中,所以每个客户端都对应一个Region和一个Zone,再进行服务调用的时候会优先选择同处于一个Zone中的服务提供者,若访问不到才会访问其他Zone。通过Zone属性的由来,配合实际部署的物理结构,我们可以有效的设计出对区域性故障的容错集群。
  3. 服务下线:当客户端下线或重启时会给Eureka Server发送REST请求告诉服务端它下线了,服务端接到请求会将该服务改为下线状态(DOWN),并传播出去。

服务注册中心

失效剔除:有些时候,客户端没有正常下线,而是因为内存溢出、网络故障等原因使得客户端不能正常工作,而服务注册中心并不会收到服务下线的通知。为了可以剔除这些已经失效的服务,服务注册中心在启动后会创建一个定时任务,默认每隔一段时间(默认60s)将当前清单中超时(默认90s)没有续约的服务剔除。

自我保护机制:当我们在本地调试Eureka的时候经常会遇到下面的提示,表示触发了自我保护机制。

EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY’RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.

具体细节参考:点击打开链接。

发表评论

表情:
评论列表 (有 0 条评论,191人围观)

还没有评论,来说两句吧...

相关阅读