Spring Cloud系列(四)服务注册与发现Spring Cloud Eureka

了解 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、端口等,如果其中一个服务修改了, 可能所有依赖它的服务都需要重新修改,这个工程量是很大的,而且这么多服务还要考虑各种命名冲突等问题。如果有了服务治理功能,我们就可以通过它提供的服务注册和服务发现机制来实现对微服务应用实例的自动化管理。

服务注册

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

服务发现

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

用一张图来认识一下


上图简要描述了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依赖



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

        <parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.2.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
		<spring-cloud.version>Finchley.RC2</spring-cloud.version>
	</properties>

	<dependencies>
		<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-server</artifactId>
		</dependency>

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

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

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

	<repositories>
		<repository>
			<id>spring-milestones</id>
			<name>Spring Milestones</name>
			<url>https://repo.spring.io/milestone</url>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</repository>
	</repositories>

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

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

@EnableEurekaServer
@SpringBootApplication
public class Application {

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

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

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

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


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

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


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

@EnableDiscoveryClient
@SpringBootApplication
public class Application {

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

在添加一个RESTFul API提供服务

@RestController
public class HelloController {
	
	@GetMapping("hello")
	public String index(){
		return "hello";
	}
}

修改配置文件

#应用名称
spring:
  application:
    name: hello-service #为服务命名
server:
  port: 2222
eureka:
  client:
    service-url: 
      defaultZone: http://localhost:1111/eureka/ #指定服务注册中心位置

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


这就已经成功实现了一个服务注册中心。

高可用注册中心

        在微服务架构的分布式环境中,我们要考虑发生故障的情况。上面的例子只是一个单节点的注册中心,如果它发生故障宕机了,就会导致整个微服务系统挂掉,所以我们需要构建一个高可用的注册中心。

        Eureka Server在设计上就考虑到了高可用问题,在Eureka的服务治理设计中,每一个服务提供者也是服务消费者,服务注册中心也不例外。在上面的例子我们就设置了两个参数

①eureka.client.register-with-eureka: 表示是否将自己注册到Eureka Server,默认为true。

②eureka.client.fetch-registry :表示是否从Eureka Server获取注册信息,默认为true。

        Eureka Server的高可用实际就是将自己作为服务像其他服务注册中心注册自己,这样就可以实现一组互相注册的服务注册中心,以实现服务清单同步,达到集群高可用的效果。接下来我们实现一个三节点的服务注册中心。为了和单节点区分开,我重新创建一个应用。步骤和创建单节点的服务端一样,起名eureka-server-cluster-vFinchley.Rc2

然后删掉application.properties文件,创建application-peer1.yml,application-peer2.yml,application-peer3.yml

application-peer1.yml

spring:
  application:
    name: eureka-server
server:
  port: 1111
eureka:
  instance:
    hostname: peer1
  client:
    service-url:
      defaultZone: http://peer2:1112/eureka/,http://peer3:1113/eureka/  #map类型 多个逗号隔开 指向peer2和peer3

application-peer2.yml

spring:
  application:
    name: eureka-server
server:
  port: 1112
eureka:
  instance:
    hostname: peer2
  client:
    service-url:
      defaultZone: http://peer1:1111/eureka/,http://peer3:1113/eureka/  #map类型 多个逗号隔开 指向peer1和peer3

application-peer3.yml

spring:
  application:
    name: eureka-server
server:
  port: 1113
eureka:
  instance:
    hostname: peer3
  client:
    service-url:
      defaultZone: http://peer1:1111/eureka/,http://peer2:1112/eureka/  #map类型 多个逗号隔开 指向peer1和peer2

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

127.0.0.1 peer1

127.0.0.1 peer2

127.0.0.1 peer3

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

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

然后分别打开http://localhost:1111/http://localhost:1112/http://localhost:1113/发现都有了另外的两个服务




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

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

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

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

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

现在我们关闭一个服务注册中心,发现http://localhost:2222/hello仍然可以访问。

访问注册中心页面,发现挂掉的服务也进入到unavailable-replicas行了


这就成功实现了一个高可用的服务注册中心。

服务消费者

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

搭建服务消费者应用

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


pom文件如下

        <parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.2.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
		<spring-cloud.version>Finchley.RC2</spring-cloud.version>
	</properties>

	<dependencies>
		<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>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
		</dependency>

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

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

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

	<repositories>
		<repository>
			<id>spring-milestones</id>
			<name>Spring Milestones</name>
			<url>https://repo.spring.io/milestone</url>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</repository>
	</repositories>

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

@SpringBootApplication
@EnableDiscoveryClient
public class Application {

	@Bean
	@LoadBalanced
	RestTemplate restTemplate() {
		return new RestTemplate();
	}
	
	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}
}

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

#应用名称
spring:
  application:
    name: ribbon-consumer #为服务命名
server:
  port: 3333
eureka:
  client:
    service-url: 
      defaultZone: http://localhost:1111/eureka/ #指定服务注册中心位置

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

@RestController
public class ConsumerController {

	@Autowired
	private RestTemplate restTemplate;
	
	@GetMapping("consumer")
	public String consumer(){
		return restTemplate.getForEntity("http://HELLO-SERVICE/hello", String.class).getBody();
	}
}

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

然后分别启动单节点服务端、单节点客户端、服务消费应用。

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

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

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


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


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

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

