Eureka管理机制
心跳检测
1.Eureka Client会定期(默认30秒)向Eureka Server端发送信息刷新注册信息,表示续约。若在指定时间(默认90秒)没有向服务器发送信息,则服务端将其从服务列表中删除。
- 控制发送心跳间隔
eureka.instance.lease-renewal-interval-in-seconds 表示eureka client发送心跳给server端的频率,默认30秒。 如果在leaseExpirationDurationInSeconds后,server端没有收到client的心跳,则将摘除该instance。
- 控制超时清理间隔
eureka.instance.lease-expiration-duration-in-seconds 表示eureka server至上一次收到client的心跳之后,等待下一次心跳的超时时间,在这个时间内若没收到下一次心跳, 则将移除该instance。默认为90秒 如果该值太大,则很可能将流量转发过去的时候,该instance已经不存活了。 如果该值设置太小了,则instance则很可能因为临时的网络抖动而被摘除掉。 建议该值至少应该大于leaseRenewalIntervalInSeconds
2.Eureka Server定期(默认60秒)检测注册列表,清理无效服务节点。但是由于Server默认开启了自我保护机制,即使有无效节点也不会剔除。
- 控制清理无效节点间隔
eureka.server.eviction-interval-timer-in-ms eureka server清理无效节点的时间间隔,默认60000毫秒,即60秒
自我保护机制
默认情况下,如果Eureka Server在一定时间内没有接收到某个微服务实例的心跳,Eureka Server将会注销该实例(默认90秒)。但是当网络分区故障发生时,微服务与Eureka Server之间无法正常通信,以上行为可能变得非常危险了——因为微服务本身其实是健康的,此时本不应该注销这个微服务。
Eureka通过“自我保护模式”来解决上述问题,一旦进入该模式,Eureka Server就会保护服务注册表中的信息,不再删除服务注册表中的数据(也就是不会注销任何微服务)。当网络故障恢复后,该Eureka Server节点会自动退出自我保护模式。
自我保护的工作机制是如果在15分钟内正常心跳的客户端节点低于85%,那么Eureka就认为客户端与注册中心出现了网络故障,Eureka Server自动进入自我保护机制,此时会出现以下几种情况:
- Eureka Server不再从注册列表中移除因为长时间没收到心跳而应该过期的服务。
- Eureka Server仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上,保证当前节点依然
可用。 - 当网络稳定时,当前Eureka Server新的注册信息会被同步到其它节点中。
控制启用/关闭自我保护配置如下,开发测试可关闭,发布运行时建议启用。
eureka.server.enable-self-preservation是否开启自我保护模式,默认为true。
Eureka的自我保护机制的意义在于当EurekaServer由于自身发生网络故障等原因无法接收到EurekaClient端发送的心跳(续约)时,不会将未收到心跳(续约)请求的服务下线,虽然这样短时间内可能造成EurekaServer维护的注册列表信息不是完全准确的,但保证了EurekaServer可用性。它底层源码主要通过expectedNumberOfRenewsPerMin,numberOfRenewsPerMinThreshold这两个值判断是否进入自我保护模式,当每分钟收到的心跳数量小于期望收到的心跳数量,EurekaServer便会进入自我保护模式,不会剔除任何一个服务,直到心跳回复正常后退出自我保护模式。
服务注册源码:
if (this.expectedNumberOfRenewsPerMin > 0) {
// Since the client wants to cancel it, reduce the threshold
// (1
// for 30 seconds, 2 for a minute)
// 每分钟期望收到的心跳(续约)次数增加两次
this.expectedNumberOfRenewsPerMin = this.expectedNumberOfRenewsPerMin + 2;
// 每分钟期望最小心跳(续约 )次数 = 修改后的expectedNumberOfRenewsPerMin * 默认的0.85
this.numberOfRenewsPerMinThreshold =
(int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold());
}
服务下线源码:
synchronized (lock) {
if (this.expectedNumberOfRenewsPerMin > 0) {
// Since the client wants to cancel it, reduce the threshold (1 for 30 seconds, 2 for a minute)
// 每分钟期望收到的心跳(续约)次数减少两次
this.expectedNumberOfRenewsPerMin = this.expectedNumberOfRenewsPerMin - 2;
// 每分钟期望最小心跳(续约 )次数 = 修改后的expectedNumberOfRenewsPerMin * 默认的0.85
this.numberOfRenewsPerMinThreshold =
(int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold());
}
}
Eureka Server控制台有Renews threshold和Renews (last min)两个参数
Renews threshold:Eureka Server 期望每分钟收到客户端实例续约的总数。 Renews (last min):Eureka Server 最后 1 分钟收到客户端实例续约的总数。
当自我保护模式被激活时会发现Renews (last min)*85% < Renews threshold。
服务注册列表
通过心跳机制Eureka Server端定时更新注册列表。在Eureka Client端也提供了服务注册列表缓存,Client中启动了一个定时任务,周期性的通过Http请求去Server端拉取最新的注册表信息,并缓存到本地,默认30秒。
eureka.client.registry-fetch-interval-seconds //更新列表缓存时间,默认30秒
eureka.client.fetch-registry //默认开启,抓取注册列表
当Eureka Server端出现故障时,Eureka Client也可以通过缓存的注册列表实现服务查找和调用。
SpringCloud Hystrix断路器
SpringCloud提供了熔断机制,当多个服务互相关联,某个服务坏掉,会影响关联的服务导致大面积失败,称为雪崩效应。
Hystrix断路器提供了服务降级、服务隔离等机制保护系统。
1.服务降级
- 默认情况下,Cloud Hystrix要求1秒钟响应,超过1秒被认为失败,会自动调用“备胎”方法。
2.服务隔离机制
- 当服务在某个段时间内(默认5S),如果处理失败的频率达到指定比率(默认50%),这是Hystrix就要将断路器阀Open(断开服务),又过5S会将阀半开状态,如果处理成功率达到指定标准,会将断路器阀Close,未达到标准再Open。
服务降级使用方法
1.普通服务降级使用
- 在pom.xml导入spring-cloud-starter-netflix-hystrix
- 在启动类前使用@EnableCircuitBreaker或@EnableHystrix
- 在服务处理方法前使用@HystrixCommand(fallbackMethod="loadDirectionFallBack")
@GetMapping("/direction/{id}")
@HystrixCommand(fallbackMethod="loadDirectionFallBack")
public YdmaResult loadDirection(@PathVariable("id")int id) {
return directionService.loadDirection(id);
}
//fallback方法参数和原服务方法保持一致
public YdmaResult loadDirectionFallBack(int id) {
YdmaResult result = new YdmaResult();
result.setCode(YdmaConstant.ERROR1);
result.setMsg(YdmaConstant.ERROR_MSG);
return result;
}
- 默认处理时间1S,可以通过下面配置改变
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=2000
2.Feign服务降级使用
通过feign接口模式调用服务,发生延迟或异常,默认是不会触发Hystrix服务降级的,需要进行设置和开启。
- 编写Feign接口实现类,在实现类方法中写默认FallBack返回逻辑.
提示:实现组件要追加@Component扫描标记
- 将Feign接口实现类给@FeignClient的fallback属性指定
@FeignClient(name="YDMA-DIRECTION",fallback=DirectionFeignRemoteFallBack.class)
public interface DirectionFeignRemote {
@GetMapping("/direction/get")
public YdmaResult loadDirection(int id);
@GetMapping("/direction/{id}")
public YdmaResult loadDirection1(@PathVariable("id")int id);
@GetMapping("/direction/all")
public YdmaResult loadAllDirections();
}
- 在application.properties配置中打开Feign启用Hystrix设置
在A、B、C默认结合启用,但是从D以后默认关闭,需要打开设置
feign.hystrix.enabled=true
SpringCloud Zuul
网关,外界环境访问Cloud服务中心的服务时,都需要通过网关组件进行访问,相当于代理作用。微服务网关是微服务架构中一个不可或缺的部分。通过服务网关统一向外系统提供REST API的过程中,除了具备服务路由、均衡负载功能之外,它还具备了权限控制等功能。
- 在Spring cloud体系中,一般上选择zuul或者Gateway网关技术。
Spring Cloud Zuul:Zuul是Netflix开源的微服务网关
可以和Eureka、Ribbon、Hystrix等组件配合使用,
Spring Cloud对Zuul进行了整合与增强,Zuul的主要功能是路由转发和过滤器。
Spring Cloud Gateway:是由spring官方基于Spring5.0,Spring Boot2.0,Project Reactor等技术开发的网关,
提供了一个构建在Spring Ecosystem之上的API网关,旨在提供一种简单而有效的途径来发送API,
并向他们提供交叉关注点,例如:安全性,监控/指标和弹性。
目的是为了替换Spring Cloud Netfilx Zuul的。
SpringCloud Zuul网关的搭建过程如下:
1.创建一个项目,在pom.xml导入spring-boot-starter-parent、spring-cloud-denpendences、spring-cloud-starter-netflix-eureka-client、spring-cloud-starter-netflix-zuul
org.springframework.boot
spring-boot-starter-parent
2.0.5.RELEASE
org.springframework.cloud
spring-cloud-dependencies
Finchley.RELEASE
pom
import
org.springframework.cloud
spring-cloud-starter-netflix-zuul
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
2.在application.properties配置文件定义eureka参数
server.port=9999
spring.application.name=ydma-zuul
eureka.client.serviceUrl.defaultZone=http://localhost:3333/eureka
3.在启动类前使用@EnableZuulProxy、@EnableDiscoveryClient等
@SpringBootApplication
@EnableDiscoveryClient
@EnableZuulProxy//启用zuul代理
public class ZuulRunBoot {
public static void main(String[] args) {
SpringApplication.run(ZuulRunBoot.class, args);
}
}
4.外界访问,通过zuul网关访问eureka注册其他的集群服务,访问格式
http://localhost:9999/服务名/请求
提示:服务名用小写
Zuul Filter
SpringCloud提供了很多内置的filter,进行请求处理。
Zuul过滤器适合做一些权限检查、身份认证等处理。
编写实现类,继承ZuulFilter,重写约定方法
@Component
public class CheckLoginFilter extends ZuulFilter{
@Autowired
private RestTemplate restTemplate;
@Override
public boolean shouldFilter() {
//获取请求URI,不需要登录的就return false;需要登录的return true
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String uri = request.getRequestURI();
List checkUris = new ArrayList();
checkUris.add("/ydma-video/chapters");
System.out.println("检查"+uri+"是否需要进行拦截检查");
if(checkUris.contains(uri)) {
return true;//true调用filter
}
return false;//false不调用filter
}
@Override
public Object run() throws ZuulException {
//过滤器执行逻辑
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
HttpServletResponse response = ctx.getResponse();
String ticket = request.getParameter("ticket");
if(ticket != null&&!"".equals(ticket)) {
YdmaResult checkResult = restTemplate.getForObject(
"http://YDMA-USER/user/ticket?ticket="+ticket, YdmaResult.class);
if(checkResult.getCode()==YdmaConstant.SUCCESS) {
ctx.setSendZuulResponse(true);//允许继续路由调用服务
ctx.setResponseStatusCode(200);
return null;
}
}
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "GET,POST");
response.setContentType("text/html;charset=utf-8");
ctx.setSendZuulResponse(false);//阻止继续路由调用服务
ctx.setResponseStatusCode(401);//response.setStatus(401);
ctx.setResponseBody("{\"code\":-1,\"msg\":\"用户身份不合法,未登录\"}");//out.println("{\"code\":-1,\"msg\":\"用户身份不合法,未登录\"}");
return null;
}
@Override
public String filterType() {
return "pre";//过滤器类型,pre、post、error、route
}
@Override
public int filterOrder() {
return 0;//过滤器执行顺序... -3、-2、-1、0、1、2、3
}
}
Zuul Fallback回退机制
我们在项目中使用Spring cloud zuul的时候,在zuul进行路由分发时,如果后端服务没有启动,或者调用超时,这时候我们希望Zuul提供一种hystrix服务降级处理,而不是将异常暴露出来。
Zuul集成了Hystrix,提供一个FallbackProvider回退机制当路由后面的服务发生故障时进行降级处理。
@Component
public class ServiceFallbackProvider implements FallbackProvider {
@Override
// 返回值表示需要针对此微服务做回退处理(该名称一定要是注册进入 eureka 微服务中的那个 serviceId 名称)
public String getRoute() {
return "*"; // api服务id,如果需要所有调用都支持回退,则return "*"或return null
}
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.OK; // 请求网关成功了,所以是ok
}
@Override
public int getRawStatusCode() throws IOException {
return HttpStatus.OK.value();
}
@Override
public String getStatusText() throws IOException {
return HttpStatus.OK.getReasonPhrase();
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
String msg = "{\"code\":-1,\"msg\":\"服务调用失败\"}";
return new ByteArrayInputStream(msg.getBytes("UTF-8")); // 返回前端的内容
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON_UTF8); // 设置头
return httpHeaders;
}
};
}
}