当前位置: 代码迷 >> 综合 >> Spring Cloud Eureka(服务治理)(2)
  详细解决方案

Spring Cloud Eureka(服务治理)(2)

热度:42   发布时间:2023-11-20 01:37:11.0

1.服务发现与消费

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

首先启动服务注册中心eureka-server,然后通过命令行的方式启动两个eureka-service(为了实验Ribbon的客户端负载均衡功能,通过java -jar 命令行的方式来启动两个不同端口的eureka-service):

java -jar eureka-service-0.0.1-SNAPSHOT.jar --server.port=8082

java -jar eureka-service-0.0.1-SNAPSHOT.jar --server.port=8083

创建一个Boot的基础工程来实现服务消费者,取名为ribbon-consumer,并在pom.xml中引入如下依赖内容:

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.example</groupId><artifactId>eureka-ribbon</artifactId><version>0.0.1-SNAPSHOT</version><packaging>jar</packaging><name>eureka-ribbon</name><description>Demo project for Spring Boot</description><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.0.4.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.SR1</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-ribbon</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</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></project>

然后给应用主类上添加@EnableDiscoveryClient注解,让该应用注册为Eureka客户端应用,使其获得服务发现的能力。同时在该主类中创建RestTemplate的SpringBean实例,并通过@LoadBalanced注解开启客户端负载均衡。

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

创建ConsumerController类并实现ribbon-consumer接口。该接口通过上面创建的RestTemplate来实现对euerka-service服务提供的/hello接口进行调用。

@RestController
public class ConsumerController {@AutowiredRestTemplate restTemplate;@RequestMapping("/ribbon-consumer")public  String helloConsumer(){return restTemplate.getForEntity("http://hello-service/hello",String.class).getBody();}
}

然后在application.properties中配置Eureka服务注册中心的位置,需要跟eureka-service中的application.properties中的一样否则无法发现该服务,同时配置端口防止端口冲突。

spring.application.name=ribbon-consumer
server.port=8099eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/

而我的eureka-service的配置如下:

启动ribbon-consumer应用后,可以在Eureka信息面板看到:

除了hello-service还多了ribbon-consumer服务。

然后访问http://localhost:8099/ribbon-consumer发起GET请求

页面成功返回"Hello"。此时可以在控制台看到如下信息:

2018-09-03 22:09:18.352  INFO 13272 --- [nio-8099-exec-1] c.n.l.DynamicServerListLoadBalancer      : DynamicServerListLoadBalancer for client hello-service initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=hello-service,current list of Servers=[PC201709201534:8083, PC201709201534:8082],Load balancer stats=Zone stats: {defaultzone=[Zone:defaultzone;	Instance count:2;	Active connections count: 0;	Circuit breaker tripped count: 0;	Active connections per server: 0.0;]
},Server stats: [[Server:PC201709201534:8082;	Zone:defaultZone;	Total Requests:0;	Successive connection failure:0;	Total blackout seconds:0;	Last connection made:Thu Jan 01 08:00:00 CST 1970;	First connection made: Thu Jan 01 08:00:00 CST 1970;	Active Connections:0;	total failure count in last (1000) msecs:0;	average resp time:0.0;	90 percentile resp time:0.0;	95 percentile resp time:0.0;	min resp time:0.0;	max resp time:0.0;	stddev resp time:0.0]
, [Server:PC201709201534:8083;	Zone:defaultZone;	Total Requests:0;	Successive connection failure:0;	Total blackout seconds:0;	Last connection made:Thu Jan 01 08:00:00 CST 1970;	First connection made: Thu Jan 01 08:00:00 CST 1970;	Active Connections:0;	total failure count in last (1000) msecs:0;	average resp time:0.0;	90 percentile resp time:0.0;	95 percentile resp time:0.0;	min resp time:0.0;	max resp time:0.0;	stddev resp time:0.0]
]}ServerList:org.springframework.cloud.netflix.ribbon.eureka.DomainExtractingServerList@760980fd

Ribbon输出了当前客户端维护的hello-service的服务列表情况。中专包含了各个实例的位置,Ribbon就是按照此信息进行轮询访问,以实现对各个实例的请求总数量、第一次连接信息、上一次连接信息、总的请求失败数量等。

2.基础架构

上面我简单的演示了Eureka服务治理系统的三个核心角色:服务注册中心,服务提供者,服务消费者。

服务注册中心:Eureka提供的服务端,提供服务注册与服务发现的功能,也就是eureka-server。

服务提供者:提供服务的应用,可以是Spring Boot应用也可以是其他技术平台且遵循Eureka通信机机制的应用。他将自己提供的服务注册到Eureka,以供其他应用发现,也就是我实现的hello-service应用。

服务消费者:消费者应用从服务注册中心获取服务列表,从而使消费者可以知道去何处调用其所需要的服务,除了已经使用过的Ribbon还有Feign的消费方式。

很多时候客户端即是服务提供者也是服务消费者。

3.服务治理机制

1.服务提供者:

