Spring-Cloud Hystrix学习心得

1.Hystrix作用

最近刚接手了一个B2B项目,采用了微服务架构开发的。既然使用了微服务架构那就少不了Hystrix这东东。本人感觉Hystrix很神奇,这些天看了老师讲解的视频,想自己好好整理总结一下,也方便后期自己阅读。

在微服务架构里面,某个服务发生了故障,通过断路监控,向调用方返回一个相应的错误,而不是让调用方长时间等待,这样避免了故障在分布式系统中蔓延。下面有几个耳熟能详的专业名字。

1:服务降级 :调用服务的某个方法,由于阻塞或逻辑错误,直接调用failBack(可以理解成程序异常的处理方式,可以理解成一个备胎),降级是服务方提供的。

2:服务熔断 :由于网络原因服务调用不通(比如小明家里保险丝断了,家里停电了,但是家里面来了客人吃饭没电怎么行呢。还好家里有手电筒,就用手电筒照着吃饭吧)。熔断是消费方提供的。

3:服务雪崩 :由于网络导致服务的本身原因,导致服务阻塞,导致整个微服务的服务都不能使用。

说了这么多,其实Hystrix就是一种实现技术,这种技术隔离了服务的调用方和提供方,有了服务隔离技术后,就不会存在服务的雪崩、服务器连接资源占满的情况了。

2.Hystrix基本使用


@SpringBootApplication
@EnableEurekaClient
@Import(WebSecurityConfiguration.class)
@MapperScan("org.admincloud.provider.hystrix.mapper")
@EnableCircuitBreaker
public class HystrixApplication {

    public static void main(String[] args) {
        SpringApplication.run(HystrixApplication.class, args);
    }

}

我们在启动类上面**@EnableCircuitBreaker**这个注解


@HystrixCommand(fallbackMethod = "queryFallback",
		commandKey = "query",
		groupKey = "query-one",
		commandProperties = {
		        // 信号量最大值配置
				@HystrixProperty(name = "execution.isolation.semaphore.maxConcurrentRequests",value = "20"),
				// 信号量隔离
				@HystrixProperty(name = "execution.isolation.strategy", value = "SEMAPHORE"),
				@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000000000")
		},
		// 线程池隔离(根据实际情况去选择信号量还是线程池隔离)
		threadPoolKey = "queryHystrixPool", threadPoolProperties = {
		@HystrixProperty(name = "coreSize", value = "20")
})
@Override
public List<Notes> query(String userId) {
	List<Notes> notesList = notesMapper.selectByUserId(userId);
	if (notesList.size() == 0) {
		throw new RuntimeException("没查询到相应数据");
	}
	return notesList;
}

/**
 * 服务降级方法配置
 * @return
 */
public List<Notes> queryFallback(){
	List<Notes> notesList = new ArrayList<>();
	Notes notes = new Notes();
	notes.setUserId(UUID.randomUUID().toString());
	notes.setDescription("queryFallback");
	notesList.add(notes);
	return notesList;
}

在需要降级方法上添加**@HystrixCommand** 注解就可以了。我感觉这东西太神奇了,还是去看看这东西大概实现逻辑。

3.Hystrix实现原理

3.1 @EnableCircuitBreaker


@Override
public String[] selectImports(AnnotationMetadata metadata) {
	if (!isEnabled()) {
		return new String[0];
	}
	AnnotationAttributes attributes = AnnotationAttributes.fromMap(
			metadata.getAnnotationAttributes(this.annotationClass.getName(), true));

	Assert.notNull(attributes, "No " + getSimpleName() + " attributes found. Is "
			+ metadata.getClassName() + " annotated with @" + getSimpleName() + "?");

	// 利用类似SPI方式去加载去收集EnablCircuitBreaker
	List<String> factories = new ArrayList<>(new LinkedHashSet<>(SpringFactoriesLoader
			.loadFactoryNames(this.annotationClass, this.beanClassLoader)));

	if (factories.isEmpty() && !hasDefaultFactory()) {
		throw new IllegalStateException("Annotation @" + getSimpleName()
				+ " found, but there are no implementations. Did you forget to include a starter?");
	}

	if (factories.size() > 1) {
		// there should only ever be one DiscoveryClient, but there might be more than
		// one factory
		log.warn("More than one implementation " + "of @" + getSimpleName()
				+ " (now relying on @Conditionals to pick one): " + factories);
	}

	return factories.toArray(new String[factories.size()]);
}

