sentinel做限流教程

2021-12-16 22:33:22 浏览数 (1)

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

代码语言:txt复制
 <spring-boot.version>2.3.5.RELEASE</spring-boot.version>
代码语言:txt复制
 <spring-cloud.version>Hoxton.SR8</spring-cloud.version>
代码语言:txt复制
 <spring-cloud-alibaba.version>2.2.3.RELEASE</spring-cloud-alibaba.version>
代码语言:txt复制
 sentinel-dashboard  1.8.0

2. 项目依赖

代码语言:txt复制
       <dependency>
代码语言:txt复制
           <groupId>com.alibaba.cloud</groupId>
代码语言:txt复制
           <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
代码语言:txt复制
       </dependency>

3. sentinel 官网地址

https://github.com/alibaba/Sentinel

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

代码语言:txt复制
 1. 配置服务间调用开关及发生降级后异常处理
@AutoConfigureBefore(SentinelFeignAutoConfiguration.class)
public class SentinelAutoConfiguration {
代码语言:txt复制
   @Bean
代码语言:txt复制
   @Scope("prototype")
代码语言:txt复制
   @ConditionalOnMissingBean
代码语言:txt复制
   @ConditionalOnProperty(name = "feign.sentinel.enabled")
代码语言:txt复制
   public Feign.Builder feignSentinelBuilder() {
代码语言:txt复制
       return xxxxxSentinelFeign.builder();
代码语言:txt复制
   }
代码语言:txt复制
   @Bean
代码语言:txt复制
   @ConditionalOnMissingBean
代码语言:txt复制
   public BlockExceptionHandler blockExceptionHandler() {
代码语言:txt复制
       return new xxxxxUrlBlockHandler();
代码语言:txt复制
   }
代码语言:txt复制
}
代码语言:txt复制
2.  重写 {@link com.alibaba.cloud.sentinel.feign.SentinelFeign} 支持自动降级注入
代码语言:txt复制
public final class xxxxxSentinelFeign {
代码语言:txt复制
   private xxxxxSentinelFeign() {
代码语言:txt复制
   }
代码语言:txt复制
   public static xxxxSentinelFeign.Builder builder() {
代码语言:txt复制
       return new xxxxxSentinelFeign.Builder();
代码语言:txt复制
   }
代码语言:txt复制
   public static final class Builder extends Feign.Builder implements ApplicationContextAware {
代码语言:txt复制
       private Contract contract = new Contract.Default();
代码语言:txt复制
       private ApplicationContext applicationContext;
代码语言:txt复制
       private FeignContext feignContext;
代码语言:txt复制
       @Override
代码语言:txt复制
       public Feign.Builder invocationHandlerFactory(InvocationHandlerFactory invocationHandlerFactory) {
代码语言:txt复制
           throw new UnsupportedOperationException();
代码语言:txt复制
       }
代码语言:txt复制
       @Override
代码语言:txt复制
       public xxxxxSentinelFeign.Builder contract(Contract contract) {
代码语言:txt复制
           this.contract = contract;
代码语言:txt复制
           return this;
代码语言:txt复制
       }
代码语言:txt复制
       @Override
代码语言:txt复制
       public Feign build() {
代码语言:txt复制
           super.invocationHandlerFactory(new InvocationHandlerFactory() {
代码语言:txt复制
               @Override
代码语言:txt复制
               public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
代码语言:txt复制
                   // using reflect get fallback and fallbackFactory properties from
代码语言:txt复制
                   // FeignClientFactoryBean because FeignClientFactoryBean is a package
代码语言:txt复制
                   // level class, we can not use it in our package
代码语言:txt复制
                   Object feignClientFactoryBean = xxxxSentinelFeign.Builder.this.applicationContext
代码语言:txt复制
                           .getBean("&"   target.type().getName());
代码语言:txt复制
                   Class fallback = (Class) getFieldValue(feignClientFactoryBean, "fallback");
代码语言:txt复制
                   Class fallbackFactory = (Class) getFieldValue(feignClientFactoryBean, "fallbackFactory");
代码语言:txt复制
                   String beanName = (String) getFieldValue(feignClientFactoryBean, "contextId");
代码语言:txt复制
                   if (!StringUtils.hasText(beanName)) {
代码语言:txt复制
                       beanName = (String) getFieldValue(feignClientFactoryBean, "name");
代码语言:txt复制
                   }
代码语言:txt复制
                   Object fallbackInstance;
代码语言:txt复制
                   FallbackFactory fallbackFactoryInstance;
代码语言:txt复制
                   // check fallback and fallbackFactory properties
代码语言:txt复制
                   if (void.class != fallback) {
代码语言:txt复制
                       fallbackInstance = getFromContext(beanName, "fallback", fallback, target.type());
代码语言:txt复制
                       return new xxxxSentinelInvocationHandler(target, dispatch,
代码语言:txt复制
                               new FallbackFactory.Default(fallbackInstance));
代码语言:txt复制
                   }
代码语言:txt复制
                   if (void.class != fallbackFactory) {
代码语言:txt复制
                       fallbackFactoryInstance = (FallbackFactory) getFromContext(beanName, "fallbackFactory",
代码语言:txt复制
                               fallbackFactory, FallbackFactory.class);
代码语言:txt复制
                       return new xxxxSentinelInvocationHandler(target, dispatch, fallbackFactoryInstance);
代码语言:txt复制
                   }
代码语言:txt复制
                   return new xxxxxSentinelInvocationHandler(target, dispatch);
代码语言:txt复制
               }
代码语言:txt复制
               private Object getFromContext(String name, String type, Class fallbackType, Class targetType) {
代码语言:txt复制
                   Object fallbackInstance = feignContext.getInstance(name, fallbackType);
代码语言:txt复制
                   if (fallbackInstance == null) {
代码语言:txt复制
                       throw new IllegalStateException(String.format(
代码语言:txt复制
                               "No %s instance of type %s found for feign client %s", type, fallbackType, name));
代码语言:txt复制
                   }
代码语言:txt复制
                   if (!targetType.isAssignableFrom(fallbackType)) {
代码语言:txt复制
                       throw new IllegalStateException(String.format(
代码语言:txt复制
                               "Incompatible %s instance. Fallback/fallbackFactory of type %s is not assignable to %s for feign client %s",
代码语言:txt复制
                               type, fallbackType, targetType, name));
代码语言:txt复制
                   }
代码语言:txt复制
                   return fallbackInstance;
代码语言:txt复制
               }
代码语言:txt复制
           });
代码语言:txt复制
           super.contract(new SentinelContractHolder(contract));
代码语言:txt复制
           return super.build();
代码语言:txt复制
       }
代码语言:txt复制
       private Object getFieldValue(Object instance, String fieldName) {
代码语言:txt复制
           Field field = ReflectionUtils.findField(instance.getClass(), fieldName);
代码语言:txt复制
           field.setAccessible(true);
代码语言:txt复制
           try {
代码语言:txt复制
               return field.get(instance);
代码语言:txt复制
           }
代码语言:txt复制
           catch (IllegalAccessException e) {
代码语言:txt复制
               // ignore
代码语言:txt复制
           }
代码语言:txt复制
           return null;
代码语言:txt复制
       }
代码语言:txt复制
       @Override
代码语言:txt复制
       public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
代码语言:txt复制
           this.applicationContext = applicationContext;
代码语言:txt复制
           feignContext = this.applicationContext.getBean(FeignContext.class);
代码语言:txt复制
       }
