跳到主要内容

12、SpringCloud Alibaba - openFeign整合Sentinel实现服务熔断

前言

熔断机制是微服务中的一种链路保护机制,当链路上的某个微服务不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,响应快速失败,防止雪崩效应。

一、配置

1、 引入依赖;

<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-openfeign</artifactId>
			<version>3.0.2</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-loadbalancer -->
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-loadbalancer</artifactId>
			<version>3.0.2</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-starter-alibaba-sentinel -->
		<dependency>
			<groupId>com.alibaba.cloud</groupId>
			<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
			<version>2021.1</version>
		</dependency>

2、 打开circuitbreaker开关;

在配置文件中添加配置项,打开 feign 对 circuitbreaker 的支持

feign.circuitbreaker.enabled=true

3、 fallback实现类;

@Component
public class ServerApiFallBack implements ServerApi{
   
     
    @Override
    public Map test(String id) {
   
     
        ImmutableMap map = ImmutableMap.of("code", 500, "reason", "服务不可用");
        return map;
    }
}

4、 FeignClient接口;

@FeignClient(value = "server",fallback = ServerApiFallBack.class)
@Component
public interface ServerApi {
   
     

    @GetMapping("/server/test/{id}")
    Map test(@PathVariable String id);
}

二、FeignCircuitBreakerInvocationHandler 原理

1、 feign.circuitbreaker.enabled的作用;

1、CircuitBreakerPresentFeignTargeterConfiguration 配置类里定义了Targeter 的实现类FeignCircuitBreakerTargeter ,配置项为true时该bean生效,FeignCircuitBreakerTargeter 用于代替之前的 DefaultTargeter

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(CircuitBreaker.class)
	@ConditionalOnProperty("feign.circuitbreaker.enabled")
	protected static class CircuitBreakerPresentFeignTargeterConfiguration {
   
     
		...
		@Bean
		@ConditionalOnMissingBean
		@ConditionalOnBean(CircuitBreakerFactory.class)
		public Targeter circuitBreakerFeignTargeter(CircuitBreakerFactory circuitBreakerFactory) {
   
     
			return new FeignCircuitBreakerTargeter(circuitBreakerFactory);
		}

	}

2、CircuitBreakerPresentFeignBuilderConfiguration 配置类里定义了 Feign.Builder 的实现类FeignCircuitBreaker.builder(),配置项为true时该bean生效,FeignCircuitBreaker.builder() 用于代替之前的 Feign.builder()

2、 @FeignClient创建代理对象;

1、代理对象的创建过程从 FeignClientFactoryBean 的 getObject( ) 开始,之前已经分析过,现在省略不太重要的代码

@Override
	public Object getObject() {
   
     
		return getTarget();
	}

<T> T getTarget() {
   
     
		//这里获取到 FeignCircuitBreaker.Builder
		Feign.Builder builder = feign(context);
			
			return (T) loadBalance(builder, context, new HardCodedTarget<>(type, name, url));
		}
		
	}

protected <T> T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget<T> target) {
   
     
			//获取到 FeignCircuitBreakerTargeter
			Targeter targeter = get(context, Targeter.class);
			// 调用 target()
			return targeter.target(this, builder, context, target);
	}

2、FeignCircuitBreakerTargeter 的 target( )

	@Override
	public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
			Target.HardCodedTarget<T> target) {
   
     
		if (!(feign instanceof FeignCircuitBreaker.Builder)) {
   
     
			//不是  FeignCircuitBreaker.Builder 类型,走这里
			return feign.target(target);
		}
		FeignCircuitBreaker.Builder builder = (FeignCircuitBreaker.Builder) feign;
		String name = !StringUtils.hasText(factory.getContextId()) ? factory.getName() : factory.getContextId();
		//fallback 
		Class<?> fallback = factory.getFallback();
		if (fallback != void.class) {
   
     
			return targetWithFallback(name, context, target, builder, fallback);
		}
		//fallbackFactory 
		Class<?> fallbackFactory = factory.getFallbackFactory();
		if (fallbackFactory != void.class) {
   
     
			return targetWithFallbackFactory(name, context, target, builder, fallbackFactory);
		}
		//没有熔断功能
		return builder(name, builder).target(target);
	}

3、targetWithFallback( )

private <T> T targetWithFallback(String feignClientName, FeignContext context, Target.HardCodedTarget<T> target,
			FeignCircuitBreaker.Builder builder, Class<?> fallback) {
   
     
		//获取 fallback 实例
		T fallbackInstance = getFromContext("fallback", feignClientName, context, fallback, target.type());
		return builder(feignClientName, builder).target(target, fallbackInstance);
	}

4、target( )

public <T> T target(Target<T> target, T fallback) {
   
     
			return build(fallback != null ? new FallbackFactory.Default<T>(fallback) : null).newInstance(target);
		}

5、build( )