Spring-Cloud Hystrix学习心得_第1张图片
Spring-Cloud Hystrix学习心得_第2张图片
@EnableCircuitBreaker 注解功能主要扫描spring.factories文件,然后加载 HystrixCircuitBreakerConfiguration 对象实例

3.2 @HystrixCommandAspect

HystrixCircuitBreakerConfiguration 对象实例中创建 HystrixCommandAspect Bean实例,看到 Aspect 名字大家应该会联想到AOP切面。


@Aspect
public class HystrixCommandAspect {

    private static final Map<HystrixPointcutType, MetaHolderFactory> META_HOLDER_FACTORY_MAP;

    static {
        META_HOLDER_FACTORY_MAP = ImmutableMap.<HystrixPointcutType, MetaHolderFactory>builder()
                .put(HystrixPointcutType.COMMAND, new CommandMetaHolderFactory())
                .put(HystrixPointcutType.COLLAPSER, new CollapserMetaHolderFactory())
                .build();
    }

    @Pointcut("@annotation(com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand)")
    public void hystrixCommandAnnotationPointcut() {
    }

    @Pointcut("@annotation(com.netflix.hystrix.contrib.javanica.annotation.HystrixCollapser)")
    public void hystrixCollapserAnnotationPointcut() {
    }

    @Around("hystrixCommandAnnotationPointcut() || hystrixCollapserAnnotationPointcut()")
    public Object methodsAnnotatedWithHystrixCommand(final ProceedingJoinPoint joinPoint) throws Throwable {
        Method method = getMethodFromTarget(joinPoint);
        Validate.notNull(method, "failed to get method from joinPoint: %s", joinPoint);
        if (method.isAnnotationPresent(HystrixCommand.class) && method.isAnnotationPresent(HystrixCollapser.class)) {
            throw new IllegalStateException("method cannot be annotated with HystrixCommand and HystrixCollapser " +
                    "annotations at the same time");
        }
        MetaHolderFactory metaHolderFactory = META_HOLDER_FACTORY_MAP.get(HystrixPointcutType.of(method));
        MetaHolder metaHolder = metaHolderFactory.create(joinPoint);
        HystrixInvokable invokable = HystrixCommandFactory.getInstance().create(metaHolder);
        ExecutionType executionType = metaHolder.isCollapserAnnotationPresent() ?
                metaHolder.getCollapserExecutionType() : metaHolder.getExecutionType();

        Object result;
        try {
            if (!metaHolder.isObservable()) {
                result = CommandExecutor.execute(invokable, executionType, metaHolder);
            } else {
                result = executeObservable(invokable, executionType, metaHolder);
            }
        } catch (HystrixBadRequestException e) {
            throw e.getCause() != null ? e.getCause() : e;
        } catch (HystrixRuntimeException e) {
            throw hystrixRuntimeExceptionToThrowable(metaHolder, e);
        }
        return result;
    }
}

HystrixCommandAspect 实例拦截有**@HystrixCommand** 或 @HystrixCollapser 注解方法,生成相应的代理实例。我们这里只关注 @Around 环绕增强方法调用。(我们没必要把每一行代码读懂,只要把大概思路了解就行了)

3.2 CommandExecutor.execute()


