二. spring cloud 集成 sentinel做限流

时间:2021-7-3 作者:qvyue

1. spring cloud . spring boot . alibaba . sentinel 版本

 2.3.5.RELEASEHoxton.SR82.2.3.RELEASE
 sentinel-dashboard  1.8.0

2. 项目依赖

       com.alibaba.cloudspring-cloud-starter-alibaba-sentinel

3. sentinel 官网地址

https://github.com/alibaba/Sentinel

4. 配置微服务间统一限流降级处理

 1. 配置服务间调用开关及发生降级后异常处理
@AutoConfigureBefore(SentinelFeignAutoConfiguration.class)
public class SentinelAutoConfiguration {

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

   @Bean
   @ConditionalOnMissingBean
   public BlockExceptionHandler blockExceptionHandler() {
       return new xxxxxUrlBlockHandler();
   }
   
}

2.  重写 {@link com.alibaba.cloud.sentinel.feign.SentinelFeign} 支持自动降级注入

public final class xxxxxSentinelFeign {

   private xxxxxSentinelFeign() {

   }

   public static xxxxSentinelFeign.Builder builder() {
       return new xxxxxSentinelFeign.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 xxxxxSentinelFeign.Builder contract(Contract contract) {
           this.contract = contract;
           return this;
       }

       @Override
       public Feign build() {
           super.invocationHandlerFactory(new InvocationHandlerFactory() {
               @Override
               public InvocationHandler create(Target target, Map dispatch) {
                   // using reflect get fallback and fallbackFactory properties from
                   // FeignClientFactoryBean because FeignClientFactoryBean is a package
                   // level class, we can not use it in our package
                   Object feignClientFactoryBean = xxxxSentinelFeign.Builder.this.applicationContext
                           .getBean("&" + target.type().getName());

                   Class fallback = (Class) getFieldValue(feignClientFactoryBean, "fallback");
                   Class fallbackFactory = (Class) getFieldValue(feignClientFactoryBean, "fallbackFactory");
                   String beanName = (String) getFieldValue(feignClientFactoryBean, "contextId");
                   if (!StringUtils.hasText(beanName)) {
                       beanName = (String) getFieldValue(feignClientFactoryBean, "name");
                   }

                   Object fallbackInstance;
                   FallbackFactory fallbackFactoryInstance;
                   // check fallback and fallbackFactory properties
                   if (void.class != fallback) {
                       fallbackInstance = getFromContext(beanName, "fallback", fallback, target.type());
                       return new xxxxSentinelInvocationHandler(target, dispatch,
                               new FallbackFactory.Default(fallbackInstance));
                   }
                   if (void.class != fallbackFactory) {
                       fallbackFactoryInstance = (FallbackFactory) getFromContext(beanName, "fallbackFactory",
                               fallbackFactory, FallbackFactory.class);
                       return new xxxxSentinelInvocationHandler(target, dispatch, fallbackFactoryInstance);
                   }
                   return new xxxxxSentinelInvocationHandler(target, dispatch);
               }

               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);
       }

   }

}

3. 降级后异常处理
public class xxxxxSentinelInvocationHandler implements InvocationHandler {

   public static final String EQUALS = "equals";

   public static final String HASH_CODE = "hashCode";

   public static final String TO_STRING = "toString";

   private final Target> target;

   private final Map dispatch;

   private FallbackFactory fallbackFactory;

   private Map fallbackMethodMap;

   xxxxxxSentinelInvocationHandler(Target> target, Map dispatch,
           FallbackFactory fallbackFactory) {
       this.target = checkNotNull(target, "target");
       this.dispatch = checkNotNull(dispatch, "dispatch");
       this.fallbackFactory = fallbackFactory;
       this.fallbackMethodMap = toFallbackMethod(dispatch);
   }

   xxxxxxSentinelInvocationHandler(Target> target, Map dispatch) {
       this.target = checkNotNull(target, "target");
       this.dispatch = checkNotNull(dispatch, "dispatch");
   }

   @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 (HASH_CODE.equals(method.getName())) {
           return hashCode();
       }
       else if (TO_STRING.equals(method.getName())) {
           return toString();
       }

