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 网关配置限流降级
@Configuration
代码语言:txt复制public class GatewayConfiguration {
代码语言:txt复制 @PostConstruct
代码语言:txt复制 public void doInit() {
代码语言:txt复制 GatewayCallbackManager.setBlockHandler(new GateWayBlockRequestHandler());
代码语言:txt复制 }
代码语言:txt复制}
- 5.2 服务返回 一般返回自定义的返回体 sentinel 不能根据自定义的返回体进行异常降级 需自已处理
@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 公共的依赖里面 )
<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 控制台会跟普通服务显示一样 需要添加启动项
@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复制}