代码语言:txt复制
   }
代码语言:txt复制
}
代码语言:txt复制
3. 降级后异常处理
public class xxxxxSentinelInvocationHandler implements InvocationHandler {
代码语言:txt复制
   public static final String EQUALS = "equals";
代码语言:txt复制
   public static final String HASH_CODE = "hashCode";
代码语言:txt复制
   public static final String TO_STRING = "toString";
代码语言:txt复制
   private final Target<?> target;
代码语言:txt复制
   private final Map<Method, InvocationHandlerFactory.MethodHandler> dispatch;
代码语言:txt复制
   private FallbackFactory fallbackFactory;
代码语言:txt复制
   private Map<Method, Method> fallbackMethodMap;
代码语言:txt复制
   xxxxxxSentinelInvocationHandler(Target<?> target, Map<Method, InvocationHandlerFactory.MethodHandler> dispatch,
代码语言:txt复制
           FallbackFactory fallbackFactory) {
代码语言:txt复制
       this.target = checkNotNull(target, "target");
代码语言:txt复制
       this.dispatch = checkNotNull(dispatch, "dispatch");
代码语言:txt复制
       this.fallbackFactory = fallbackFactory;
代码语言:txt复制
       this.fallbackMethodMap = toFallbackMethod(dispatch);
代码语言:txt复制
   }
代码语言:txt复制
   xxxxxxSentinelInvocationHandler(Target<?> target, Map<Method, InvocationHandlerFactory.MethodHandler> dispatch) {
代码语言:txt复制
       this.target = checkNotNull(target, "target");
代码语言:txt复制
       this.dispatch = checkNotNull(dispatch, "dispatch");
代码语言:txt复制
   }
代码语言:txt复制
   @Override
代码语言:txt复制
   public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
代码语言:txt复制
       if (EQUALS.equals(method.getName())) {
代码语言:txt复制
           try {
代码语言:txt复制
               Object otherHandler = args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
代码语言:txt复制
               return equals(otherHandler);
代码语言:txt复制
           }
代码语言:txt复制
           catch (IllegalArgumentException e) {
代码语言:txt复制
               return false;
代码语言:txt复制
           }
代码语言:txt复制
       }
代码语言:txt复制
       else if (HASH_CODE.equals(method.getName())) {
代码语言:txt复制
           return hashCode();
代码语言:txt复制
       }
代码语言:txt复制
       else if (TO_STRING.equals(method.getName())) {
代码语言:txt复制
           return toString();
代码语言:txt复制
       }
代码语言:txt复制
       Object result;
代码语言:txt复制
       InvocationHandlerFactory.MethodHandler methodHandler = this.dispatch.get(method);
代码语言:txt复制
       // only handle by HardCodedTarget
代码语言:txt复制
       if (target instanceof Target.HardCodedTarget) {
代码语言:txt复制
           Target.HardCodedTarget hardCodedTarget = (Target.HardCodedTarget) target;
代码语言:txt复制
           MethodMetadata methodMetadata = SentinelContractHolder.METADATA_MAP
代码语言:txt复制
                   .get(hardCodedTarget.type().getName()   Feign.configKey(hardCodedTarget.type(), method));
代码语言:txt复制
           // resource default is HttpMethod:protocol://url
代码语言:txt复制
           if (methodMetadata == null) {
代码语言:txt复制
               result = methodHandler.invoke(args);
代码语言:txt复制
           }
代码语言:txt复制
           else {
代码语言:txt复制
               String resourceName = methodMetadata.template().method().toUpperCase()   ":"   hardCodedTarget.url()
代码语言:txt复制
                         methodMetadata.template().path();
代码语言:txt复制
               Entry entry = null;
代码语言:txt复制
               try {
代码语言:txt复制
                   ContextUtil.enter(resourceName);
代码语言:txt复制
                   entry = SphU.entry(resourceName, EntryType.OUT, 1, args);
代码语言:txt复制
                   result = methodHandler.invoke(args);
代码语言:txt复制
               }
代码语言:txt复制
               catch (Throwable ex) {
代码语言:txt复制
                   // fallback handle
代码语言:txt复制
                   if (!BlockException.isBlockException(ex)) {
代码语言:txt复制
                       Tracer.trace(ex);
代码语言:txt复制
                   }
代码语言:txt复制
                   if (fallbackFactory != null) {
代码语言:txt复制
                       try {
代码语言:txt复制
                           Object fallbackResult = fallbackMethodMap.get(method).invoke(fallbackFactory.create(ex),
代码语言:txt复制
                                   args);
代码语言:txt复制
                           return fallbackResult;
代码语言:txt复制
                       }
代码语言:txt复制
                       catch (IllegalAccessException e) {
代码语言:txt复制
                           // shouldn't happen as method is public due to being an
代码语言:txt复制
                           // interface
代码语言:txt复制
                           throw new AssertionError(e);
代码语言:txt复制
                       }
代码语言:txt复制
                       catch (InvocationTargetException e) {
代码语言:txt复制
                           throw new AssertionError(e.getCause());
代码语言:txt复制
                       }
代码语言:txt复制
                   }else {
代码语言:txt复制
                       // 若是XResponse类型 执行自动降级返回 XResponse
代码语言:txt复制
                       if (XResponse.class == method.getReturnType()) {
代码语言:txt复制
                           log.error("feign 服务间调用异常", ex);
代码语言:txt复制
                           //不能返回 XResponse , 涉及业务处理。 直接抛出异常,在全局异常中处理
代码语言:txt复制
//                          return  XResponse.builder()
代码语言:txt复制
//                                  .data(Boolean.FALSE)
代码语言:txt复制
//                                  .msg("服务请求超时,请稍后重试")
代码语言:txt复制
//                                  .httpStatus(HttpStatus.SERVICE_UNAVAILABLE)
代码语言:txt复制
//                                  .build();
代码语言:txt复制
                           throw  new FeginServierException("系统繁忙,请稍后重试");
代码语言:txt复制
                       }else {
代码语言:txt复制
                           throw ex;
代码语言:txt复制
                       }
代码语言:txt复制
                   }
代码语言:txt复制
               }
代码语言:txt复制
               finally {
代码语言:txt复制
                   if (entry != null) {
代码语言:txt复制
                       entry.exit(1, args);
代码语言:txt复制
                   }
代码语言:txt复制
                   ContextUtil.exit();
代码语言:txt复制
               }
代码语言:txt复制
           }
代码语言:txt复制
       }
代码语言:txt复制
       else {
代码语言:txt复制
           // other target type using default strategy
代码语言:txt复制
           result = methodHandler.invoke(args);
代码语言:txt复制
       }
代码语言:txt复制
       return result;
代码语言:txt复制
   }