服务注册
“服务提供者在启动的时候会通过发送REST请求的方式将自己注册到EurekaServer上, 同时带上了自身服务的 一 些元数据信息。Eureka Server接收到这个REST请求之后,将元数据信息存储在 一 个双层结构Map中, 其中第 一 层的key是服务名, 第二层的key是具体服务的实例名。(之前在实现Ribbon负载均衡的例子中, Eureka信息面板中 一 个服务有多个实例的清况, 这些内容就是以这样的双层Map形式 存储的。)

在服务注册时需要确认:eureka.client.register-with-eureka=true参数是否正确,该值默认为true,若设置为false将不会启动注册操作。

服务同步
如架构图中所示, 这里的两个服务提供者分别注册到了两个不同的服务注册中心上,也就是说, 它们的信息分别被两个服务注册中心所维护。 此时, 由于服务注册中心之间因互相注册为服务, 当服务提供者发送注册请求到 一 个服务注册中心时, 它会将该请求转发给集群中相连的其他注册中心,从而实现注册中心之间的服务同步 。 通过服务同步,两个服务提供者的服务信息就可以通过这两台服务注册中心中的任意 一 台获取到。

服务续约

在注册完服务后,服务提供者会维护一个心跳用来持续高速Eureka Server,它还存活着,以防止Eureka Server的剔除任务将该服务实例从服务列表中排出,我们称此操作为服务续约。

关于服务续约有两个重要的属性:

eureka.instance.leasse-renewal-interval-in-seconds=30

eureka.instance.leasse-expiration-duration-in-seconds=90

eureka.instance.leasse-renewal-interval-in-seconds参数用于定义服务续约任务的调用间隔时间,默认为30秒。

eureka.instance.leasse-expiration-duration-in-seconds参数用于定义服务失效时间,默认为90秒。

2.服务消费者:

获取服务
在这里,在服务注册中心已经注册了一个服务,并且该服务有两个实例,当我们启动服务消费者的时候,它会发送一个REST请求给服务注册中心,来获取上面注册的服务清单,为了性能考虑,Eureka Server会维护一份只读的服务清单来返回给客户端,同时该缓存清单会隔30秒更新一次。

获取服务是服务消费者的基础,所以必须确保eureka.client.fetch-registry=true参数没有被修改成false,该值默认为ture。若希望修改缓存清单的更新时间,可以通过eureka.client.registry-fetch-interval-seconds=30参数进行修改,该参数默认值为30秒。

服务调用
服务消费者在获取服务清单后,通过服务名可以获得具体提供服务的实例名和该实例的元数据,因为有这些服务实力的详细信息,所以客户端可以根据自己的需要决定具体调用哪个实例,在ribbon中会默认采用轮询的方式进行调用,从而实现客户端的负载均衡。

对于访问实例的选择,Eureka中Region和Zone的概念,一个Region中可以包含多个Zone,每个服务客户端需要被注册到一个Zone中,所以每个客户端对应一个Region和一个Zone,在服务调用的时候,先访问同处一个Zone中的服务提供方,若访问不到,就访问其他的Zone。

服务下线
在系统运行过程中必然会面临关闭和重启服务的某个实例的情况,在服务关闭期间,我们自然不会希望客户端继续调用关闭了的实例,所以在客户端程序中,当服务实例进行正常的关闭操作时,,会触发一个服务下线的REST请求给Eureka Server,告诉服务注册中心:我要下线了,服务端在接收到请求后。将该服务状态置为下线(DOWN),并把该下线事件传播出去

3.服务注册中心:

失效剔除
有些时候, 我们的服务实例并不 一 定会正常下线, 可能由于内存溢出、 网络故障等原因使得服务不能正常工作, 而服务注册中心并未收到“ 服务下线 ”的请求。 为了从服务列表中将这些无法提供服务的实例剔除, EurekaServer在启动的时候会创建 一 个定时任务,默认每隔 一 段时间(默认为60秒) 将当前清单中超时(默认为90秒)没有续约的服务剔除出去。

自我保护
当我们在本地调试基于Eureka的程序时, 基本上都会碰到这样 一 个问题, 在服务注册
中心的信息面板中出现类似下面的红色警告信息:

实际上, 该警告就是触发了EurekaServer的自我保护机制。 之前我们介绍过, 服务注册到EurekaServer之后,会维护 一 个心跳连接,告诉EurekaServer自己还活着。EurekaServer在运行期间,会统计心跳失败的比例在15分钟之内是否低于85%, 如果出现低于的情况 (在单机调试的时候很容易满足, 实际在生产环境上通常是由于网络不稳定导致), Eureka Server会将当前的实例注册信息保护起来, 让这些实例不会过期, 尽可能保护这些注册信息。 但是, 在这段保护期间内实例若出现问题, 那么客户端很容易拿到实际已经不存在的服务实例, 会出现调用失败的清况, 所以客户端必须要有容错机制, 比如可以使用请求重试、 断路器等机制。

由于本地调试很容易触发注册中心的保护机制,这会使得注册中心维护的服务实例不准确。所以在本地开发时,可以使用eureka.server.enable-self-preservation=false参数来关闭保护机制,以确保注册中心可以将不可用的实例正确剔除。

  相关解决方案