public static Object execute(HystrixInvokable invokable, ExecutionType executionType, MetaHolder metaHolder) throws RuntimeException {

	switch (executionType) {
		case SYNCHRONOUS: {
		    // 我们看同步调用,代码转到定义
			return castToExecutable(invokable, executionType).execute();
		}
		case ASYNCHRONOUS: {
		    // 异步调用也就是用了Future这个东东,我们只看同步调用,了解大概流程和思路就行了
			HystrixExecutable executable = castToExecutable(invokable, executionType);
			if (metaHolder.hasFallbackMethodCommand()
					&& ExecutionType.ASYNCHRONOUS == metaHolder.getFallbackExecutionType()) {
				return new FutureDecorator(executable.queue());
			}
			return executable.queue();
		}
		case OBSERVABLE: {
			HystrixObservable observable = castToObservable(invokable);
			return ObservableExecutionMode.EAGER == metaHolder.getObservableExecutionMode() ? observable.observe() : observable.toObservable();
		}
		default:
			throw new RuntimeException("unsupported execution type: " + executionType);
	}
}


public R execute() {
    try {
        // queue() 方法转到定义
        return queue().get();
    } catch (Exception e) {
        throw Exceptions.sneakyThrow(decomposeException(e));
    }
}

Spring-Cloud Hystrix学习心得_第3张图片
我们去看toObeservable() 这个方法,转到定义又是一个内部类,直接看Observable.defer() 这段代码。
Spring-Cloud Hystrix学习心得_第4张图片

Observable<R> hystrixObservable =
       // 我们接下来看 applyHystrixSemantics 这个参数
       Observable.defer(applyHystrixSemantics)
               .map(wrapWithAllOnNextHooks);


final Func0<Observable<R>> applyHystrixSemantics = new Func0<Observable<R>>() {
     @Override
     public Observable<R> call() {
         if (commandState.get().equals(CommandState.UNSUBSCRIBED)) {
             return Observable.never();
         }
         // 转到定义
         return applyHystrixSemantics(_cmd);
     }
 };


// 这段代码有我们想找的东西
private Observable<R> applyHystrixSemantics(final AbstractCommand<R> _cmd) {
     executionHook.onStart(_cmd);

	 //1:判断熔断器状态,是否可以访问相应的接口信息
     if (circuitBreaker.attemptExecution()) {
         final TryableSemaphore executionSemaphore = getExecutionSemaphore();
         final AtomicBoolean semaphoreHasBeenReleased = new AtomicBoolean(false);
         final Action0 singleSemaphoreRelease = new Action0() {
             @Override
             public void call() {
                 if (semaphoreHasBeenReleased.compareAndSet(false, true)) {
                     executionSemaphore.release();
                 }
             }
         };

         final Action1<Throwable> markExceptionThrown = new Action1<Throwable>() {
             @Override
             public void call(Throwable t) {
                 eventNotifier.markEvent(HystrixEventType.EXCEPTION_THROWN, commandKey);
             }
         };

         if (executionSemaphore.tryAcquire()) {
             try {
                 /* used to track userThreadExecutionTime */
                 executionResult = executionResult.setInvocationStartTime(System.currentTimeMillis());
                 return executeCommandAndObserve(_cmd)
                         .doOnError(markExceptionThrown)
                         .doOnTerminate(singleSemaphoreRelease)
                         .doOnUnsubscribe(singleSemaphoreRelease);
             } catch (RuntimeException e) {
                 return Observable.error(e);
             }
         } else {
             return handleSemaphoreRejectionViaFallback();
         }
     } else {
         // 如果允许访问接口,直接走服务降级
         return handleShortCircuitViaFallback();
     }
 }

Spring-Cloud Hystrix学习心得_第5张图片

3.3 circuitBreaker.attemptExecution()