代码语言:txt复制
   @Override
代码语言:txt复制
   public boolean equals(Object obj) {
代码语言:txt复制
       if (obj instanceof SentinelInvocationHandler) {
代码语言:txt复制
           xxxxSentinelInvocationHandler other = (xxxxSentinelInvocationHandler) obj;
代码语言:txt复制
           return target.equals(other.target);
代码语言:txt复制
       }
代码语言:txt复制
       return false;
代码语言:txt复制
   }
代码语言:txt复制
   @Override
代码语言:txt复制
   public int hashCode() {
代码语言:txt复制
       return target.hashCode();
代码语言:txt复制
   }
代码语言:txt复制
   @Override
代码语言:txt复制
   public String toString() {
代码语言:txt复制
       return target.toString();
代码语言:txt复制
   }
代码语言:txt复制
   static Map<Method, Method> toFallbackMethod(Map<Method, InvocationHandlerFactory.MethodHandler> dispatch) {
代码语言:txt复制
       Map<Method, Method> result = new LinkedHashMap<>();
代码语言:txt复制
       for (Method method : dispatch.keySet()) {
代码语言:txt复制
           method.setAccessible(true);
代码语言:txt复制
           result.put(method, method);
代码语言:txt复制
       }
代码语言:txt复制
       return result;
代码语言:txt复制
   }