       Object result;
       InvocationHandlerFactory.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 {
                       // 若是XResponse类型 执行自动降级返回 XResponse

                       if (XResponse.class == method.getReturnType()) {
                           log.error("feign 服务间调用异常", ex);
                           //不能返回 XResponse , 涉及业务处理。 直接抛出异常,在全局异常中处理
//                          return  XResponse.builder()
//                                  .data(Boolean.FALSE)
//                                  .msg("服务请求超时,请稍后重试")
//                                  .httpStatus(HttpStatus.SERVICE_UNAVAILABLE)
//                                  .build();
                           throw  new FeginServierException("系统繁忙,请稍后重试");
                       }else {
                           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;
   }

   @Override
   public boolean equals(Object obj) {
       if (obj instanceof SentinelInvocationHandler) {
           xxxxSentinelInvocationHandler other = (xxxxSentinelInvocationHandler) obj;
           return target.equals(other.target);
       }
       return false;
   }

   @Override
   public int hashCode() {
       return target.hashCode();
   }

   @Override
   public String toString() {
       return target.toString();
   }

   static Map toFallbackMethod(Map dispatch) {
       Map result = new LinkedHashMap();
       for (Method method : dispatch.keySet()) {
           method.setAccessible(true);
           result.put(method, method);
       }
       return result;
   }

}


4. 服务间流控降级自定义返回
public class xxxxxUrlBlockHandler implements BlockExceptionHandler {

   @Override
   public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
       log.error("sentinel 降级 资源名称{}", e.getRule().getResource(), e);

       response.setContentType(ContentType.JSON.toString());
       response.setStatus(HttpStatus.SERVICE_UNAVAILABLE.value());
       response.setCharacterEncoding("UTF-8");
       response.getWriter().print(JSONUtil.toJsonStr(XResponse.builder()
               .data(Boolean.FALSE)
               .msg("服务请求超时,请稍后重试")
               .httpStatus(HttpStatus.SERVICE_UNAVAILABLE)
               .build()
               .toResponseEntity()));
   }

}

5. 网关集成流控

#######将上面4步操作集成公共的依赖, 网关与各个微服务集成 xxx-common-sentinel##########

  
        com.alibaba.cloudspring-cloud-alibaba-sentinel-gatewaycom.xxxxxxxx-common-sentinel1.0.0
  • 5.1 网关配置限流降级
@Configuration
public class GatewayConfiguration {

   @PostConstruct
   public void doInit() {
       GatewayCallbackManager.setBlockHandler(new GateWayBlockRequestHandler());
   }

}

  • 5.2 服务返回 一般返回自定义的返回体 sentinel 不能根据自定义的返回体进行异常降级 需自已处理
@Component
public class GatewayBlockFilter implements GlobalFilter, Ordered {

 
   @Override
   public int getOrder() {
       return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1;
   }

   @Override
   public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
       ServerHttpResponse originalResponse = exchange.getResponse();
       ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {

           @Override
           public Mono writeWith(Publisher extends DataBuffer> body) {
               // get match route id
               Route route = (Route) exchange.getAttributes().get(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
               String id = route.getId();
               // 500 error -> degrade
               if (originalResponse.getRawStatusCode() == HttpStatus.INTERNAL_SERVER_ERROR.value() ) {
                   Entry entry = null;
                   try {
                       // 0 token
                       entry = SphU.entry(id, EntryType.OUT);
                       Tracer.trace(new RuntimeException("INTERNAL_SERVER_ERROR"));
                   } catch (BlockException e) {
                       log.error(e.getMessage(), e);
                   } finally {
                       if (entry != null){
                           entry.close();
                       }
                   }
               }
               return super.writeWith(body);
           }
       };
       // replace response with decorator
       return chain.filter(exchange.mutate().response(decoratedResponse).build());
   }
}

6. sentinel 规则持久化 – 控制台(1.8.0)

  • 6.1 在sentinel 官网下最新控制台源码

    二. spring cloud 集成 sentinel做限流
    image.png
  • 6.2 将控制台源码拿到自己项目中部署

    二. spring cloud 集成 sentinel做限流
    image.png
  • 6.3 修改持久化规则,在源码的test 目录中找到 nacos , 将nacos 目录拷贝到 源码的rule 目录下,完善流控降级的规则 。

    二. spring cloud 集成 sentinel做限流
    image.png

    二. spring cloud 集成 sentinel做限流
    image.png
  • 6.4 在自己微服务中添加sentinel对nacos 依赖 ( 可以添加到上面第四步 xxx-common-sentinel 公共的依赖里面 )

       com.alibaba.cspsentinel-datasource-nacos
  • 6.5 在网关配置里面将 sentinel 拉去配置的地址配置 (nacos)

    二. spring cloud 集成 sentinel做限流
    image.png
  • 6.6 在所有服务依赖的配置文件中添加 其他规则配置拉取地址, 如在 application-dev.yml中

    二. spring cloud 集成 sentinel做限流
    image.png
  • 6.7 默认网关启动在sentinel 控制台会跟普通服务显示一样 需要添加启动项

@SpringCloudApplication
public class XXXGatewayApplication {

   public static void main(String[] args) {
       System.setProperty("csp.sentinel.app.type", "1");
       SpringApplication.run(XXXGatewayApplication.class, args);
   }
}

声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:qvyue@qq.com 进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。