Spring Cloud usa Sentinel en lugar de Hystrix como el procesamiento unificado de Fallback para el fusible de Feign

La idea de este artículo se basa en el uso inicial de Feign.Si aún tiene una base cero, se recomienda agregar algunos conocimientos de Spring Cloud Feign + Hystrix. Spring Cloud Feign usa Hystrix como fusible por defecto. A medida que la familia Spring Cloud Alibaba se fortalece, la versión Sentinel-0.9.0 también es compatible con Gateway. Hoy echamos un vistazo a cómo simplificar el código, usar Feign, cómo usar Sentinel en lugar de Hystrix y simplificar el código, administración unificada de Fallback.

  • Fuente del problema:

Los escenarios de uso de Feign son los siguientes:

@FeignClient(value = "user-service", fallbackFactory = UserFeignFallbackFactory.class)
public interface RemoteUserFeign {
	@GetMapping("/user/{id}")
	User findById(@PathVariable("id") int id);
}
@Component
public class UserFeignFallbackFactory implements FallbackFactory<RemoteUserFeign> {
	@Override
	public RemoteUserFeign create(Throwable cause) {
		RemoteUserFeignImpl feign = new RemoteUserFeignImpl();
		feign.setCause(cause);
		return feign;
	}
}
@Slf4j
public class RemoteUserFeignImpl implements RemoteUserFeign {
	@Setter
	private Throwable cause;
	@Override
	public User findById(int id) {
		log.error("feign 查询用户信息失败 id:{},throw:{}", id, cause);
		return null;
	}
}

Cuando usamos Feign, la anotación @FeignClient utilizada debe establecerse en los parámetros fallbackFactory cada vez. Como resultado, habrá mucho código redundante en el proyecto. Entonces, ¿podemos tener un Fallback predeterminado personalizado para manejar estas mismas cosas?

  • identificar el problema

Primero, usamos Sentinel e introducimos paquetes relacionados con Alibaba-Sentinel

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    <version>0.9.0.RELEASE</version>
</dependency>

Mirando el código fuente directamente, no hay mucho código relacionado con Feign en el paquete spring-cloud-starter-alibaba-sentinel. Lo principal es SentinelFeign implementado por Hystrix.

Luego, después de los puntos de interrupción de Fingir y la depuración del código fuente, ubico la parte del código central y tomo capturas de pantalla de las clases relacionadas y los números de línea.

