客户端健康检测与常用配置
1、回顾
- 最小型Eureka集群
- Eureka客户端(服务者/调用者)分别向Eureka服务器进行注册
- 通过浏览器访问调用者就可以间接访问到服务者
- Eureka客户端默认情况下每30秒会向Eureka服务器发送一个心跳检测请求,Eureka服务器则会知道该Eureka客户端还存活,继续保持该客户端的服务列表
- 下面则介绍Eureka的常用配置
2、客户端常用配置
2.1、心跳配置
2.1.1、lease-renewal-interval-in-seconds
server:
port: 8080
spring:
application:
name: first-service-provider
eureka:
instance:
lease-renewal-interval-in-seconds: 5
hostname: localhost
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
logging:
level:
com.netflix: DEBUG
- 将默认每30秒一次的心跳请求修改为5S每次
- 为看到效果,我们在控制台输出日志
2.1.2、心跳测试
- 首先险需要启动服务器,即是先启动MyApplicationServer(atm_eureka_server)
- 启动MyApplicationProvider(atm_eureka_provider),查看控制台输入的日志
2.1.3、lease-expiration-duration-in-seconds
- Eureka服务器在默认期限内(90S),接收不到Eureka客户端的心跳请求,说明该客户端可能出现故障
- 对于Eureka服务器而言,应该及时将该Eureka客户端清除
- 默认时间90s:服务器在90S内如果没有接收到客户端的心跳请求,则将该客户端从服务列表清除
- Eureka服务器内有一个定时器,默认每60S才开启,开启后才开始清除无效的客户端实例,换句话说,如果客户端在一定期限内没有发送心跳请求,Eureka服务器并不会立刻清除客户端实例,而是当定时器启动时才进行清除
2.1.4、心跳测试
server:
port: 8761
eureka:
client:
registerWithEureka: false
fetchRegistry: false
server:
enable-self-preservation: false
eviction-interval-timer-in-ms: 5000
server:
port: 8080
spring:
application:
name: first-service-provider
eureka:
instance:
lease-renewal-interval-in-seconds: 5
lease-expiration-duration-in-seconds: 10
hostname: localhost
client:
serviceUrl:
defaultZone: http:
logging:
level:
com.netflix: DEBUG
- 开启Eureka服务器
- 开启Eureka客户端(服务提供者)
- 开启Eureka客户端成功后,成功向服务器注册,之后关闭Eureka客户端
- 规定时间内,客户端实例被清除
- 我们再看看关闭Eureka客户端之后,Eureka服务器的情况
2.2、服务列表抓取配置
- 客户端也会到服务器端抓取服务列表
- 编写一个服务调用者去服务器抓取服务列表
- 客户端每隔一定的时间就会向服务器抓取最新的服务列表,抓取成功后则保存在客户端本地
- 默认30S抓取一次
package com.atm.cloud
import java.util.List
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.cloud.client.ServiceInstance
import org.springframework.cloud.client.loadbalancer.LoadBalanced
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.MediaType
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestMethod
import org.springframework.web.bind.annotation.ResponseBody
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.client.RestTemplate
import org.springframework.cloud.client.discovery.DiscoveryClient
@RestController
@Configuration
public class InvokerController {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate()
}
@RequestMapping(value = "/router", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public String router() {
RestTemplate restTemplate = getRestTemplate()
// 根据应用名称调用服务
String json = restTemplate.getForObject(
"http://first-service-provider/person/1", String.class)
return json
}
@Autowired
public DiscoveryClient discoveryClient
//获取服务列表,并将服务列表的数量显示出来
//默认是30s抓取一次,可以根据需求进行修改
@GetMapping("/list")
@ResponseBody
public String serviceCount() {
List<String> serviceNames = discoveryClient.getServices()
for (String serviceId : serviceNames) {
List<ServiceInstance> serviceInstance = discoveryClient
.getInstances(serviceId)
System.out.println(serviceId + ": " + serviceInstance.size())
}
return ""
}
}
server:
port: 9000
spring:
application:
name: first-service-invoker
eureka:
instance:
hostname: localhost
client:
registry-fetch-interval-seconds: 5
serviceUrl:
defaultZone: http://localhost:8761/eureka/
logging:
level:
com.netflix: DEBUG
2.3、元数据的配置和使用
2.3.1、元数据的配置
server:
port: 8080
spring:
application:
name: first-service-provider
eureka:
instance:
lease-renewal-interval-in-seconds: 5
lease-expiration-duration-in-seconds: 10
metadata-map:
company-name: aitemi
hostname: localhost
client:
serviceUrl:
defaultZone: http:
logging:
level:
com.netflix: DEBUG
2.3.2、元数据的使用
package com.atm.cloud
import java.util.List
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.cloud.client.ServiceInstance
import org.springframework.cloud.client.loadbalancer.LoadBalanced
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.MediaType
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestMethod
import org.springframework.web.bind.annotation.ResponseBody
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.client.RestTemplate
import org.springframework.cloud.client.discovery.DiscoveryClient
@RestController
@Configuration
public class InvokerController {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate()
}
@RequestMapping(value = "/router", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public String router() {
RestTemplate restTemplate = getRestTemplate()
// 根据应用名称调用服务
String json = restTemplate.getForObject(
"http://first-service-provider/person/1", String.class)
return json
}
@Autowired
public DiscoveryClient discoveryClient
//获取服务列表,并将服务列表的数量显示出来
//默认是30s抓取一次,可以根据需求进行修改
@GetMapping("/list")
@ResponseBody
public String serviceCount() {
List<String> serviceNames = discoveryClient.getServices()
for (String serviceId : serviceNames) {
List<ServiceInstance> serviceInstance = discoveryClient
.getInstances(serviceId)
System.out.println(serviceId + ": " + serviceInstance.size())
}
return ""
}
@GetMapping("/metedata")
@ResponseBody
public String getMetedata(){
System.out.println("Come into getMetedata...")
List<ServiceInstance> serviceInstances = discoveryClient.getInstances("first-service-provider")
for (ServiceInstance serviceInstance : serviceInstances) {
String name=serviceInstance.getMetadata().get("company-name")
System.out.println(serviceInstance.getPort()+"---->>>"+name)
}
return ""
}
}
2.4、关闭自我保护模式
- 客户端会定时向服务器端发送心跳服务
- 如果失败率(默认15分钟85%)超过一定的比率,整个服务实例会被先保护起来,并不会立刻从服务列表中清除,从而可能引起其他客户端调用服务失败
- 自我保护模式可能导致灾难蔓延,为解决这种情况,一般会在其他客户端使用容错机制
server:
port: 8761
eureka:
client:
registerWithEureka: false
fetchRegistry: false
server:
enable-self-preservation: false
eviction-interval-timer-in-ms: 5000
3、健康检测监控
- 在实际开发中,还有可能出现一种情况
- 客户端的服务需要连接数据库,可是数据库已经崩溃,但是客户端仍然向服务器端发送心跳请求,这种情况下服务器端误认为客户端的服务是正常的
- 客户端应该及时告诉服务器端自己的健康状态
- 加入Actuator,提供了很多端点,其中一个叫做/heath端点
3.1、健康监控
3.1.1、服务提供者引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>1.5.4.RELEASE</version>
</dependency>
3.1.2、配置文件
server:
port: 8080
spring:
application:
name: first-service-provider
endpoints:
sensitive: false
eureka:
instance:
lease-renewal-interval-in-seconds: 5
lease-expiration-duration-in-seconds: 10
metadata-map:
company-name: aitemi
hostname: localhost
client:
serviceUrl:
defaultZone: http:
logging:
level:
com.netflix: DEBUG
3.1.3、端口测试
3.2、健康模拟
3.2.1、服务提供者控制器
package com.atm.cloud
import org.springframework.http.MediaType
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestMethod
import org.springframework.web.bind.annotation.RestController
@RestController
public class MyController {
//能否访问数据库
public static boolean canVisitOb=true
@RequestMapping(value = "/db/{can}", method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public void setOb(@PathVariable boolean can){
this.canVisitOb=can
}
}
3.2.2、服务提供者健康监控器
package com.atm.cloud;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.Status;
import org.springframework.stereotype.Component;
/**
* 健康指示器
* @author Aitemi
*
*/
@Component
public class MyHealthIndicator implements HealthIndicator{
public Health health() {
if(MyController.canVisitOb){
return new Health.Builder(Status.UP).build();
}else{
return new Health.Builder(Status.DOWN).build();
}
}
}
- 我们可以发现,虽然客户端已经修改了健康状态,但是服务器端并不知道,所以还需要编写一个健康处理器
3.2.3、服务提供者健康处理器
package com.atm.cloud;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.health.Status;
import org.springframework.stereotype.Component;
import com.netflix.appinfo.HealthCheckHandler;
import com.netflix.appinfo.InstanceInfo.InstanceStatus;
/**
* 健康检测处理器
* @author Aitemi
*
*/
@Component
public class MyHealthCheckHandler implements HealthCheckHandler{
@Autowired
private MyHealthIndicator myHealthIndicator;
public InstanceStatus getStatus(InstanceStatus currentStatus) {
Status status=myHealthIndicator.health().getStatus();
if(status.equals(Status.UP)){
return InstanceStatus.UP;
}else{
return InstanceStatus.DOWN;
}
}
}