代码语言:txt复制
}
代码语言:txt复制
4. 服务间流控降级自定义返回
public class xxxxxUrlBlockHandler implements BlockExceptionHandler {
代码语言:txt复制
   @Override
代码语言:txt复制
   public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
代码语言:txt复制
       log.error("sentinel 降级 资源名称{}", e.getRule().getResource(), e);
代码语言:txt复制
       response.setContentType(ContentType.JSON.toString());
代码语言:txt复制
       response.setStatus(HttpStatus.SERVICE_UNAVAILABLE.value());
代码语言:txt复制
       response.setCharacterEncoding("UTF-8");
代码语言:txt复制
       response.getWriter().print(JSONUtil.toJsonStr(XResponse.builder()
代码语言:txt复制
               .data(Boolean.FALSE)
代码语言:txt复制
               .msg("服务请求超时,请稍后重试")
代码语言:txt复制
               .httpStatus(HttpStatus.SERVICE_UNAVAILABLE)
代码语言:txt复制
               .build()
代码语言:txt复制
               .toResponseEntity()));
代码语言:txt复制
   }
代码语言:txt复制
}

5. 网关集成流控

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

代码语言:txt复制
  <!--断路器网关依赖   -->
代码语言:txt复制
        <dependency>
代码语言:txt复制
            <groupId>com.alibaba.cloud</groupId>
