Spring Cloud微服务实战-服务治理(Spring Cloud Eureka)
Posted KeSuns
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring Cloud微服务实战-服务治理(Spring Cloud Eureka)相关的知识,希望对你有一定的参考价值。
Spring Cloud微服务实战-服务治理(Spring Cloud Eureka)
1. Spring Cloud Eureka简介
Spring Cloud Eureka主要用来完成微服务中的服务治理。是基于Netflix Eureka做的二次封装,Spring Cloud通过为Eureka增加了Spring Boot风格的自动化配置,我们只需要通过引入依赖和注解配置就能让Spring Boot构建的微服务应用轻松地与Eureka服务治理体系进行整合。
2. 服务治理背景
在微服务开发工程中,整个系统微服务应用非常多,并且随着业务的发展,微服务的数量在不断增加。而微服务之间的调用非常平凡,假如一个微服务(A)的地址发生变化,调用A接口的其他应用都要进行修改,如果只是通过手工维护的方式,那么很容易出现错误并消耗大量的人力。为了解决微服务架构中的服务实例维护,我们可以通过Spring Cloud Eureka进行服务治理。服务治理包括服务注册和服务发现。
- 服务注册中心
构建一个注册中心,每个服务单元想注册中心等级自己提供的服务,将主机与端口号、版本号、通信协议等一些附加信息告知注册中心,注册中心按服务名分类组织服务清单。
- 服务提供者
在服务注册中心注册,给其他应用提供服务(比如提供接口给其他应用调用)
- 服务发现消费者
服务间的调用不再通过指定具体的实例地址来实现,而是通过向服务名发起请求调用的实现。服务调用方在调用服务提供方接口的时候,并不知道具体的服务实例位置。因此,调用方需要向服务注册中心咨询服务,并获取所有服务的实例清单,以实现对具体服务实例的访问。
3. 简单服务注册中心代码实例
-
eureka-server:服务注册中心,各个服务都注册在此。只需要一个注解@EnableEurekaServer,这个注解需要在springboot工程的启动application类上加
-
eureka-service:服务提供者,注册在eureka-server,注册的名称叫做EUREKA-SERVICE,其他服务调用该服务时就不需要手动配置IP和端口了,可以直接使用EUREKA-SERVICE加端口进行访问。通过注解@EnableEurekaClient 表明自己是一个eurekaclient,但是仅仅@EnableEurekaClient是不够的,还需要在application.yml配置文件中注明自己的服务注册中心的地址
-
eureka-client:服务发现和消费,调用eureka-service的接口。这里是通过spring cloud ribbon负载均衡客户端来调用eureka-service,关于ribbon后面文章会介绍。
application.properties
1 spring.application.name=eureka-server 2 server.port=8761 3 4 eureka.instance.hostname=localhost 5 6 # 代表不向注册中心注册自己 7 eureka.client.register-with-eureka=false 8 9 # 不去检索服务 10 eureka.client.fetch-registry=false 11 eureka.client.service-url.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka
EurekaServerApplication.java
1 package com.suns.eurekaserver; 2 3 4 import org.springframework.boot.SpringApplication; 5 import org.springframework.boot.autoconfigure.SpringBootApplication; 6 import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; 7 8 @EnableEurekaServer 9 @SpringBootApplication 10 public class EurekaServerApplication { 11 12 public static void main(String[] args) { 13 SpringApplication.run(EurekaServerApplication.class, args); 14 } 15 }
application.properties
spring.application.name=eureka-service server.port=8762 eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
EurekaServiceApplication.java
1 package com.suns.eurekaservice; 2 3 import org.springframework.boot.SpringApplication; 4 import org.springframework.boot.autoconfigure.SpringBootApplication; 5 import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 6 7 @EnableDiscoveryClient 8 @SpringBootApplication 9 public class EurekaServiceApplication { 10 11 public static void main(String[] args) { 12 SpringApplication.run(EurekaServiceApplication.class, args); 13 } 14 }
HelloController.java
1 package com.suns.eurekaservice.api; 2 3 import org.apache.log4j.Logger; 4 import org.springframework.beans.factory.annotation.Autowired; 5 import org.springframework.cloud.client.ServiceInstance; 6 import org.springframework.cloud.client.discovery.DiscoveryClient; 7 import org.springframework.web.bind.annotation.RequestMapping; 8 import org.springframework.web.bind.annotation.RequestMethod; 9 import org.springframework.web.bind.annotation.RestController; 10 11 12 @RestController 13 public class HelloController { 14 15 private final Logger logger = Logger.getLogger(getClass()); 16 17 @Autowired 18 private DiscoveryClient client; 19 20 @RequestMapping(value = "/hello", method = RequestMethod.GET) 21 public String index() { 22 ServiceInstance instance = client.getLocalServiceInstance(); 23 logger.info("/hello, host: " + instance.getHost() + ", service_id: " + instance.getServiceId()); 24 return "Hello World"; 25 } 26 }
启动eureka-server和eureka-service后,打开服务注册中心管理页面:发现服务提供者已成功注册到注册中心。
application.properties
1 spring.application.name=eureka-client 2 server.port=8763 3 4 eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
EurekaClientApplication.java
1 package com.suns.eurekaclient; 2 3 import org.springframework.boot.SpringApplication; 4 import org.springframework.boot.autoconfigure.SpringBootApplication; 5 import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 6 import org.springframework.cloud.client.loadbalancer.LoadBalanced; 7 import org.springframework.context.annotation.Bean; 8 import org.springframework.web.client.RestTemplate; 9 10 @EnableDiscoveryClient 11 @SpringBootApplication 12 public class EurekaClientApplication { 13 14 @Bean 15 @LoadBalanced 16 RestTemplate restTemplate() { 17 return new RestTemplate(); 18 } 19 20 public static void main(String[] args) { 21 SpringApplication.run(EurekaClientApplication.class, args); 22 } 23 }
ConsumerController.java
1 package com.suns.eurekaclient.api; 2 3 import org.springframework.beans.factory.annotation.Autowired; 4 import org.springframework.web.bind.annotation.RequestMapping; 5 import org.springframework.web.bind.annotation.RequestMethod; 6 import org.springframework.web.bind.annotation.RestController; 7 import org.springframework.web.client.RestTemplate; 8 9 10 @RestController 11 public class ConsumerController { 12 13 @Autowired 14 RestTemplate restTemplate; 15 16 @RequestMapping(value = "/ribbon-consumer", method = RequestMethod.GET) 17 public String helloConsumer() { 18 return restTemplate.getForEntity("http://EUREKA-SERVICE/hello", String.class).getBody(); 19 } 20 }
通过访问http://localhost:8763/ribbon-consumer,发现页面成功显示Hello World
查看eureka-service日志,成功打印出如下日志:
2020-02-10 07:33:01.234 INFO 4036 --- [nio-8762-exec-9] c.s.eurekaservice.api.HelloController : /hello, host: DCNA7245.APAC.Local, service_id: eureka-service
4. 高可用注册中心代码实例
Eureka Server的高可用实际上就是将自己作为服务向其他服务注册中心注册自己,这样就形成了一组互相注册的服务注册中心,以实现服务清单的互相同步,达到高可用的效果。
-
eureka-high-avai-server:服务注册中心。通过spring boot profile启动2个不同实例的eureka-server。
-
eureka-high-avai-service:服务提供者。通过spring boot profile启动2个eureka-service
-
eureka-high-avai-client:服务消费者。
需要修改C:\\Windows\\System32\\drivers\\etc\\hosts,在文件末尾加上一下信息
127.0.0.1 server1
127.0.0.1 server2
在配置高可用注册中心时,要配置一下2个参数,这2个参数是想服务注册中心注册自己以实现达到高可用。:
eureka.client.register-with-eureka=true(默认是true)
eureka.client.fetch-registry=true(默认是true)
application.yml
--- spring: profiles: server1 # 指定profile=server1 application: name: eureka-high-avai-server server: port: 8761 eureka: instance: hostname: server1 # 指定当profile=server1时,主机名 client: serviceUrl: defaultZone: http://server2:8762/eureka/ # 将自己注册到server2这个Eureka上面去 server: enableSelfPreservation: false # 关掉Eureka的自我保护机制 --- spring: profiles: server2 application: name: eureka-high-avai-server server: port: 8762 eureka: instance: hostname: server2 client: serviceUrl: defaultZone: http://server1:8761/eureka/ server: enable-self-preservation: false
EurekaHighAvaiServerApplication.java
1 package com.suns.eurekahighavaiserver; 2 3 import org.springframework.boot.SpringApplication; 4 import org.springframework.boot.autoconfigure.SpringBootApplication; 5 import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; 6 7 @EnableEurekaServer 8 @SpringBootApplication 9 public class EurekaHighAvaiServerApplication { 10 11 public static void main(String[] args) { 12 SpringApplication.run(EurekaHighAvaiServerApplication.class, args); 13 } 14 }
通过:
java -jar eureka-high-avai-server-0.0.1-SNAPSHOT.jar –spring.profiles.active=server1
java -jar eureka-high-avai-server-0.0.1-SNAPSHOT.jar –spring.profiles.active=server2
来启动2个eureka-high-avai-server
application.yml
我这里配置service1和service2同时向server1和server2注册,如果只配置service1向server1注册,service2向server2注册。在2个service启动后,注册中心会自动同步从而实现注册中心之间服务同步。这是由于服务注册中心之间因互相注册为服务,当服务提供者发送注册请求到一个服务注册中心时,它会将该请求转发给集群中项链的其他注册中心。
1 --- 2 spring: 3 profiles: service1 4 application: 5 name: eureka-high-avai-service 6 server: 7 port: 8763 8 eureka: 9 client: 10 serviceUrl: 11 defaultZone: http://server1:8761/eureka/,http://server2:8762/eureka/ 12 13 --- 14 spring: 15 profiles: service2 16 application: 17 name: eureka-high-avai-service 18 server: 19 port: 8764 20 eureka: 21 client: 22 serviceUrl: 23 defaultZone: http://server1:8761/eureka/,http://server2:8762/eureka/
EurekaHighAvaiServiceApplication.java
1 package com.suns.eurekahighavaiservice; 2 3 import org.springframework.boot.SpringApplication; 4 import org.springframework.boot.autoconfigure.SpringBootApplication; 5 import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 6 7 @EnableDiscoveryClient 8 @SpringBootApplication 9 public class EurekaHighAvaiServiceApplication { 10 11 public static void main(String[] args) { 12 SpringApplication.run(EurekaHighAvaiServiceApplication.class, args); 13 } 14 }
HelloController.java
1 package com.suns.eurekahighavaiservice.api; 2 3 import org.apache.log4j.Logger; 4 import org.springframework.beans.factory.annotation.Autowired; 5 import org.springframework.beans.factory.annotation.Value; 6 import org.springframework.cloud.client.ServiceInstance; 7 import org.springframework.cloud.client.discovery.DiscoveryClient; 8 import org.springframework.web.bind.annotation.RequestMapping; 9 import org.springframework.web.bind.annotation.RequestMethod; 10 import org.springframework.web.bind.annotation.RestController; 11 12 13 @RestController 14 public class HelloController { 15 16 private final Logger logger = Logger.getLogger(getClass()); 17 18 @Autowired 19 private DiscoveryClient client; 20 21 @Value("${server.port}") 22 String port; 23 24 @RequestMapping(value = "/hello", method = RequestMethod.GET) 25 public String index() { 26 ServiceInstance instance = client.getLocalServiceInstance(); 27 logger.info("/hello, host: " + instance.getHost() + ", service_id: " + instance.getServiceId() + ", port:" + port); 28 return "/hello, host: " + instance.getHost() + ", service_id: " + instance.getServiceId() + ", port:" + port; 29 } 30 }
通过:
java -jar eureka-high-avai-service-0.0.1-SNAPSHOT.jar –spring.profiles.active=service1
java -jar eureka-high-avai-service-0.0.1-SNAPSHOT.jar –spring.profiles.active=service2
1 spring.application.name=eureka-client 2 server.port=8765 3 4 eureka.client.service-url.defaultZone=http://server1:8761/eureka/,http://server2:8762/eureka/
EurekaHighAvaiClientApplication.java
1 package com.suns.eurekahighavaiclient; 2 3 import org.springframework.boot.SpringApplication; 4 import org.springframework.boot.autoconfigure.SpringBootApplication; 5 import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 6 import org.springframework.cloud.client.loadbalancer.LoadBalanced; 7 import org.springframework.context.annotation.Bean; 8 import org.springframework.web.client.RestTemplate; 9 10 @EnableDiscoveryClient 11 @SpringBootApplication 12 public class EurekaHighAvaiClientApplication { 13 14 @Bean 15 @LoadBalanced 16 RestTemplate restTemplate() { 17 return new RestTemplate(); 18 } 19 20 public static void main(String[] args) { 21 SpringApplication.run(EurekaHighAvaiClientApplication.class, args); 22 } 23 }
ConsumerController.java
1 package com.suns.eurekahighavaiclient.api; 2 3 import org.springframework.beans.factory.annotation.Autowired; 4 import org.springframework.web.bind.annotation.RequestMapping; 5 import org.springframework.web.bind.annotation.RequestMethod; 6 import org.springframework.web.bind.annotation.RestController; 7 import org.springframework.web.client.RestTemplate; 8 9 @RestController 10 public class ConsumerController { 11 12 @Autowired 13 RestTemplate restTemplate; 14 15 @RequestMapping(value = "/ribbon-consumer", method = RequestMethod.GET) 16 public String helloConsumer() { 17 return restTemplate.getForEntity("http://EUREKA-HIGH-AVAI-SERVICE/hello", String.class).getBody(); 18 } 19 }
启动完成后,访问eureka-high-avai-client服务:http://localhost:8765/ribbon-consumer。多次调用以后发现页面返回的服务端口不一致:
/hello, host: DESKTOP-1BVDM6L, service_id: eureka-high-avai-service, port:8763 /hello, host: DESKTOP-1BVDM6L, service_id: eureka-high-avai-service, port:8764
5. Eureka详解
- 服务注册:“服务提供者”在启动的时候会通过发送REST请求的方式将自己注册到Eureka Server上,同时带上了自身服务的一些元数据信息。Eureka Server接收到这个REST请求之后,将元数据信息存储在一个双层结构Map中,其中第一层的key是服务名,第二层的key是具体服务的实例名。Eureka server会维护一份只读的服务清单来返回给服务消费者,同时改缓存清单会每隔30秒更新一次。
- 服务同步:当服务提供者发送注册请求到一个服务注册中心时,该注册中心会将请求转发给集群中相连的其他注册中心,从而实现注册中心之间的服务同步。
- 服务续约:在注册完服务之后,服务提供者会维护一个心跳来告诉Eureka Server:“我还活着”,防止Eureka Server的“剔除任务”将该服务实例从服务列表中排除出去,我们称该操作为服务预约(Renew)。可以通过一下参数进行相关配置。
1 eureka: 2 instance: 3 leaseRenewalIntervalInSeconds: 1 #心跳间隔时间1s 4 leaseExpirationDurationInSeconds: 2 #连续2s未响应时将服务注销
- 获取服务:服务消费者会发送一个REST请求给服务注册中心,来获取注册中心的服务清单。
- 服务调用:服务消费者获取到服务清单后,通过服务名获得具体提供服务的实例名和该实例的元数据信息。再根据自己的需要决定具体调用那个实例。对于访问实例的选择,Eureka中有Region和Zone的概念,一个Region包含多个Zone,每个服务客户端需要注册到一个Zone中,所以客户端对应一个Region和一个Zone。在进行服务调用的时候,优先访问同处一个Zone中的服务提供方,若访问不到,就访问其他的Zone。
- 服务下线:当服务实例进行正常的关闭操作时,它会触发一个服务下线的REST请求给Eureka Server,告诉服务注册中心:“我要下线了”。服务端接收到请求以后,将该服务状态设置为下线(DOWN),并把改下线事件传播出去。
- 失效剔除:对于非正常关闭的服务,Eureka Server会启动一个定时任务,每60S(默认)将清单没有续约的服务(90秒没有收到服务提供者的心跳)移除。
- 自我保护:Eureka Server每30S接收其他注册服务的心跳,同时Eureka Server会统计心跳失败的比例(15分钟之内是否低于85%),如果低于85%,Eureka Server会将当前的实例注册信息保护起来,让这些实例不会过期。可以通过一下配置确保注册中心可以将不可用的实例正确移除。低于85%时,访问Eureka Server会出现: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.报错
1 eureka: 2 server: 3 enable-self-preservation: false
6. 配置详解
服务注册类配置
通过org.springframework.cloud.netflix.eureka.EurekaClientConfigBean类来查看详细配置,这些配置信息都是eureka.client为前缀。
参数名 | 说明 | 默认值 |
enabled | 启用Eureka客户端 | true |
registryFetchIntervalSeconds | 从Eureka服务端获取注册信息的时间间隔 | 30S |
instanceInfoReplicationIntervalSeconds | 更新实例信息的变化到Eureka服务端的时间间隔 | 30S |
initialInstanceInfoReplicationIntervalSeconds | 初始化实例信息到Eureka服务端的时间间隔 | 40S |
eurekaServiceUrlPollIntervalSeconds | 轮询Eureka服务端地址更改的时间间隔。当与Spring Cloud Config配合,动态刷新Eureka的serviceURL地址需要关注 | 300S |
eurekaServerReadTimeoutSeconds | 读取Eureka Server信息的超时时间 | 8S |
eurekaServerConnectTimeoutSeconds | 连接Eureka Server信息的超时时间 | 5S |
eurekaServerTotalConnections | 从Eureka客户端到所有Eureka服务端的连接总数 | 200S |
eurekaServerTotalConnectionsPerHost | 从Eureka客户端到所有Eureka服务端主机的连接总数 | 50S |
eurekaConnectionIdleTimeoutSeconds | Eureka服务端连接的空闲关闭时间 | 30S |
heartbeatExecutorThreadPoolSize | 心跳连接池的初始化线程数 | 2S |
heartbeatExecutorExponentialBackOffBound | 心跳超时重试延迟时间的最大乘数值 | 10S |
cacheRefreshExecutorThreadPoolSize | 缓存刷新线程池的初始化线程数 | 2S |
cacheRefreshExecutorExponentialBackOffBound | 缓存刷新重试延迟时间的最大乘数值 | 10S |
useDnsForFetchingServiceUrls | 使用DNS来获取Eureka服务端的serviceUrl | false |
registerWithEureka | 是否要将自身的实例信息注册到Eureka服务端 | true |
preferSameZoneEureka | 是否偏好使用处于相同Zone的Eureka服务端 | true |
filterOnlyUpInstances | 获取实例时是否过滤,仅保留UP状态的实例 | true |
fetchRegistry | 是否从Eureka服务端获取注册信息 | true |
服务实例类配置
服务实例类的配置信息,可以通过org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean源码来获取详细信息,这些配置以eureka.instance为前缀。EurekaInstanceConfigBean中大部分都是对服务实例元数据的配置,元数据是用来描述自身服务信息的对象,其中包含了一些标准话的元数据,比如服务名称、实例名称、实例IP、实例端口等用于服务治理的重要信息;以及一些用于负载均衡策略或是其他特殊用途的自定义元数据信息。
服务实例类的配置信息是通过EurekaInstanceConfigBean来加载,但真正注册时候会包装成com.netflix.appinfo.InstanceInfo。
-
实例名配置
实例名,即InstanceInfo中的instanceId参数,它是区分同一服务中不同实例的唯一标识。Spring Cloud Eureka默认配置实例名的规则如下:
${spring.cloud.client.hostname}:${spring.application.name}:${spring.application.instance_id:${server.port}
只要满足instance_id或者port不一致就能启动同一个服务的多个实例
-
断点配置
在InstanceInfo中homePageUrl、statusPageUrl、healthCheckUrl,他们分别代表了应用主页的URL、状态页的URL、健康检查的URL。其中状态页和健康检查的URL在Spring Cloud Eureka中默认使用了spring-boot-actuator模块提供的/info端点和/health端点。为了确保服务正常,我们必须确保Eureka客户端的/health断点在发送元数据的时候,是一个能够被注册中心访问到的地址,否则注册中心不会根据应用的健康检查来更改状态。而/info端点如果不正确,会导致在Eureka面板中单机服务实例时,无法访问到服务实例提供的信息接口。
如果设置了context-path,所有spring-boot-actuator模块的监控端点都会增加一个前缀:
1 management.context-path=/hello 2 3 eureka.instance.statusPageUrlPath=${management.context-path}/info 4 eureka.instance.healthCheckUrlPath=${management.context-path}/health
-
健康检测
默认情况下,Eureka中各个服务实例的健康检测并不是通过spring-boot-actuator模块的/health端点来实现,而是依靠客户端心跳的方式来保持服务实例的存货。这样无法真正监控服务提供者是否能正常提供服务(比如服务提供者数据库连接不上),但是在注册中心它还是UP状态。为了避免这种情况,我们可以把Eureka客户端的健康检测交给spring-boot-actuator模块的/health端点,以实现更加全面的健康维护。
1)在pom.xml中引入spring-boot-actuator模块。
2)在application.properties中增加参数配置eureka.client.healthcheck.enabled=默认是true
3)如果客户端的/health端点路径做了特殊配置,参考前面的端点配置
-
其他配置
参数名 | 说明 | 默认值 |
preferIpAddress | 是否优先使用IP作为主机名的标识 | false |
leaseRenewalIntervalInSeconds | Eureka客户端向服务端发送心跳的时间间隔 | 30S |
leaseExpirationDurationInSeconds | Eureka服务端收到最后一次心跳之后等待的时间上限。超过之后服务端将该服务实例从服务清单中剔除,从而禁止服务调用请求被发送到该实例 | 90S |
nonSecurePort | 非安全的通信端口号 | 80 |
securePort | 安全的通信端口号 | 443 |
nonSecurePortEnabled | 是否启用非安全的通信端口号 | true |
securePortEnabled | 是否启用非安全的通信端口号 | |
appname | 服务名,默认取spring.application.name配置值,如果没有则为unknown | |
hostname | 主机名,不配置的时候将根据操作系统的主机名来获取 |
|
7. 鸣谢
感谢《Spring Cloud微服务实战》作者:瞿永超
8. 说明
这篇文章主要是《Spring Cloud微服务实战》的读书笔记。
以上是关于Spring Cloud微服务实战-服务治理(Spring Cloud Eureka)的主要内容,如果未能解决你的问题,请参考以下文章