public Feign build(final FallbackFactory<?> nullableFallbackFactory) {
   
     
			//创建 InvocationHandlerFactory,用于创建 FeignCircuitBreakerInvocationHandler
			super.invocationHandlerFactory(new InvocationHandlerFactory() {
   
     
				@Override
				public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
   
     
					return new FeignCircuitBreakerInvocationHandler(circuitBreakerFactory, feignClientName, target,
							dispatch, nullableFallbackFactory);
				}
			});
			return super.build();
		}

3、 FeignCircuitBreakerInvocationHandler;

1、调用接口方法时,会进入到代理的invoke( ) 方法

public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
   
     
		// early exit if the invoked method is from java.lang.Object
		// code is the same as ReflectiveFeign.FeignInvocationHandler
		if ("equals".equals(method.getName())) {
   
     
			try {
   
     
				Object otherHandler = args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
				return equals(otherHandler);
			}
			catch (IllegalArgumentException e) {
   
     
				return false;
			}
		}
		else if ("hashCode".equals(method.getName())) {
   
     
			return hashCode();
		}
		else if ("toString".equals(method.getName())) {
   
     
			return toString();
		}
		String circuitName = this.feignClientName + "_" + method.getName();
		CircuitBreaker circuitBreaker = this.factory.create(circuitName);
		//调用服务的逻辑
		Supplier<Object> supplier = asSupplier(method, args);
		if (this.nullableFallbackFactory != null) {
   
     
			//服务熔断的逻辑
			Function<Throwable, Object> fallbackFunction = throwable -> {
   
     
				Object fallback = this.nullableFallbackFactory.create(throwable);
				try {
   
     
					return this.fallbackMethodMap.get(method).invoke(fallback, args);
				}
				catch (Exception e) {
   
     
					throw new IllegalStateException(e);
				}
			};
			//执行后续逻辑
			return circuitBreaker.run(supplier, fallbackFunction);
		}
		return circuitBreaker.run(supplier);
	}
	

2、asSupplier( )

	//调用服务的逻辑
	private Supplier<Object> asSupplier(final Method method, final Object[] args) {
   
     
		return () -> {
   
     
			try {
   
     
				return this.dispatch.get(method).invoke(args);
			}
			catch (RuntimeException throwable) {
   
     
				throw throwable;
			}
			catch (Throwable throwable) {
   
     
				throw new RuntimeException(throwable);
			}
		};
	}

3、circuitBreaker.run( )

先调用服务端接口,如果出现异常,则执行服务熔断的逻辑

public <T> T run(Supplier<T> toRun, Function<Throwable, T> fallback) {
   
     
		Entry entry = null;
		try {
   
     
			entry = SphU.entry(resourceName, entryType);
			// If the SphU.entry() does not throw BlockException, it means that the
			// request can pass.
			//调用服务端接口
			return toRun.get();
		}
		catch (BlockException ex) {
   
     
			// SphU.entry() may throw BlockException which indicates that
			// the request was rejected (flow control or circuit breaking triggered).
			// So it should not be counted as the business exception.
			//异常时 调用 fallback
			return fallback.apply(ex);
		}
		catch (Exception ex) {
   
     
			// For other kinds of exceptions, we'll trace the exception count via
			// Tracer.trace(ex).
			Tracer.trace(ex);
			//异常时 调用 fallback
			return fallback.apply(ex);
		}
		finally {
   
     
			// Guarantee the invocation has been completed.
			if (entry != null) {
   
     
				entry.exit();
			}
		}
	}

三、SentinelInvocationHandler 原理

跟上面的区别主要是 SentinelFeign.builder() 和 SentinelInvocationHandler 两个类

1、 打开setinel开关;

feign.sentinel.enabled=true

2、 feign.sentinel.enabled的作用;

SentinelFeignAutoConfiguration 配置类里定义了Feign.Builder 的实现类 SentinelFeign.builder(),配置项为true时该bean生效

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({
   
      SphU.class, Feign.class })
public class SentinelFeignAutoConfiguration {
   
     

	@Bean
	@Scope("prototype")
	@ConditionalOnMissingBean
	@ConditionalOnProperty(name = "feign.sentinel.enabled")
	public Feign.Builder feignSentinelBuilder() {
   
     
		return SentinelFeign.builder();
	}

}

3、 SentinelFeign.builder()的build()方法;

