(转)Spring Cloud(二)

(二期)23、微服务框架spring cloud(二)

【课程23】熔断器-Hystrix.xmind0.1MB

【课程23】微服务...zuul.xmind0.2MB

熔断器-Hystrix
雪崩效应

在微服务架构中通常会有多个服务层调用,基础服务的故障可能会导致级联故障,进而造成整个系统不可用的情况,这种现象被称为服务雪崩效应。服务雪崩效应是一种因“服务提供者”的不可用导致“服务消费者”的不可用,并将不可用逐渐放大的过程。 

如下图所示:A作为服务提供者,B为A的服务消费者,C和D是B的服务消费者。A不可用引起了B的不可用,并将不可用像滚雪球一样放大到C和D时,雪崩效应就形成了。 

解决方向
  • 为网络其请求设置超时时间
  • 使用断路器模式
  • 统计一段时间内的失败次数来决定正常访问还是直接返回
  • 断路器处于半开状态,应许一个请求进入,如果请求正常,就关闭断路器。

在这种情况下就需要整个服务机构具有故障隔离的功能,避免某一个服务挂掉影响全局。

在Spring Cloud 中Hystrix组件就扮演这个角色。 一个实现了超时机制和断路器模式的工具类库。

Hystrix会在某个服务连续调用N次不响应的情况下,立即通知调用端调用失败,避免调用端持续等待而影响了整体服务。Hystrix间隔时间会再次检查此服务,如果服务恢复将继续提供服务。 

简介

Hystrix是Netflix开源的一个延迟和容错库,用于隔离访问远程系统、服务或第三方库,防止级联失败,从而提高系统的可用性与容错性。

Hystrix主要通过以下几点实现延迟和容错:

  • 包裹请求:使用HystrixCommand(或HystrixObservableCommand)包裹对依赖的调用逻辑,每个命令在独立线程中执行。这使用到了设计模式中的“命令模式”。
  • 跳闸机制:当某服务的错误率超过一定阈值时,Hystrix可以自动或者手动跳闸,停止请求该服务一段时间。
  • 资源隔离:Hystrix为每个依赖都维护了一个小型的线程池(或者信号量)。如果该线程池已满,发往该依赖的请求就被立即拒绝,而不是排队等候,从而加速失败判定。
  • 监控:Hystrix可以近乎实时地监控运行指标和配置的变化,例如成功、失败、超时、以及被拒绝的请求等。
  • 回退机制:当请求失败、超时、被拒绝,或当断路器打开时,执行回退逻辑。回退逻辑可由开发人员自行提供,例如返回一个缺省值。
  • 自我修复:断路器打开一段时间后,会自动进入“半开”状态。断路器打开、关闭、半开的逻辑转换,上文已经详细探讨过,不再赘述。

服务降级

优先核心服务,非核心服务不可用或弱可用。

  • 通过HystrixCommand注解
  • fallbackMethod回退函数
Hystrix如何解决依赖隔离
  • 1:Hystrix使用命令模式HystrixCommand(Command)包装依赖调用逻辑,每个命令在单独线程中/信号授权下执行。
  • 2:可配置依赖调用超时时间,超时时间一般设为比99.5%平均时间略高即可.当调用超时时,直接返回或执行fallback逻辑。
  • 3:为每个依赖提供一个小的线程池(或信号),如果线程池已满调用将被立即拒绝,默认不采用排队.加速失败判定时间。
  • 4:依赖调用结果分:成功,失败(抛出异常),超时,线程拒绝,短路。 请求失败(异常,拒绝,超时,短路)时执行fallback(降级)逻辑。
  • 5:提供熔断器组件,可以自动运行或手动调用,停止当前依赖一段时间(10秒),熔断器默认错误率阈值为50%,超过将自动运行。
  • 6:提供近实时依赖的统计和监控
Hystrix整合步骤

第一步:导入hystrix坐标

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

第二步、启动application添加注解@EnableHystrix启动断溶处理

@EnableHystrix
或者
@EnableCircuitBreaker

第三步、使用@HystrixCommand注解,在对应的接口上添加容灾方法