代码语言:txt复制
            <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
代码语言:txt复制
        </dependency>
代码语言:txt复制
        <dependency>
代码语言:txt复制
            <groupId>com.xxxx</groupId>
代码语言:txt复制
            <artifactId>xxxx-common-sentinel</artifactId>
代码语言:txt复制
            <version>1.0.0</version>
代码语言:txt复制
        </dependency>
  • 5.1 网关配置限流降级
代码语言:txt复制
@Configuration
代码语言:txt复制
public class GatewayConfiguration {
代码语言:txt复制
   @PostConstruct
代码语言:txt复制
   public void doInit() {
代码语言:txt复制
       GatewayCallbackManager.setBlockHandler(new GateWayBlockRequestHandler());
代码语言:txt复制
   }
代码语言:txt复制
}
  • 5.2 服务返回 一般返回自定义的返回体 sentinel 不能根据自定义的返回体进行异常降级 需自已处理
代码语言:txt复制
@Component
代码语言:txt复制
public class GatewayBlockFilter implements GlobalFilter, Ordered {
代码语言:txt复制
   @Override
代码语言:txt复制
   public int getOrder() {
代码语言:txt复制
       return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1;
代码语言:txt复制
   }
代码语言:txt复制
   @Override
代码语言:txt复制
   public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
代码语言:txt复制
       ServerHttpResponse originalResponse = exchange.getResponse();
代码语言:txt复制
       ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
代码语言:txt复制
           @Override
代码语言:txt复制
           public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
代码语言:txt复制
               // get match route id
代码语言:txt复制
               Route route = (Route) exchange.getAttributes().get(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
代码语言:txt复制
               String id = route.getId();
代码语言:txt复制
               // 500 error -> degrade
代码语言:txt复制
               if (originalResponse.getRawStatusCode() == HttpStatus.INTERNAL_SERVER_ERROR.value() ) {
代码语言:txt复制
                   Entry entry = null;
代码语言:txt复制
                   try {
代码语言:txt复制
                       // 0 token
代码语言:txt复制
                       entry = SphU.entry(id, EntryType.OUT);
代码语言:txt复制
                       Tracer.trace(new RuntimeException("INTERNAL_SERVER_ERROR"));
代码语言:txt复制
                   } catch (BlockException e) {
代码语言:txt复制
                       log.error(e.getMessage(), e);
代码语言:txt复制
                   } finally {
代码语言:txt复制
                       if (entry != null){
代码语言:txt复制
                           entry.close();
代码语言:txt复制
                       }
代码语言:txt复制
                   }
代码语言:txt复制
               }
代码语言:txt复制
               return super.writeWith(body);
代码语言:txt复制
           }
代码语言:txt复制
       };
代码语言:txt复制
       // replace response with decorator
代码语言:txt复制
       return chain.filter(exchange.mutate().response(decoratedResponse).build());
代码语言:txt复制
   }
代码语言:txt复制
}

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 公共的依赖里面 )
代码语言:txt复制
       <dependency>
代码语言:txt复制
           <groupId>com.alibaba.csp</groupId>
代码语言:txt复制
           <artifactId>sentinel-datasource-nacos</artifactId>
代码语言:txt复制
       </dependency>
  • 6.5 在网关配置里面将 sentinel 拉去配置的地址配置 (nacos)

image.png

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

image.png

  • 6.7 默认网关启动在sentinel 控制台会跟普通服务显示一样 需要添加启动项
代码语言:txt复制
@SpringCloudApplication
代码语言:txt复制
public class XXXGatewayApplication {
代码语言:txt复制
   public static void main(String[] args) {
代码语言:txt复制
       System.setProperty("csp.sentinel.app.type", "1");
代码语言:txt复制
       SpringApplication.run(XXXGatewayApplication.class, args);
代码语言:txt复制
   }
代码语言:txt复制
}

0 人点赞