Spring Cloud Alibaba Sentinel对RestTemplate的支持

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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注解所在的类。br/>如果单独放在@Configuration标记的类中目前是有问题的,当然后续版本中会进行修复,对应的问题描述:
https://github.com/spring-cloud-incubator/spring-cloud-alibaba/issues/227

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

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

1.  public class ExceptionUtil {
2.      public static SentinelClientHttpResponse handleException(HttpRequest request,
3.              byte[] body, ClientHttpRequestExecution execution, BlockException ex) {
4.          System.err.println("Oops: " + ex.getClass().getCanonicalName());
5.          return new SentinelClientHttpResponse("custom block info");
6.      }
7.  
8.      public static SentinelClientHttpResponse fallback(HttpRequest request,
9.              byte[] body, ClientHttpRequestExecution execution, BlockException ex) {
10.         System.err.println("fallback: " + ex.getClass().getCanonicalName());
11.         return new SentinelClientHttpResponse("custom fallback info");
12.     }
13. }

原理剖析
核心代码在org.springframework.cloud.alibaba.sentinel.custom.SentinelBeanPostProcessor中,实现了MergedBeanDefinitionPostProcessor接口,MergedBeanDefinitionPostProcessor接口实现了BeanPostProcessor接口。
核心方法就是重写的postProcessMergedBeanDefinition和postProcessAfterInitialization。
postProcessMergedBeanDefinition

1.  private ConcurrentHashMap<String, SentinelRestTemplate> cache = new ConcurrentHashMap<>();
2.  
3.  @Override
4.  public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition,
5.              Class<?> beanType, String beanName) {
6.      if (checkSentinelProtect(beanDefinition, beanType)) {
7.          SentinelRestTemplate sentinelRestTemplate = ((StandardMethodMetadata) beanDefinition
8.                      .getSource()).getIntrospectedMethod()
9.                              .getAnnotation(SentinelRestTemplate.class);
10.         // 获取SentinelRestTemplate注解对象存储起来
11.         cache.put(beanName, sentinelRestTemplate);
12.     }
13. }
14. // 判断bean是否加了SentinelRestTemplate注解并且是RestTemplate
15. private boolean checkSentinelProtect(RootBeanDefinition beanDefinition,
16.             Class<?> beanType) {
17.     return beanType == RestTemplate.class
18.                 && beanDefinition.getSource() instanceof StandardMethodMetadata
19.                 && ((StandardMethodMetadata) beanDefinition.getSource())
20.                         .isAnnotated(SentinelRestTemplate.class.getName());
21. }

postProcessAfterInitialization

1.  @Override
2.  public Object postProcessAfterInitialization(Object bean, String beanName)
3.              throws BeansException {
4.      if (cache.containsKey(beanName)) {
5.          // add interceptor for each RestTemplate with @SentinelRestTemplate annotation
6.          StringBuilder interceptorBeanName = new StringBuilder();
7.          // 缓存中得到注解对象
8.          SentinelRestTemplate sentinelRestTemplate = cache.get(beanName);
9.          // 生成interceptorBeanName SentinelProtectInterceptor名称
10.         interceptorBeanName
11.                     .append(StringUtils.uncapitalize(
12.                             SentinelProtectInterceptor.class.getSimpleName()))
13.                     .append("_")
14.                     .append(sentinelRestTemplate.blockHandlerClass().getSimpleName())
15.                     .append(sentinelRestTemplate.blockHandler()).append("_")
16.                     .append(sentinelRestTemplate.fallbackClass().getSimpleName())
17.                     .append(sentinelRestTemplate.fallback());
18.         RestTemplate restTemplate = (RestTemplate) bean;
19.         // 注册SentinelProtectInterceptor
20.         registerBean(interceptorBeanName.toString(), sentinelRestTemplate);
21.         // 获取SentinelProtectInterceptor
22.         SentinelProtectInterceptor sentinelProtectInterceptor = applicationContext
23.                     .getBean(interceptorBeanName.toString(),
24.                             SentinelProtectInterceptor.class);
25.         // 给restTemplate添加拦截器
26.         restTemplate.getInterceptors().add(sentinelProtectInterceptor);
27.     }
28.     return bean;
29. }
30. // 注册SentinelProtectInterceptor类
31. private void registerBean(String interceptorBeanName,
32.             SentinelRestTemplate sentinelRestTemplate) {
33.     // register SentinelProtectInterceptor bean
34.     DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext
35.                 .getAutowireCapableBeanFactory();
36.     BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder
37.                 .genericBeanDefinition(SentinelProtectInterceptor.class);
38.     beanDefinitionBuilder.addConstructorArgValue(sentinelRestTemplate);
39.     BeanDefinition interceptorBeanDefinition = beanDefinitionBuilder
40.                 .getRawBeanDefinition();
41.     beanFactory.registerBeanDefinition(interceptorBeanName,
42.                 interceptorBeanDefinition);
43. }

看到这边大家就明白了,其实就是给restTemplate添加拦截器来处理。跟Ribbon中的@LoadBalanced原理是一样的。
SentinelProtectInterceptor
Sentinel RestTemplate 限流的资源规则提供两种粒度:
? schema://host:port/path:协议、主机、端口和路径
? schema://host:port:协议、主机和端口
这两种粒度从org.springframework.cloud.alibaba.sentinel.custom.SentinelProtectInterceptor.intercept(HttpRequest, byte[], ClientHttpRequestExecution)方法中可以看的出来

1.  URI uri = request.getURI();
2.  String hostResource = uri.getScheme() + "://" + uri.getHost()
3.              + (uri.getPort() == -1 ? "" : ":" + uri.getPort());
4.  String hostWithPathResource = hostResource + uri.getPath();
下面就是根据hostResource和hostWithPathResource进行限流
1.  ContextUtil.enter(hostWithPathResource);
2.  if (entryWithPath) {
3.      hostWithPathEntry = SphU.entry(hostWithPathResource);
4.  }
5.  hostEntry = SphU.entry(hostResource);
6.  // 执行Http调用
7.  response = execution.execute(request, body);

在后面就是释放资源,异常处理等代码,大家自己去了解下。

加入星球特权

1、从前端到后端玩转Spring Cloud
2、实战分库分表中间件Sharding-JDBC
3、实战分布式任务调度框架Elastic Job
4、配置中心Apollo实战
5、高并发解决方案之缓存
6、更多课程等你来解锁,20+课程

技术图片

尹吉欢
我不差钱啊
喜欢作者


以上是关于Spring Cloud Alibaba Sentinel对RestTemplate的支持的主要内容,如果未能解决你的问题,请参考以下文章

Spring Cloud Alibaba环境搭建

Spring Cloud Alibaba全家桶——Spring Cloud Alibaba介绍

Spring Cloud Alibaba系列教程——Spring Cloud Alibaba开篇

spring boot 整合spring cloud alibaba

spring boot 整合spring cloud alibaba

深度剖析Spring Cloud Alibaba系列——如何兼容Spring Cloud