SpringCloud高可用学习

雪崩效应

基础服务的故障导致的级联故障,最终导致服务的不可用。

形成原因:
1、服务提供者不可用
2、服务调用者不可用
3、大流量重试

服务雪崩应对策略:
1、流量控制
2、改进缓存
3、服务自动扩容
4、服务调用者降级服务

断路器
SpringCloud高可用学习_第1张图片
工作原理.png

由上图可以看到,断路器有三种工作模式,分别是关闭、半开、打开。

熔断器开关相互转换的逻辑:
1、服务的健康状况 = 请求失败数 / 请求总数.
2、熔断器开关由关闭到打开的状态转换是通过当前服务健康状况和设定阈值比较决定的
  a、关闭时, 请求被允许通过熔断器. 如果当前健康状况高于设定阈值, 开关继续保持关闭. 如果当前健康状况低于设定阈值, 开关则切换为打开状态
  b、打开状态, 经过一段时间后, 熔断器会自动进入半开状态, 这时熔断器只允许一个请求通过. 当该请求调用成功时, 熔断器恢复到关闭状态. 若该请求失败, 熔断器继续保持打开状态, 接下来的请求被禁止通过

Hystrix简介及代码示例
Feign的Fallback及FallbackFactory

1、使用FallbackFactory时以DEBUG模式启动时报空指针异常问题排查

2018-07-06 14:14:09.714 ERROR 28486 --- [           main] c.l.c.f.UserFeignClientFallbackFactory   : 服务调用失败:{}

java.lang.RuntimeException: null
    at org.springframework.cloud.netflix.feign.HystrixTargeter.targetWithFallbackFactory(HystrixTargeter.java:59) [spring-cloud-netflix-core-1.2.3.RELEASE.jar:1.2.3.RELEASE]
    at org.springframework.cloud.netflix.feign.HystrixTargeter.target(HystrixTargeter.java:45) [spring-cloud-netflix-core-1.2.3.RELEASE.jar:1.2.3.RELEASE]
    at org.springframework.cloud.netflix.feign.FeignClientFactoryBean.loadBalance(FeignClientFactoryBean.java:146) [spring-cloud-netflix-core-1.2.3.RELEASE.jar:1.2.3.RELEASE]
    at org.springframework.cloud.netflix.feign.FeignClientFactoryBean.getObject(FeignClientFactoryBean.java:167) [spring-cloud-netflix-core-1.2.3.RELEASE.jar:1.2.3.RELEASE]
    at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.doGetObjectFromFactoryBean(FactoryBeanRegistrySupport.java:168) [spring-beans-4.3.4.RELEASE.jar:4.3.4.RELEASE]  

源码解读:

//FeignClientFactoryBean时,Spring默认调用bean的getObject方法创建实例
@Override
public Object getObject() throws Exception {
    FeignContext context = applicationContext.getBean(FeignContext.class);
    Feign.Builder builder = feign(context);
        if (!StringUtils.hasText(this.url)) {
            String url;
            if (!this.name.startsWith("http")) {
                url = "http://" + this.name;
            }
            else {
                url = this.name;
            }
            url += cleanPath();
            return loadBalance(builder, context, new HardCodedTarget<>(this.type,
                    this.name, url));
        }
        if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
            this.url = "http://" + this.url;
        }
        String url = this.url + cleanPath();
        Client client = getOptional(context, Client.class);
        if (client != null) {
            if (client instanceof LoadBalancerFeignClient) {
                // not lod balancing because we have a url,
                // but ribbon is on the classpath, so unwrap
                client = ((LoadBalancerFeignClient)client).getDelegate();
            }
            builder.client(client);
        }
        Targeter targeter = get(context, Targeter.class);
        return targeter.target(this, builder, context, new HardCodedTarget<>(
                this.type, this.name, url));
    }
@Override
public  T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,Target.HardCodedTarget target) {
    if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
            return feign.target(target);
        }
        feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
        Class fallback = factory.getFallback();
        if (fallback != void.class) {
            return targetWithFallback(factory.getName(), context, target, builder, fallback);
        }
        Class fallbackFactory = factory.getFallbackFactory();
        if (fallbackFactory != void.class) {
            return targetWithFallbackFactory(factory.getName(), context, target, builder, fallbackFactory);
        }
        return feign.target(target);
    }
