Toggle navigation
架构师课程
极客时间
开源
文章
博客
SpringCloud
CloudAlibaba
SpringBoot
关于
登录
|
注册
支付宝扫一扫帮助发展吧~
微信扫一扫帮助发展吧~
更多技术分享尽在公众号 ~ 关注下吧!
Spring Cloud Alibaba Sentinel对RestTemplate的支持
尹吉欢
2019-05-05
0条评论
405人阅读
版权声明:转载请先联系作者并标记出处。
Sentinel
去注册
去登录
登录后发表
去注册
去登录
登录后发表
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](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
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) 