Spring Cloud Alibaba Sentinel對RestTemplate的支持

Spring Cloud Alibaba Sentinel 支持對 RestTemplate 的服務調用使用 Sentinel 進行保護,在構造 RestTemplate bean的時候須要加上 @SentinelRestTemplate 註解。git

須要注意的是目前的版本spring-cloud-starter-alibaba-sentinel.0.2.1.RELEASE在配置RestTemplate的時候有個Bug,須要將配置放在Spring Boot的啓動類中,也就是@SpringBootApplication註解所在的類。github

若是單獨放在@Configuration標記的類中目前是有問題的,固然後續版本中會進行修復,對應的問題描述:https://github.com/spring-cloud-incubator/spring-cloud-alibaba/issues/227spring

@Bean
@SentinelRestTemplate(fallback = "fallback", fallbackClass = ExceptionUtil.class, blockHandler="handleException",blockHandlerClass=ExceptionUtil.class)
public RestTemplate restTemplate() {
    return new RestTemplate();
}
  • blockHandler

限流後處理的方法api

  • blockHandlerClass

限流後處理的類緩存

  • fallback

熔斷後處理的方法微信

  • fallbackClass

熔斷後處理的類app

異常處理類定義須要注意的是該方法的參數跟返回值跟 org.springframework.http.client.ClientHttpRequestInterceptor#interceptor 方法一致,其中參數多出了一個 BlockException 參數用於獲取 Sentinel 捕獲的異常。ide

public class ExceptionUtil {
    public static SentinelClientHttpResponse handleException(HttpRequest request,
            byte[] body, ClientHttpRequestExecution execution, BlockException ex) {
        System.err.println("Oops: " + ex.getClass().getCanonicalName());
        return new SentinelClientHttpResponse("custom block info");
    }
    
    public static SentinelClientHttpResponse fallback(HttpRequest request,
            byte[] body, ClientHttpRequestExecution execution, BlockException ex) {
        System.err.println("fallback: " + ex.getClass().getCanonicalName());
        return new SentinelClientHttpResponse("custom fallback info");
    }
}

原理剖析

核心代碼在org.springframework.cloud.alibaba.sentinel.custom.SentinelBeanPostProcessor中,實現了MergedBeanDefinitionPostProcessor接口,MergedBeanDefinitionPostProcessor接口實現了BeanPostProcessor接口。post

核心方法就是重寫的postProcessMergedBeanDefinition和postProcessAfterInitialization。學習

postProcessMergedBeanDefinition

private ConcurrentHashMap<String, SentinelRestTemplate> cache = new ConcurrentHashMap<>();

@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition,
            Class<?> beanType, String beanName) {
    if (checkSentinelProtect(beanDefinition, beanType)) {
        SentinelRestTemplate sentinelRestTemplate = ((StandardMethodMetadata) beanDefinition
                    .getSource()).getIntrospectedMethod()
                            .getAnnotation(SentinelRestTemplate.class);
        // 獲取SentinelRestTemplate註解對象存儲起來
        cache.put(beanName, sentinelRestTemplate);
    }
}
// 判斷bean是否加了SentinelRestTemplate註解而且是RestTemplate
private boolean checkSentinelProtect(RootBeanDefinition beanDefinition,
            Class<?> beanType) {
    return beanType == RestTemplate.class
                && beanDefinition.getSource() instanceof StandardMethodMetadata
                && ((StandardMethodMetadata) beanDefinition.getSource())
                        .isAnnotated(SentinelRestTemplate.class.getName());
}

postProcessAfterInitialization

@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
            throws BeansException {
    if (cache.containsKey(beanName)) {
        // add interceptor for each RestTemplate with @SentinelRestTemplate annotation
        StringBuilder interceptorBeanName = new StringBuilder();
        // 緩存中獲得註解對象
        SentinelRestTemplate sentinelRestTemplate = cache.get(beanName);
        // 生成interceptorBeanName SentinelProtectInterceptor名稱
        interceptorBeanName
                    .append(StringUtils.uncapitalize(
                            SentinelProtectInterceptor.class.getSimpleName()))
                    .append("_")
                    .append(sentinelRestTemplate.blockHandlerClass().getSimpleName())
                    .append(sentinelRestTemplate.blockHandler()).append("_")
                    .append(sentinelRestTemplate.fallbackClass().getSimpleName())
                    .append(sentinelRestTemplate.fallback());
        RestTemplate restTemplate = (RestTemplate) bean;
        // 註冊SentinelProtectInterceptor
        registerBean(interceptorBeanName.toString(), sentinelRestTemplate);
        // 獲取SentinelProtectInterceptor
        SentinelProtectInterceptor sentinelProtectInterceptor = applicationContext
                    .getBean(interceptorBeanName.toString(),
                            SentinelProtectInterceptor.class);
        // 給restTemplate添加攔截器
        restTemplate.getInterceptors().add(sentinelProtectInterceptor);
    }
    return bean;
}
// 註冊SentinelProtectInterceptor類
private void registerBean(String interceptorBeanName,
            SentinelRestTemplate sentinelRestTemplate) {
    // register SentinelProtectInterceptor bean
    DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext
                .getAutowireCapableBeanFactory();
    BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder
                .genericBeanDefinition(SentinelProtectInterceptor.class);
    beanDefinitionBuilder.addConstructorArgValue(sentinelRestTemplate);
    BeanDefinition interceptorBeanDefinition = beanDefinitionBuilder
                .getRawBeanDefinition();
    beanFactory.registerBeanDefinition(interceptorBeanName,
                interceptorBeanDefinition);
}

看到這邊你們就明白了,其實就是給restTemplate添加攔截器來處理。跟Ribbon中的@LoadBalanced原理是同樣的。

SentinelProtectInterceptor

Sentinel RestTemplate 限流的資源規則提供兩種粒度:

  • schema://host:port/path:協議、主機、端口和路徑
  • schema://host:port:協議、主機和端口

這兩種粒度從org.springframework.cloud.alibaba.sentinel.custom.SentinelProtectInterceptor.intercept(HttpRequest, byte[], ClientHttpRequestExecution)方法中能夠看的出來

URI uri = request.getURI();
String hostResource = uri.getScheme() + "://" + uri.getHost()
            + (uri.getPort() == -1 ? "" : ":" + uri.getPort());
String hostWithPathResource = hostResource + uri.getPath();

下面就是根據hostResource和hostWithPathResource進行限流

ContextUtil.enter(hostWithPathResource);
if (entryWithPath) {
    hostWithPathEntry = SphU.entry(hostWithPathResource);
}
hostEntry = SphU.entry(hostResource);
// 執行Http調用
response = execution.execute(request, body);

在後面就是釋放資源,異常處理等代碼,你們本身去了解下。

歡迎加入個人知識星球,一塊兒交流技術,免費學習猿天地的課程(http://cxytiandi.com/course

PS:目前星球中正在星主的帶領下組隊學習Sentinel,等你哦!

微信掃碼加入猿天地知識星球

猿天地

相關文章
相關標籤/搜索