主要作用是: 创建 invocationHandlerFactory,重写create( ) 方法;invocationHandlerFactory 用于创建 SentinelInvocationHandler ,代替前面的 FeignCircuitBreakerInvocationHandler。

 	public Feign build() {
   
     
            super.invocationHandlerFactory(new InvocationHandlerFactory() {
   
     
                public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
   
     
                    GenericApplicationContext gctx = (GenericApplicationContext)Builder.this.applicationContext;
                    BeanDefinition def = gctx.getBeanDefinition(target.type().getName());
                    FeignClientFactoryBean feignClientFactoryBean = (FeignClientFactoryBean)def.getAttribute("feignClientsRegistrarFactoryBean");
                    //从BeanDefinition 里获取到 fallback、fallbackFactory 
                    Class fallback = feignClientFactoryBean.getFallback();
                    Class fallbackFactory = feignClientFactoryBean.getFallbackFactory();
                    String beanName = feignClientFactoryBean.getContextId();
                    if (!StringUtils.hasText(beanName)) {
   
     
                        beanName = feignClientFactoryBean.getName();
                    }

                    if (Void.TYPE != fallback) {
   
     
                    	//创建 fallback 实例
                        Object fallbackInstance = this.getFromContext(beanName, "fallback", fallback, target.type());
                        //创建 SentinelInvocationHandler
                        return new SentinelInvocationHandler(target, dispatch, new org.springframework.cloud.openfeign.FallbackFactory.Default(fallbackInstance));
                    } else if (Void.TYPE != fallbackFactory) {
   
     
                        FallbackFactory fallbackFactoryInstance = (FallbackFactory)this.getFromContext(beanName, "fallbackFactory", fallbackFactory, FallbackFactory.class);
                        return new SentinelInvocationHandler(target, dispatch, fallbackFactoryInstance);
                    } else {
   
     
                        return new SentinelInvocationHandler(target, dispatch);
                    }
                }

                private Object getFromContext(String name, String type, Class fallbackType, Class targetType) {
   
     
                    Object fallbackInstance = Builder.this.feignContext.getInstance(name, fallbackType);
                    if (fallbackInstance == null) {
   
     
                        throw new IllegalStateException(String.format("No %s instance of type %s found for feign client %s", type, fallbackType, name));
                    } else if (!targetType.isAssignableFrom(fallbackType)) {
   
     
                        throw new IllegalStateException(String.format("Incompatible %s instance. Fallback/fallbackFactory of type %s is not assignable to %s for feign client %s", type, fallbackType, targetType, name));
                    } else {
   
     
                        return fallbackInstance;
                    }
                }
            });
            super.contract(new SentinelContractHolder(this.contract));
            return super.build();
        }

4、 SentinelInvocationHandler;

@Override
	public Object invoke(final Object proxy, final Method method, final Object[] args)
			throws Throwable {
   
     
		if ("equals".equals(method.getName())) {
   
     
			try {
   
     
				Object otherHandler = args.length > 0 && args[0] != null
						? Proxy.getInvocationHandler(args[0])
						: null;
				return equals(otherHandler);
			}
			catch (IllegalArgumentException e) {
   
     
				return false;
			}
		}
		else if ("hashCode".equals(method.getName())) {
   
     
			return hashCode();
		}
		else if ("toString".equals(method.getName())) {
   
     
			return toString();
		}

		Object result;
		MethodHandler methodHandler = this.dispatch.get(method);
		// only handle by HardCodedTarget
		if (target instanceof Target.HardCodedTarget) {
   
     
			Target.HardCodedTarget hardCodedTarget = (Target.HardCodedTarget) target;
			MethodMetadata methodMetadata = SentinelContractHolder.METADATA_MAP
					.get(hardCodedTarget.type().getName()
							+ Feign.configKey(hardCodedTarget.type(), method));
			// resource default is HttpMethod:protocol://url
			if (methodMetadata == null) {
   
     
				result = methodHandler.invoke(args);
			}
			else {
   
     
				String resourceName = methodMetadata.template().method().toUpperCase()
						+ ":" + hardCodedTarget.url() + methodMetadata.template().path();
				Entry entry = null;
				try {
   
     
					ContextUtil.enter(resourceName);
					entry = SphU.entry(resourceName, EntryType.OUT, 1, args);
					//调用服务端接口
					result = methodHandler.invoke(args);
				}
				catch (Throwable ex) {
   
     
					// fallback handle
					if (!BlockException.isBlockException(ex)) {
   
     
						Tracer.trace(ex);
					}
					if (fallbackFactory != null) {
   
     
						try {
   
     
							//异常时 调用熔断逻辑
							Object fallbackResult = fallbackMethodMap.get(method)
									.invoke(fallbackFactory.create(ex), args);
							return fallbackResult;
						}
						catch (IllegalAccessException e) {
   
     
							// shouldn't happen as method is public due to being an
							// interface
							throw new AssertionError(e);
						}
						catch (InvocationTargetException e) {
   
     
							throw new AssertionError(e.getCause());
						}
					}
					else {
   
     
						// throw exception if fallbackFactory is null
						throw ex;
					}
				}
				finally {
   
     
					if (entry != null) {
   
     
						entry.exit(1, args);
					}
					ContextUtil.exit();
				}
			}
		}
		else {
   
     
			// other target type using default strategy
			result = methodHandler.invoke(args);
		}

		return result;
	}

四、总结

涉及到的两个配置项 :

feign.sentinel.enabled=true

feign.circuitbreaker.enabled=true

涉及到的重要的类:

FeignCircuitBreakerTargeter 代替默认的 DefaultTargeter

FeignCircuitBreaker.builder() 代替默认的 Feign.builder()

FeignCircuitBreakerInvocationHandler 代替默认的 FeignInvocationHandler

SentinelInvocationHandler 代替默认的 FeignInvocationHandler