zoukankan      html  css  js  c++  java
  • 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,等你哦!

    微信扫码加入猿天地知识星球

    猿天地

  • 相关阅读:
    intel 蓝牙驱动安装时报错
    H310C,B365,M.2 NVME SSD,USB3.0,安装 WIN7 64 位
    C# .NET 判断输入的字符串是否只包含数字和英文字母
    squid http,https, 代理,默认端口3128
    C# .net mvc web api 返回 json 内容,过滤值为null的属性
    centos7安装python-3.5
    systemctl命令完全指南
    Centos7中systemctl命令详解
    Python if 和 for 的多种写法
    CentOS 7.0,启用iptables防火墙
  • 原文地址:https://www.cnblogs.com/yinjihuan/p/10811889.html
Copyright © 2011-2022 走看看