diff --git a/pom.xml b/pom.xml index 94a523859..ac6ae9962 100644 --- a/pom.xml +++ b/pom.xml @@ -59,6 +59,7 @@ 2.0.0.RELEASE 2.0.0.RELEASE + 4.12 3.0 1.7.25 @@ -82,6 +83,7 @@ spring-cloud-starter-alibaba spring-cloud-starter-alicloud spring-cloud-alicloud-oss + spring-cloud-alibaba-sentinel-zuul diff --git a/spring-cloud-alibaba-sentinel-zuul/README.md b/spring-cloud-alibaba-sentinel-zuul/README.md new file mode 100755 index 000000000..2d0d2fd12 --- /dev/null +++ b/spring-cloud-alibaba-sentinel-zuul/README.md @@ -0,0 +1,141 @@ +# Sentinel Spring Cloud Zuul Adapter + +Zuul does not provide rateLimit function, If use default `SentinelRibbonFilter` route filter. it wrapped by Hystrix Command. so only provide Service level +circuit protect. + +Sentinel can provide `ServiceId` level and `API Path` level flow control for spring cloud zuul gateway service. + +*Note*: this project is for zuul 1. + +## How to use + +1. Add maven dependency + +```xml + + org.springframework.cloud + spring-cloud-alibaba-sentinel-zuul + x.y.z + + +``` + +2. Set application.property + +``` +// default value is false +spring.cloud.sentinel.zuul.enabled=true +``` + +## How it works + +As Zuul run as per thread per connection block model, we add filters around `route Filter` to trace sentinel statistics. + +- `SentinelPreFilter`: Get an entry of resource,the first order is **ServiceId**, then **API Path**. +- `SentinelPostFilter`: When success response,exit entry. +- `SentinelErrorFilter`: When get an `Exception`, trace the exception and exit context. + + +the order of Filter can be changed by configuration: + +``` +spring.cloud.sentinel.zuul.order.post=0 +spring.cloud.sentinel.zuul.order.pre=10000 +spring.cloud.sentinel.zuul.order.error=-1 +``` + + +Filters create structure like: + + +```bash + +EntranceNode: machine-root(t:3 pq:0 bq:0 tq:0 rt:0 prq:0 1mp:0 1mb:0 1mt:0) +-EntranceNode: coke(t:2 pq:0 bq:0 tq:0 rt:0 prq:0 1mp:0 1mb:0 1mt:0) +--coke(t:2 pq:0 bq:0 tq:0 rt:0 prq:0 1mp:0 1mb:0 1mt:0) +---/coke/uri(t:0 pq:0 bq:0 tq:0 rt:0 prq:0 1mp:0 1mb:0 1mt:0) +-EntranceNode: sentinel_default_context(t:0 pq:0 bq:0 tq:0 rt:0 prq:0 1mp:0 1mb:0 1mt:0) +-EntranceNode: book(t:1 pq:0 bq:0 tq:0 rt:0 prq:0 1mp:0 1mb:0 1mt:0) +--book(t:1 pq:0 bq:0 tq:0 rt:0 prq:0 1mp:0 1mb:0 1mt:0) +---/book/uri(t:0 pq:0 bq:0 tq:0 rt:0 prq:0 1mp:0 1mb:0 1mt:0) + +``` + +`book` and `coke` are serviceId. + +`---/book/uri` is api path, the real uri is `/uri`. + + +## Integration with Sentinel DashBord + +Start [Sentinel DashBord](https://github.com/alibaba/Sentinel/wiki/%E6%8E%A7%E5%88%B6%E5%8F%B0). + +## Rule config with dataSource + +Sentinel has full rule config features. see [Dynamic-Rule-Configuration](https://github.com/alibaba/Sentinel/wiki/Dynamic-Rule-Configuration) + + +## Custom Fallbacks + +Implements `SentinelFallbackProvider` to define your own Fallback Provider when Sentinel Block Exception throwing for different rout. the default +Fallback Provider is `DefaultBlockFallbackProvider`. + +By default Fallback route is `ServiveId + URI PATH`, example `/book/coke`, first `book` is serviceId, `/uri` is URI PATH, so both +can be needed. + +Here is an example: + +```java + +// custom provider +public class MyCokeServiceBlockFallbackProvider implements SentinelFallbackProvider { + + private Logger logger = LoggerFactory.getLogger(DefaultBlockFallbackProvider.class); + + // you can define root as service level + @Override + public String getRoute() { + return "/coke/uri"; + } + + @Override + public ClientHttpResponse fallbackResponse(String route, Throwable cause) { + if (cause instanceof BlockException) { + logger.info("get in fallback block exception:{}", cause); + return response(HttpStatus.TOO_MANY_REQUESTS, route); + } else { + return response(HttpStatus.INTERNAL_SERVER_ERROR, route); + } + } + } + +``` + +## Custom Request Origin Parser +By default this adapter use `DefaultRequestOriginParser` to parse sentinel origin. + +```java + +public class CustomRequestOriginParser implements RequestOriginParser { + @Override + public String parseOrigin(HttpServletRequest request) { + // do custom logic. + return ""; + } +} + +``` + +## Custom UrlCleaner +By default this adapter use `DefaultUrlCleaner` to define uri resource. + +```java +public class CustomUrlCleaner implements UrlCleaner { + + @Override + public String clean(String originUrl) { + // do custom logic. + return originUrl; + } +} +``` \ No newline at end of file diff --git a/spring-cloud-alibaba-sentinel-zuul/pom.xml b/spring-cloud-alibaba-sentinel-zuul/pom.xml new file mode 100644 index 000000000..60ae2dff4 --- /dev/null +++ b/spring-cloud-alibaba-sentinel-zuul/pom.xml @@ -0,0 +1,43 @@ + + + + spring-cloud-alibaba + org.springframework.cloud + 0.2.0.BUILD-SNAPSHOT + + 4.0.0 + + spring-cloud-alibaba-sentinel-zuul + + + + org.springframework.cloud + spring-cloud-starter-netflix-zuul + provided + + + com.alibaba.csp + sentinel-zuul-adapter + 1.4.2 + + + org.springframework.boot + spring-boot-configuration-processor + provided + true + + + junit + junit + test + + + org.mockito + mockito-core + test + + + + \ No newline at end of file diff --git a/spring-cloud-alibaba-sentinel-zuul/src/main/java/org/springframework/cloud/alibaba/sentinel/zuul/SentinelZuulAutoConfiguration.java b/spring-cloud-alibaba-sentinel-zuul/src/main/java/org/springframework/cloud/alibaba/sentinel/zuul/SentinelZuulAutoConfiguration.java new file mode 100644 index 000000000..9fc776374 --- /dev/null +++ b/spring-cloud-alibaba-sentinel-zuul/src/main/java/org/springframework/cloud/alibaba/sentinel/zuul/SentinelZuulAutoConfiguration.java @@ -0,0 +1,116 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.alibaba.sentinel.zuul; + +import com.alibaba.csp.sentinel.adapter.zuul.fallback.DefaultRequestOriginParser; +import com.alibaba.csp.sentinel.adapter.zuul.fallback.DefaultUrlCleaner; +import com.alibaba.csp.sentinel.adapter.zuul.fallback.RequestOriginParser; +import com.alibaba.csp.sentinel.adapter.zuul.fallback.UrlCleaner; +import com.alibaba.csp.sentinel.adapter.zuul.filters.SentinelErrorFilter; +import com.alibaba.csp.sentinel.adapter.zuul.filters.SentinelPostFilter; +import com.alibaba.csp.sentinel.adapter.zuul.filters.SentinelPreFilter; +import com.alibaba.csp.sentinel.adapter.zuul.properties.SentinelZuulProperties; +import com.alibaba.csp.sentinel.util.StringUtil; +import com.netflix.zuul.ZuulFilter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cloud.alibaba.sentinel.zuul.listener.FallBackProviderListener; +import org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; + +import static org.springframework.cloud.alibaba.sentinel.zuul.SentinelZuulAutoConfiguration.PREFIX; + +/** + * Sentinel Spring Cloud Zuul AutoConfiguration + * + * @author tiger + */ +@Configuration +@ConditionalOnProperty(prefix = PREFIX, name = "enabled", havingValue = "true") +public class SentinelZuulAutoConfiguration { + + @Autowired + private Environment environment; + + public static final String PREFIX = "spring.cloud.alibaba.sentinel.zuul"; + + @Bean + public SentinelZuulProperties sentinelZuulProperties() { + SentinelZuulProperties properties = new SentinelZuulProperties(); + String enabledStr = environment.getProperty(PREFIX + "." + "enabled"); + String preOrderStr = environment.getProperty(PREFIX + "." + "order.pre"); + String postOrderStr = environment.getProperty(PREFIX + "." + "order.post"); + String errorOrderStr = environment.getProperty(PREFIX + "." + "order.error"); + if (StringUtil.isNotEmpty(enabledStr)) { + Boolean enabled = Boolean.valueOf(enabledStr); + properties.setEnabled(enabled); + } + if (StringUtil.isNotEmpty(preOrderStr)) { + properties.getOrder().setPre(Integer.parseInt(preOrderStr)); + } + if (StringUtil.isNotEmpty(postOrderStr)) { + properties.getOrder().setPost(Integer.parseInt(postOrderStr)); + } + if (StringUtil.isNotEmpty(errorOrderStr)) { + properties.getOrder().setError(Integer.parseInt(errorOrderStr)); + } + return properties; + } + + @Bean + @ConditionalOnMissingBean(ProxyRequestHelper.class) + public ProxyRequestHelper proxyRequestHelper() { + return new ProxyRequestHelper(); + } + + @Bean + @ConditionalOnMissingBean(UrlCleaner.class) + public UrlCleaner urlCleaner(){ + return new DefaultUrlCleaner(); + } + + @Bean + @ConditionalOnMissingBean(RequestOriginParser.class) + public RequestOriginParser requestOriginParser(){ + return new DefaultRequestOriginParser(); + } + + @Bean + public ZuulFilter preFilter(SentinelZuulProperties sentinelZuulProperties,UrlCleaner urlCleaner, + RequestOriginParser requestOriginParser) { + return new SentinelPreFilter(sentinelZuulProperties,urlCleaner,requestOriginParser); + } + + @Bean + public ZuulFilter postFilter(SentinelZuulProperties sentinelZuulProperties) { + return new SentinelPostFilter(sentinelZuulProperties); + } + + @Bean + public ZuulFilter errorFilter(SentinelZuulProperties sentinelZuulProperties) { + return new SentinelErrorFilter(sentinelZuulProperties); + } + + @Bean + public FallBackProviderListener fallBackProviderListener() { + return new FallBackProviderListener(); + } + +} diff --git a/spring-cloud-alibaba-sentinel-zuul/src/main/java/org/springframework/cloud/alibaba/sentinel/zuul/listener/FallBackProviderListener.java b/spring-cloud-alibaba-sentinel-zuul/src/main/java/org/springframework/cloud/alibaba/sentinel/zuul/listener/FallBackProviderListener.java new file mode 100644 index 000000000..d4d19bdf7 --- /dev/null +++ b/spring-cloud-alibaba-sentinel-zuul/src/main/java/org/springframework/cloud/alibaba/sentinel/zuul/listener/FallBackProviderListener.java @@ -0,0 +1,38 @@ +package org.springframework.cloud.alibaba.sentinel.zuul.listener; + +import com.alibaba.csp.sentinel.adapter.zuul.fallback.DefaultBlockFallbackProvider; +import com.alibaba.csp.sentinel.adapter.zuul.fallback.ZuulBlockFallbackManager; +import com.alibaba.csp.sentinel.adapter.zuul.fallback.ZuulBlockFallbackProvider; +import org.apache.commons.collections.MapUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.context.event.ApplicationStartedEvent; +import org.springframework.context.event.EventListener; + +import java.util.Map; + + +/** + * @author tiger + */ +public class FallBackProviderListener { + + private static final Logger logger = LoggerFactory.getLogger(FallBackProviderListener.class); + + @EventListener(classes = ApplicationStartedEvent.class) + public void appStartedListener(ApplicationStartedEvent event) throws Exception { + Map providerMap = event.getApplicationContext().getBeansOfType( + ZuulBlockFallbackProvider.class); + if (MapUtils.isNotEmpty(providerMap)) { + providerMap.forEach((k, v) -> { + logger.info("[Sentinel] Register provider name: {}, instance: {}", k, v); + ZuulBlockFallbackManager.registerProvider(v); + }); + } else { + logger.info("[Sentinel] Register default fallback provider. "); + ZuulBlockFallbackManager.registerProvider(new DefaultBlockFallbackProvider()); + } + } + + +} diff --git a/spring-cloud-alibaba-sentinel-zuul/src/main/resources/META-INF/spring.factories b/spring-cloud-alibaba-sentinel-zuul/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..b4ced151e --- /dev/null +++ b/spring-cloud-alibaba-sentinel-zuul/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +org.springframework.cloud.alibaba.sentinel.zuul.SentinelZuulAutoConfiguration \ No newline at end of file