天天看点

Spring Cloud Alibaba-Sentinel源码阅读(一)-Sentinel的使用

一、调用sentinel-core API方式

1、加入依赖

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-core</artifactId>
    <version>1.4.1</version>
</dependency>
           

2、使用

public static void main(String[] args) {
	Entry entry = null;
	try {
		// 流控入口
		entry = SphU.entry("HelloWorld");
		// 业务逻辑.
		System.out.println("hello world");
	} catch (BlockException e1) {
		// 被流控会流控异常,业务可以自己做相应的处理
		System.out.println("blocked!");
	} finally {
		if (entry != null) {
			entry.exit();
		}
	}
}
           

二、@SentinelResourceAspect注解方式

1、加入依赖

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-annotation-aspectj</artifactId>
    <version>x.y.z</version>
</dependency>
           

2、使用

// 原函数
@SentinelResource(value = "hello", blockHandler = "exceptionHandler", fallback = "helloFallback")
public String hello ( long s){
	return String.format("Hello at %d", s);
}

// Fallback 函数,函数签名与原函数一致.
public String helloFallback (long s){
	return String.format("Halooooo %d", s);
}

// Block 异常处理函数,参数最后多一个 BlockException,其余与原函数一致.
public String exceptionHandler (long s, BlockException ex){
	// Do some log here.
	ex.printStackTrace();
	return "Oops, error occurred at " + s;
}
           

3、原理

直接看下@SentinelResource的切面类SentinelResourceAspect

@Aspect
public class SentinelResourceAspect extends AbstractSentinelAspectSupport {

    @Pointcut("@annotation(com.alibaba.csp.sentinel.annotation.SentinelResource)")
    public void sentinelResourceAnnotationPointcut() {
    }

    @Around("sentinelResourceAnnotationPointcut()")
    public Object invokeResourceWithSentinel(ProceedingJoinPoint pjp) throws Throwable {
        Method originMethod = resolveMethod(pjp);

        SentinelResource annotation = originMethod.getAnnotation(SentinelResource.class);
        if (annotation == null) {
            // Should not go through here.
            throw new IllegalStateException("Wrong state for SentinelResource annotation");
        }
        String resourceName = getResourceName(annotation.value(), originMethod);
        EntryType entryType = annotation.entryType();
        int resourceType = annotation.resourceType();
        Entry entry = null;
        try {
			// 调用sentinel-core API
            entry = SphU.entry(resourceName, resourceType, entryType, pjp.getArgs());
            // 业务执行
            return pjp.proceed();
        } catch (BlockException ex) {
            return handleBlockException(pjp, annotation, ex);
        } catch (Throwable ex) {
            Class<? extends Throwable>[] exceptionsToIgnore = annotation.exceptionsToIgnore();
            // The ignore list will be checked first.
            if (exceptionsToIgnore.length > 0 && exceptionBelongsTo(ex, exceptionsToIgnore)) {
                throw ex;
            }
            if (exceptionBelongsTo(ex, annotation.exceptionsToTrace())) {
                traceException(ex);
                return handleFallback(pjp, annotation, ex);
            }

            // No fallback function can handle the exception, so throw it out.
            throw ex;
        } finally {
            if (entry != null) {
                entry.exit(1, pjp.getArgs());
            }
        }
    }
}
           

由SentinelResourceAspect切面类可知,其实就是对sentinel-core API的封装。

三、Spring Boot方式

1、加入依赖

<dependency>
	<groupId>com.alibaba.csp</groupId>
	<artifactId>sentinel-spring-boot-starter</artifactId>
	<version>1.7.0.RELEASE</version>
</dependency>
           

2、原理

sentinel-spring-boot-starter依赖了sentinel-web-servlet-spring-boot-autoconfigure,这个autoconfigure的spring.factories中会自动注册SentinelWebMvcAutoConfiguration

@EnableConfigurationProperties({SentinelWebMvcProperties.class})
@EnableWebMvcCircuitBreaker
@ConditionalOnProperty(
    value = {"sentinel.autoconfigure.enable"},
    matchIfMissing = true,
    havingValue = "true"
)
@Configuration
@Import({SentinelConfiguration.class})
public class SentinelWebMvcAutoConfiguration {
    public SentinelWebMvcAutoConfiguration() {
    }
}
           
@Import({SentinelFilterConfiguration.class, CompatibleSentinelConfiguration.class})
@Configuration
public class SentinelConfiguration {
    public SentinelConfiguration() {
    }
}
           
public class SentinelFilterConfiguration {
    private final SentinelWebMvcProperties properties;

    public SentinelFilterConfiguration(SentinelWebMvcProperties properties) {
        this.properties = properties;
    }

    @Bean
    @ConditionalOnMissingBean({CommonFilter.class})
    public FilterRegistrationBean sentinel2Filter() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        Filter filterConfig = this.properties.getFilter();
        if (filterConfig.getUrlPatterns() == null || filterConfig.getUrlPatterns().isEmpty()) {
            List<String> defaultPatterns = new ArrayList();
            defaultPatterns.add("/*");
            filterConfig.setUrlPatterns(defaultPatterns);
        }

        registration.addUrlPatterns((String[])filterConfig.getUrlPatterns().toArray(new String[0]));
        // 注入Filter
        javax.servlet.Filter filter = new CommonFilter();
        registration.setFilter(filter);
        registration.setOrder(filterConfig.getOrder());
        registration.addInitParameter("HTTP_METHOD_SPECIFY", String.valueOf(this.properties.getHttpMethodSpecify()));
        return registration;
    }
}
           

看下CommonFilter类的继承关系:

Spring Cloud Alibaba-Sentinel源码阅读(一)-Sentinel的使用

由此就进入了Web Servlter模块,直接看下CommonFilter#doFilter

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
	HttpServletRequest sRequest = (HttpServletRequest)request;
	Entry urlEntry = null;

	try {
		String target = FilterUtil.filterTarget(sRequest);
		boolean skipSentinel = this.detectSkipSentinel(target);
		UrlCleaner urlCleaner = WebCallbackManager.getUrlCleaner();
		if (urlCleaner != null) {
			target = urlCleaner.clean(target);
		}

		if (!StringUtil.isEmpty(target) && !skipSentinel) {
			String origin = this.parseOrigin(sRequest);
			ContextUtil.enter(target, origin);
			if (this.httpMethodSpecify) {
				String pathWithHttpMethod = sRequest.getMethod().toUpperCase() + ":" + target;
				urlEntry = SphU.entry(pathWithHttpMethod, 1, EntryType.IN);
			} else {
				// 调用sentinel-core API
				urlEntry = SphU.entry(target, 1, EntryType.IN);
			}
		}

		chain.doFilter(request, response);
	} catch (BlockException var15) {
		HttpServletResponse sResponse = (HttpServletResponse)response;
		WebCallbackManager.getUrlBlockHandler().blocked(sRequest, sResponse, var15);
	} catch (ServletException | RuntimeException | IOException var16) {
		Tracer.traceEntry(var16, urlEntry);
		throw var16;
	} finally {
		if (urlEntry != null) {
			urlEntry.exit();
		}

		ContextUtil.exit();
	}
}
           

由此可知:其实也是对sentinel-core API的封装,默认会对所有的外部请求拦截,当然是否流控还要看有没有在Sentinel Dashboard配置限流规则!

继续阅读