@HystrixCommand(fallbackMethod = "getUsernameFallback")
@GetMapping("/username")
public Object getUsername() {
    return restTemplate.getForObject("http://EUREKA-CLIENT-2/username", String.class);
}
 
  
public Object getUsernameFallback() {
    return "fall back";
}

默认提示:@HystrixCommand可以和@DefaultProperties配合使用

@hystrixCommand注解参数详解

Feign使用Hystrix回退机制

定义一个实现类,接口端使用@FeignClient的fallback指向要回退的类。

/**
 * Feign的fallback测试类
 * 使用@FeignClient的fallback属性指定回退类
 */
@FeignClient(name = "microservice-provider-user", fallback = FeignClientFallback.class)
public interface UserFeignClient {
 
  
    @GetMapping(value = "/{id}")
    User findById(@PathVariable("id") Long id);
 
  
}
 
  
/**
 * 回退类FeignClientFallback需实现Feign Client接口
 * FeignClientFallback也可以是public class,没有区别
 */
@Component
class FeignClientFallback implements UserFeignClient {
 
  
    @Override
    public User findById(Long id) {
        User user = new User();
        user.setId(-1L);
        user.setUsername("默认用户");
        return user;
    }
}

同时在yml配置文件中配置:

feign:
  hystrix:
    enabled: true
检查回退原因

第一步、feign接口使用fallbackFactory属性

@FeignClient(name = "eureka-client-2", fallbackFactory = Demo2ClientFallbackFactory.class)
public interface Demo2Client {
 
  
    @GetMapping("/username")
    public String getUsername();
 
  
}

第二步、定义fallbackFactory的实现类。重写create方法,使用Throwable cause查看回退原因。

 
  
@Component
public class Demo2ClientFallbackFactory implements FallbackFactory<Demo2Client> {
 
  
    public Demo2Client create(Throwable cause) {
 
  
        return new Demo2Client() {
            @Override
            public String getUsername() {
                
                System.out.println(cause.toString());
                return "hello world fallbackfactory";
            }
        };
    }
}

在实际开发中,并不是所有的请求都要提前预备好服务降级问题,如果我就是要将服务调用失败的信息展示给用户,那么此时就没有必要添加断路器了。

Hystrix断路器模式的状态监控

关闭服务B端:

访问 http://localhost:8010/actuator/health,结果如下,此时Hystrix的状态依然是 UP,这是因为我们的失败率还没有达到阈值(默认是5秒返回20次失败)

断路器的状态也会暴露在Actuator提供的 /actuator/health 端口中,这样就可以直观地了解断路器的状态。

<!--actuator用于应用监控管理-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

yml配置

management:
  endpoint:
    health:
      show-details: always

如果控制台输出:

#说明断路器已经打开
Hystrix circuit short-circuited and is OPEN
Hystrix的两种隔离方式

https://www.cnblogs.com/java-synchronized/p/7927726.html

  • 线程隔离
  • 它将在单独的线程上执行,并发请求受线程池中的线程数量的限制。
  • 把执行依赖代码的线程与请求线程(如:jetty线程)分离,请求线程可以自由控制离开的时间(异步过程)。
  • 通过线程池大小可以控制并发量,当线程池饱和时可以提前拒绝服务,防止依赖问题扩散。
  • 线上建议线程池不要设置过大,否则大量堵塞线程有可能会拖慢服务器。

  • 信号量隔离
  • 它将在调用线程上执行,开销相对较小,并发请求收到信号量个数的限制。
  • 信号隔离也可以用于限制并发访问,防止阻塞扩散, 与线程隔离最大不同在于执行依赖代码的线程依然是请求线程(该线程需要通过信号申请)
  • 当n个并发请求去调用一个目标服务接口时,都要获取一个信号量才能真正去调用目标服务接口,但信号量有限,默认是10个,可以使用maxConcurrentRequests参数配置,如果并发请求数多于信号量个数,就有线程需要进入队列排队,但排队队列也有上限,默认是 5,如果排队队列也满,则必定有请求线程会走fallback流程,从而达到限流和防止雪崩的目的。

服务网关-Zuul