private boolean isAfterSleepWindow() {
     final long circuitOpenTime = circuitOpened.get();
     final long currentTime = System.currentTimeMillis();
     final long sleepWindowTime = properties.circuitBreakerSleepWindowInMilliseconds().get();
     return currentTime > circuitOpenTime + sleepWindowTime;
 }

 @Override
 public boolean attemptExecution() {
     //1:如果断路器处于开启状态,直接返回false
     if (properties.circuitBreakerForceOpen().get()) {
         return false;
     }
     //2:断路器关闭,返回true,表示接口允许被调用
     if (properties.circuitBreakerForceClosed().get()) {
         return true;
     }
     if (circuitOpened.get() == -1) {
         return true;
     } else {
     	 // 判断断路器最后开启时间距离现在,有没有超过那个滑动窗口时间 ,如果超过也是可以允许访问的(我们经常说熔断器半开状态)
     	 // 这个是不是熔断器的半开状态,是不是兄弟们
         if (isAfterSleepWindow()) {
             if (status.compareAndSet(Status.OPEN, Status.HALF_OPEN)) {
                 //only the first request after sleep window should execute
                 return true;
             } else {
                 return false;
             }
         } else {
             return false;
         }
     }
 }

Spring-Cloud Hystrix学习心得_第6张图片

3.4 getExecutionSemaphore()


protected TryableSemaphore getExecutionSemaphore() {
      // 1:判断使用哪种隔离级别
	  if (properties.executionIsolationStrategy().get() == ExecutionIsolationStrategy.SEMAPHORE) {
	      if (executionSemaphoreOverride == null) {
	          TryableSemaphore _s = executionSemaphorePerCircuit.get(commandKey.name());
	          if (_s == null) {
	              // 使用了信号量隔离级别, properties.executionIsolationSemaphoreMaxConcurrentRequests() 这个自己转到定义去看,这个参数值就是我们自己配置的 ,最大请求数值就是用了一个线程安全AtomicInteger来保存的
	              executionSemaphorePerCircuit.putIfAbsent(commandKey.name(), new TryableSemaphoreActual(properties.executionIsolationSemaphoreMaxConcurrentRequests()));

	              return executionSemaphorePerCircuit.get(commandKey.name());
	          } else {
	              return _s;
	          }
	      } else {
	          return executionSemaphoreOverride;
	      }
	  } else {
	      // 这里就使用了线程池隔离级别,转过去看看
	      return TryableSemaphoreNoOp.DEFAULT;
	  }
}

Spring-Cloud Hystrix学习心得_第7张图片


@Override
public boolean tryAcquire() {
    int currentCount = count.incrementAndGet();
    //1:判断当前请求数是否大于最大限制数(就是我们配置文件配的那个参数值,如果大于表示直接返回false)
    if (currentCount > numberOfPermits.get()) {
        count.decrementAndGet();
        // 返回false 代码执行执行handleSemaphoreRejectionViaFallback() 拒绝执行这段了
        return false;
    } else {
        // 2:在限制范围内,允许继续执行
        return true;
    }
}

下面这段代码很深,我直接截图了,了解一下大概流程就可以了。
Spring-Cloud Hystrix学习心得_第8张图片
Spring-Cloud Hystrix学习心得_第9张图片
Spring-Cloud Hystrix学习心得_第10张图片
Spring-Cloud Hystrix学习心得_第11张图片
Spring-Cloud Hystrix学习心得_第12张图片
Spring-Cloud Hystrix学习心得_第13张图片
看到最后你会发现 MethodExecutionAction 用反射方式去调用目标方法。目标方法调用失败了就走服务降级处理,降级方法也是通过反射方式去调用的。


private Object execute(Object o, Method m, Object... args) throws CommandActionExecutionException {
      Object result = null;
      try {
          m.setAccessible(true); // suppress Java language access
          if (isCompileWeaving() && metaHolder.getAjcMethod() != null) {
              result = invokeAjcMethod(metaHolder.getAjcMethod(), o, metaHolder, args);
          } else {
              result = m.invoke(o, args);
          }
      } catch (IllegalAccessException e) {
          propagateCause(e);
      } catch (InvocationTargetException e) {
          propagateCause(e);
      }
      return result;
  }

本人也是一个小白,本人也只了解一个Hystrix大概流程。但希望对大家有帮助。

你可能感兴趣的:(SpringCloud)