        服务提供者在启动后会通过REST请求的方式将自己注册到服务注册中心,并附带自身的一些元数据。服务注册中心接收到这些元数据后会把它们存储到一个双层结构Map中,第一层的key值是服务名,第二层是具体服务的实例名。在服务注册时注意eureka.client,register-with-eureka=true 如果是false不会注册。

服务同步

        如果具有多个服务注册中心,其中一个服务中心接受到服务提供者的注册信息后会转发给其他的注册中心,所以服务列表是同步的,可以通过任意一个服务注册中心获取服务列表。

服务续约

        服务注册后,服务提供者会维护一个心跳来告诉服务注册中心自己还在正常运行,以避免服务注册中心将自己在服务列表剔除。

eureka.instance.lease-expiration-duration-in-seconds=90 定义服务失效的时间,默认90s

eureka.instance. lease-renewal-interval-in-seconds=30 定义服务续约任务的调用时间,默认30s

服务消费者
获取服务

        启动服务消费者时,会自动发送REST请求去服务注册中心获取注册的服务清单,为了性能考虑,Eureka Server会返回一份可读的服务清单给服务消费者,并且每30s更新一次缓存清单。必须确保eureka.client.fetch-registry=true没有被修改成false,否则无法在服务注册中心获取服务清单,若想修改缓存清单的更新时间可以通过eureka.client.registry-fetch-interval-seconds=30,该参数默认30s.

服务调用

        服务消费者获取服务清单后,通过服务名可以获得具体提供服务的实例名和该实例的元数据信息,根据这些元数据信息,服务消费者可以自己决定调用哪个服务实例,Ribbon默认是采用轮询的方式进行客户端负载均衡。对于访问实例的选择,简单介绍一下,Eureka中有Region和Zone的概念,一个Region可以包含多个Zone,每个客户端都会被注册到一个Zone中,所以每个客户端都对应一个Region和一个Zone,再进行服务调用的时候会优先选择同处于一个Zone中的服务提供者,若访问不到才会访问其他Zone。通过Zone属性的由来,配合实际部署的物理结构,我们可以有效的设计出对区域性故障的容错集群。

服务下线

        当客户端下线或重启时会给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.

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

配置详解

        在Eureka的服务治理体系中,主要分为服务端和客户端两个不同的角色,服务端为服务注册中心,客户端为提供各个接口的微服务应用。在实际应用中,我们所做的配置内容都是对客户端进行的操作,而Eureka服务端更像一个现成的产品,大多数情况下我们不需要修改它的配置。如果你想了解的话,可以去这个类中查看org.springframework.cloud.netflix.eureka.server.EurekaServerConfigBean,服务端的属性都是以eureka.server为前缀。

Eureka客户端的主要配置

1.服务注册相关的配置信息,包括服务注册中心的地址,服务获取的间隔时间、可用区域等。

2.服务实例相关的配置信息,包括服务实例的名称、IP地址、端口号、健康检查路径等。

服务注册类配置

        关于服务注册类的配置信息可用通过org.springframework.cloud.netflix.eureka.EurekaClientConfigBean查看,这些配置信息前缀都是eureka.client。

指定注册中心

        通过eureka.client.serviceUrl参数实现,该参数的定义如下,它的配置值存储在HashMap类型中,并且有一组默认值,默认的key是defaultZone,默认的value为http://localhost:8761/eureka。我们通过这样设置就可以重新设置注册中心:eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/。如果是高可用注册中心则多个之间逗号隔开。

其他配置

这些配置都以eureka.client开头


服务实例类配置

        关于服务实例类的配置信息可以在org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean类查看,这些配置信息的前缀都是eureka.instance。

实例名配置

        它是区分同一服务不同实例的唯一标识。在Netflix Eureka的原生实现中,默认是以主机名作为默认值,这样的设置使得在同一主机上无法启动多个相同的服务实例。所以在Spring Cloud Eureka中针对同一主机启动多个实例的情况,对实例名的默认命名做了更合理的扩展,采用如下规则


        我们可以通过eureka.instance.instanceId来配置实例名。当我们在本地做负载均衡调试时,采用的是指定多个端口来启动同一服务的多个实例,但这样你会发现实例名其实还是一样的只是端口不一样,我们可以这样设置指定不同的实例名

eureka.instance.instanceId=${spring.application.name}:${random.int}

这样就可以通过不指定端口就启动同一服务的多个实例。

端点配置

    在org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean类中,有一些URL的配置信息,如homePageUrl、statusPageUrl、healthCheckUrl。其中状态页和健康检查的URL默认是使用actuator提供的info和health端点。我们必须保证客户端的health端点是一个可以被注册中心正确访问到的地址,否则注册中心不会根据应用的健康检查来改变应用状态(仅当开启了healthcheck功能时,以该端点信息作为健康检查标准而不是心跳)。如果info端点不能正常访问,则在Eureka面板单击服务时不能访问到服务实例提供的信息接口。一般情况下我们不需要配置这些信息,如果你修改了info端点和health端点的路径,那么就需要修改这个配置了,只需要和actuator的做同样修改即可,比如加上相同的前缀、改为同样的新路径等。

其他配置


    除了前三个可能会修改外,其余是使用默认配置就可以。

猜你喜欢

转载自blog.csdn.net/WYA1993/article/details/80550275