在微服务架构模式下,后端服务的实例数一般是动态的,对于客户端而言很难发现动态改变的服务实例的访问地址信息。因此在基于微服务的项目中为了简化前端的调用逻辑,通常会引入API Gateway作为轻量级网关,同时API Gateway中也会实现相关的认证逻辑从而简化内部服务之间相互调用的复杂度。 

Spring Cloud体系中支持API Gateway落地的技术就是Zuul。Spring Cloud Zuul路由是微服务架构中不可或缺的一部分,提供动态路由,监控,弹性,安全等的边缘服务。Zuul是Netflix出品的一个基于JVM路由和服务端的负载均衡器。 

它的具体作用就是服务转发,接收并转发所有内外部的客户端调用。使用Zuul可以作为资源的统一访问入口,同时也可以在网关做一些权限校验等类似的功能。 

集成步骤

第一步、

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

第二步、yml配置

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

第三步、配置路由规则,链接可以不用添加应用名称。可以配置前缀调整规则

# 路由规则配置
zuul:
  routes:
    api-a:
      path: /usernameByFeign
      serviceId: eureka-client-1

第三步、application添加注解@EnableZuulProxy

启动项目之后可以看到在注册中心已经Zuul的注册了。

配置过滤器

第一步继承网关路由。

  • PRE: 这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
  • ROUTING:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用Apache HttpClient或Netfilx Ribbon请求微服务。
  • POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。

ERROR:在其他阶段发生错误时执行该过滤器。 

  • 除了默认的过滤器类型,Zuul还允许我们创建自定义的过滤器类型。例如,我们可以定制一种STATIC类型的过滤器,直接在Zuul中生成响应,而不将请求转发到后端的微服务。
鉴权代码示例
 
  
public class PreLogFilter extends ZuulFilter {
 
  
    /**
     * pre请求之前
     * route用于将请求路由转到微服务
     * post路由到微服务以后执行
     * error在其他阶段发生错误的时候执行
     * @return
     */
    @Override
    public String filterType() {
        return "pre";
    }
 
  
    /**
     * 执行顺序
     * @return
     */
    @Override
    public int filterOrder() {
        return 0;
    }
 
  
    /**
     * true返回一个boolean判断该过滤器是否要执行
     * @return
     */
    @Override
    public boolean shouldFilter() {
        return true;
    }
 
  
    /**
     * 过滤器执行具体内容
     * @return
     * @throws ZuulException
     */
    @Override
    public Object run() throws ZuulException {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        System.out.println("---》路由获取所有参数值" + request.getParameterMap().toString());
        System.out.println(request.getHeader("cookie"));
        return null;
    }
}
@Bean
PreLogFilter preLogFilter() {
    return new PreLogFilter();
}
限流代码示例
<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>27.0-jre</version>
</dependency>
import com.google.common.util.concurrent.RateLimiter;
import com.netflix.zuul.ZuulFilter;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;
 
  
/**
 * 限流
 */
public class RateLimiterFilter extends ZuulFilter {
 
  
    //guava的令牌桶算法
    private static final RateLimiter RATE_LIMITER = RateLimiter.create(100);
 
  
    @Override
    public String filterType() {
        return PRE_TYPE;
    }
 
  
    @Override
    public int filterOrder() {
        return -5;
    }
 
  
    @Override
    public boolean shouldFilter() {
        return true;
    }
 
  
    @Override
    public Object run(){
 
  
        if(!RATE_LIMITER.tryAcquire()) {
            throw new RuntimeException("占坑失败~~");
        }
 
  
        System.out.println("-------->没限流");
        return null;
    }
}
@Bean
RateLimiterFilter rateLimiterFilter() {
    return new RateLimiterFilter();
}
应用场景

前置过滤器

  • 限流
  • 鉴权
  • 参数校验

后置过滤器

  • 统计
  • 日志
高可用

多个zuul节点同时注册到euraka上,节点名称保持一致。与其他客户端方法一致。这时候,有多个zuul对外暴露接口,外层就可以弄个nginx搞负载均衡。

Cloud-Admin项目

项目

文档中心

猜你喜欢

转载自www.cnblogs.com/free-wings/p/10095708.html