private  T targetWithFallbackFactory(String feignClientName, FeignContext context,Target.HardCodedTarget target,  HystrixFeign.Builder builder,Class fallbackFactoryClass) {
    FallbackFactory fallbackFactory = (FallbackFactory)
    getFromContext("fallbackFactory", feignClientName, context, fallbackFactoryClass, FallbackFactory.class);
    /* We take a sample fallback from the fallback factory to check if it returns a fallback that is compatible with the annotated feign interface. */
    Object exampleFallback = fallbackFactory.create(new RuntimeException());
    Assert.notNull(exampleFallback,String.format("Incompatible fallbackFactory instance for feign client %s. Factory may not produce null!",feignClientName));
    if(!target.type().isAssignableFrom(exampleFallback.getClass())) {
        throw new IllegalStateException(
             String.format("Incompatible fallbackFactory instance for feign client %s. Factory produces instances of '%s', but should produce instances of '%s'",feignClientName, exampleFallback.getClass(), target.type()));
        }
    return builder.target(target, fallbackFactory);
}
//在target方法中,由于我们设置了fallbackFactory属性,所以会调用targetWithFallbackFactory,该方法就会为我们自动设置一次异常抛出,用以检验我们的fallbackFactory是否正确。
如何禁用单个FeignClient的Hystrix支持
Hystrix Dashboard的使用

1、启动Hystrix监控,访问:http://localhost:8090/hystrix

SpringCloud高可用学习_第2张图片
Hystrix Dashboard.png

2、填入包含断路器的应用,点击monitor stream按钮
SpringCloud高可用学习_第3张图片
健康信息监控.png

Turbine的使用

疑问:有了Hystrix Dashboard,为什么还需要Turbine?
业务复杂的场景中,服务调用层级比较多的时候,可能涉及到几个十几个甚至几十个微服务,我们想看到一次业务请求对应的所有微服务的性能,而不是一个微服务一个微服务的去查看,所以我们需要一个工具能让我们汇总系统内多个服务的数据并显示到Hystrix Dashboard上,使用Turbine就可以做到这一点。
1、添加相关依赖

    
        
        
            org.springframework.cloud
            spring-cloud-starter-eureka
        
        
        
            org.springframework.cloud
            spring-cloud-starter-hystrix
        
        
            org.springframework.cloud
            spring-cloud-starter-hystrix-dashboard
        
        
        
            org.springframework.cloud
            spring-cloud-starter-turbine
        
    

2、配置yml

//使用http://turbine-host:port/turbine.stream?cluster=[clusterName]来查看这个集群对应的服务的HystrixCommand信息
turbine:
  aggregator:
      clusterConfig: MICROSERVICE-FALLBACK-WITH-FEIGN
  appConfig: microservice-fallback-with-feign
  clusterNameExpression: "'default'"

turbine.appConfig:配置是一个eureka服务ID列表
turbine.aggregator.clusterConfig:配置集群名
turbine.clusterNameExpression: 元数据映射

3、@EnableTurbine开启

SpringRetry
   
   
        org.springframework.retry
        spring-retry
   
   
       org.aspectj
       aspectjweaver
   
/**
 * @author: 苏小城
 * @date: 17/12/1
 * @version: 1.0.0
 */
public interface BusinessService {
    /**
     * 业务service
     */
    @Retryable(value = {ArithmeticException.class,RemoteAccessException.class,RuntimeException.class},
            maxAttempts=5,backoff= @Backoff(delay = 2000,multiplier = 1))
    void businessService();
    @Recover
    void recover();
}

/**
 * @author: 苏小城
 * @date: 17/12/1
 * @version: 1.0.0
 */
@Service
public class BusinessServiceImpl implements BusinessService {
   @Override
   public void businessService() {
       System.out.println("===业务逻辑操作===");
       int i = 1/0;//除零异常
   }
   @Override
   public void recover() {
       System.out.println("RPC调用异常返回空数据");
   }
}
测试结果
Ribbon+SpringRetry实现负载均衡和服务请求重试

策略模式:
1、简单轮询负载均衡(RoundRobin)
2、随机负载均衡 (Random)
3、加权响应时间负载均衡 (WeightedResponseTime)
4、区域感知轮询负载均衡(ZoneAware)
5、选择一个最小的并发请求(BestAvailableRule)

你可能感兴趣的:(SpringCloud高可用学习)