乐趣区

Spring-Cloud-Alibaba-Sentinel对RestTemplate的支持

Spring Cloud Alibaba Sentinel 支持对 RestTemplate 的服务调用使用 Sentinel 进行保护,在构造 RestTemplate bean 的时候需要加上 @SentinelRestTemplate 注解。

需要注意的是目前的版本 spring-cloud-starter-alibaba-sentinel.0.2.1.RELEASE 在配置 RestTemplate 的时候有个 Bug,需要将配置放在 Spring Boot 的启动类中,也就是 @SpringBootApplication 注解所在的类。

如果单独放在 @Configuration 标记的类中目前是有问题的,当然后续版本中会进行修复,对应的问题描述:https://github.com/spring-cloud-incubator/spring-cloud-alibaba/issues/227

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

限流后处理的方法

  • blockHandlerClass

限流后处理的类

  • fallback

熔断后处理的方法

  • fallbackClass

熔断后处理的类

异常处理类定义需要注意的是该方法的参数跟返回值跟 org.springframework.http.client.ClientHttpRequestInterceptor#interceptor 方法一致,其中参数多出了一个 BlockException 参数用于获取 Sentinel 捕获的异常。

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 接口。

核心方法就是重写的 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,等你哦!

退出移动版