Spring Cloud Alibaba Sentinel对RestTemplate的支持_第1张图片
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 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的支持_第2张图片

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