美文网首页程序员
二. spring cloud 集成 sentinel做限流

二. spring cloud 集成 sentinel做限流

作者: 仅仅如此_丶 | 来源:发表于2020-12-21 16:16 被阅读0次

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

     <spring-boot.version>2.3.5.RELEASE</spring-boot.version>
     <spring-cloud.version>Hoxton.SR8</spring-cloud.version>
     <spring-cloud-alibaba.version>2.2.3.RELEASE</spring-cloud-alibaba.version>
     sentinel-dashboard  1.8.0
    

    2. 项目依赖

           <dependency>
               <groupId>com.alibaba.cloud</groupId>
               <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
           </dependency>
    

    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<Method, MethodHandler> 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<Method, InvocationHandlerFactory.MethodHandler> dispatch;
    
       private FallbackFactory fallbackFactory;
    
       private Map<Method, Method> fallbackMethodMap;
    
       xxxxxxSentinelInvocationHandler(Target<?> target, Map<Method, InvocationHandlerFactory.MethodHandler> dispatch,
               FallbackFactory fallbackFactory) {
           this.target = checkNotNull(target, "target");
           this.dispatch = checkNotNull(dispatch, "dispatch");
           this.fallbackFactory = fallbackFactory;
           this.fallbackMethodMap = toFallbackMethod(dispatch);
       }
    
       xxxxxxSentinelInvocationHandler(Target<?> target, Map<Method, InvocationHandlerFactory.MethodHandler> 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<Method, Method> toFallbackMethod(Map<Method, InvocationHandlerFactory.MethodHandler> dispatch) {
           Map<Method, Method> 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##########

      <!--断路器网关依赖   -->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
            </dependency>
            <dependency>
                <groupId>com.xxxx</groupId>
                <artifactId>xxxx-common-sentinel</artifactId>
                <version>1.0.0</version>
            </dependency>
    
    • 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<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
           ServerHttpResponse originalResponse = exchange.getResponse();
           ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
    
               @Override
               public Mono<Void> 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 官网下最新控制台源码


      image.png
    • 6.2 将控制台源码拿到自己项目中部署


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


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

           <dependency>
               <groupId>com.alibaba.csp</groupId>
               <artifactId>sentinel-datasource-nacos</artifactId>
           </dependency>
    
    • 6.5 在网关配置里面将 sentinel 拉去配置的地址配置 (nacos)


      image.png
    • 6.6 在所有服务依赖的配置文件中添加 其他规则配置拉取地址, 如在 application-dev.yml中


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

    相关文章

      网友评论

        本文标题:二. spring cloud 集成 sentinel做限流

        本文链接:https://www.haomeiwen.com/subject/hndrnktx.html