Se puede ver en el código fuente de SentinelFeign que cuando no se establece fallbackFactory, FallbackFactory no se pasa al método de construcción SentinelInvocationHandler. Entonces surge nuestra solución:

  1. Escribir una FallbackFactory pública
  2. Al reescribir SentinelFeign para convertir fallbackFactory en void.class, pasamos nuestra propia instancia pública de FallbackFactory
  • problema resuelto

  1. Primero escribe el procesador de respaldo real
    @Slf4j
    @AllArgsConstructor
    public class FunFeignFallback<T> implements MethodInterceptor {
    	private final Class<T> targetType;
    	private final String targetName;
    	private final Throwable cause;
    
    	@Nullable
    	@Override
    	public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    		String errorMessage = cause.getMessage();
    		log.error("FunFeignFallback:[{}.{}] serviceId:[{}] message:[{}]", targetType.getName(), method.getName(), targetName, errorMessage);
    		// 非 FeignException,直接返回
    		if (!(cause instanceof FeignException)) {
                //此处只是示例,具体可以返回带有业务错误数据的对象
    			return null;
    		}
    		FeignException exception = (FeignException) cause;
            //此处只是示例,具体可以返回带有业务错误数据的对象
    		return JsonUtils.toJson(exception.content());
    	}
    
    	@Override
    	public boolean equals(Object o) {
    		if (this == o) {
    			return true;
    		}
    		if (o == null || getClass() != o.getClass()) {
    			return false;
    		}
    		FunFeignFallback<?> that = (FunFeignFallback<?>) o;
    		return targetType.equals(that.targetType);
    	}
    
    	@Override
    	public int hashCode() {
    		return Objects.hash(targetType);
    	}
    }
  2. Entonces escribe FallbackFactory
    @AllArgsConstructor
    public class FunFallbackFactory<T> implements FallbackFactory<T> {
    	private final Target<T> target;
    
    	@Override
    	@SuppressWarnings("unchecked")
    	public T create(Throwable cause) {
    		final Class<T> targetType = target.type();
    		final String targetName = target.name();
    		Enhancer enhancer = new Enhancer();
    		enhancer.setSuperclass(targetType);
    		enhancer.setUseCache(true);
    		enhancer.setCallback(new FunFeignFallback<>(targetType, targetName, cause));
    		return (T) enhancer.create();
    	}
    }
  3. Vuelva a implementar SentinelFeign. Como no tenemos acceso a SentinelInvocationHandler, tenemos que crear el paquete
    org.springframework.cloud.alibaba.sentinel.feign en nuestro proyecto y poner el nuevo SentinelFeign en este paquete.
    public class FunSentinelFeign {
    	public static FunSentinelFeign.Builder builder() {
    		return new FunSentinelFeign.Builder();
    	}
    	public static final class Builder extends Feign.Builder
    			implements ApplicationContextAware {
    		private Contract contract = new Contract.Default();
    		private ApplicationContext applicationContext;
    		private FeignContext feignContext;
    		@Override
    		public Feign.Builder invocationHandlerFactory(
    				InvocationHandlerFactory invocationHandlerFactory) {
    			throw new UnsupportedOperationException();
    		}
    		@Override
    		public FunSentinelFeign.Builder contract(Contract contract) {
    			this.contract = contract;
    			return this;
    		}
    		@Override
    		public Feign build() {
    			super.invocationHandlerFactory(new InvocationHandlerFactory() {
    				@Override
    				public InvocationHandler create(Target target,
    												Map<Method, MethodHandler> dispatch) {
    					Object feignClientFactoryBean = FunSentinelFeign.Builder.this.applicationContext
    							.getBean("&" + target.type().getName());
    
    					Class fallback = (Class) getFieldValue(feignClientFactoryBean,
    							"fallback");
    					Class fallbackFactory = (Class) getFieldValue(feignClientFactoryBean,
    							"fallbackFactory");
    					String name = (String) getFieldValue(feignClientFactoryBean, "name");
    
    					Object fallbackInstance;
    					FallbackFactory fallbackFactoryInstance;
    					// check fallback and fallbackFactory properties
    					if (void.class != fallback) {
    						fallbackInstance = getFromContext(name, "fallback", fallback,
    								target.type());
    						return new SentinelInvocationHandler(target, dispatch,
    								new FallbackFactory.Default(fallbackInstance));
    					}
    					if (void.class != fallbackFactory) {
    						fallbackFactoryInstance = (FallbackFactory) getFromContext(name,
    								"fallbackFactory", fallbackFactory,
    								FallbackFactory.class);
    						return new SentinelInvocationHandler(target, dispatch,
    								fallbackFactoryInstance);
    					}
    					// 默认的 fallbackFactory
    					FunFallbackFactory funFallbackFactory = new FunFallbackFactory(target);
    					return new SentinelInvocationHandler(target, dispatch, funFallbackFactory);
    				}
    
    				private Object getFromContext(String name, String type,
    											  Class fallbackType, Class targetType) {
    					Object fallbackInstance = 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));
    					}
    
    					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));
    					}
    					return fallbackInstance;
    				}
    			});
    			super.contract(new SentinelContractHolder(contract));
    			return super.build();
    		}
    		private Object getFieldValue(Object instance, String fieldName) {
    			Field field = ReflectionUtils.findField(instance.getClass(), fieldName);
    			field.setAccessible(true);
    			try {
    				return field.get(instance);
    			} catch (IllegalAccessException e) {
    				// ignore
    			}
    			return null;
    		}
    		@Override
    		public void setApplicationContext(ApplicationContext applicationContext)
    				throws BeansException {
    			this.applicationContext = applicationContext;
    			feignContext = this.applicationContext.getBean(FeignContext.class);
    		}
    	}
    }
    

     

  4. Finalmente inyecte nuestro SentinelFeign Bean
    @Configuration
    public class FunFeignFallbackConfiguration {
    	@Bean
    	@Scope("prototype")
    	@ConditionalOnClass({SphU.class, Feign.class})
    	@ConditionalOnProperty(name = "feign.sentinel.enabled")
    	@Primary
    	public Feign.Builder feignSentinelBuilder() {
    		return FunSentinelFeign.builder();
    	}
    }

     

Recuerde que cuando usa Sentinel, debe configurar feign.sentinel.enabled = true en el archivo de configuración y comentar feign.hystrix.enabled = true.

@FeignClient(value = "user-service")
public interface RemoteUserFeign {
	@GetMapping("/user/{id}")
	User findById(@PathVariable("id") int id);
}

Ya terminaste aquí. Para escribir Feign más tarde, simplemente escriba el siguiente código. En cuanto a algunos especiales, también puede personalizar el fallbackFactory y agregarlo en las anotaciones.

Supongo que te gusta

Origin blog.csdn.net/ttzommed/article/details/90669320
Recomendado
Clasificación