@SentinelRestTemplate集成RestTemplate空指针异常

实际这是一篇给出临时性解决方案的博文,因为这个问题有望在Spring Cloud Alibaba2.2.3版本以后解决掉,https://github.com/alibaba/spring-cloud-alibaba/issues/1346,这里也介绍了这个Bug的相关情况。

在使用RestTemplate的时候,一般情况下遇到网络故障或服务超时会报出例如:java.net.SocketTimeoutException: Read timed out, java.net.SocketException: connetct time out  这种通信异常的报错,但是当使用了@SentinelRestTemplate注解后,开启了Sentinel的限流降级以后,SentinelProtectInterceptor拦截器中com.alibaba.cloud.sentinel.custom.SentinelProtectInterceptor.intercept(HttpRequest, byte[], ClientHttpRequestExecution) 方法,Catch方法中,会吞掉错误的异常抛出,使得response返回null, 这个时候,RestTemplate调用的Response变为null,致使程序会报出 java.lang.NullPointerException的错误。

		Entry hostEntry = null;
		Entry hostWithPathEntry = null;
		ClientHttpResponse response = null;
		try {
			hostEntry = SphU.entry(hostResource, EntryType.OUT);
			if (entryWithPath) {
				hostWithPathEntry = SphU.entry(hostWithPathResource, EntryType.OUT);
			}
			response = execution.execute(request, body);
			if (this.restTemplate.getErrorHandler().hasError(response)) {
				Tracer.trace(
						new IllegalStateException("RestTemplate ErrorHandler has error"));
			}
		}
		catch (Throwable e) {
			if (!BlockException.isBlockException(e)) {
				Tracer.trace(e);
                // 这里会吞噬掉错误不再像外部抛出
			}
			else {
				return handleBlockException(request, body, execution, (BlockException) e);
			}
		}
		finally {
			if (hostWithPathEntry != null) {
				hostWithPathEntry.exit();
			}
			if (hostEntry != null) {
				hostEntry.exit();
			}
		}
		return response; // 而Response直接返回为null

sca的官方也在https://github.com/alibaba/spring-cloud-alibaba/pull/1374 这里,Rethrow non block exception and trace entry,
解决后的代码 https://github.com/alibaba/spring-cloud-alibaba/pull/1374/commits/4c5bf778632153bfbaf5dc092533f4c7303eb9d7

			catch (Throwable e) {
				if (!BlockException.isBlockException(e)) {
					Tracer.trace(e);
					if (entryWithPath) {
						Tracer.traceEntry(e, hostWithPathEntry);
					}
					Tracer.traceEntry(e, hostEntry);
					throw (IOException) e;
                    // 增加了对非限流降级错误的异常抛出
				}
				else {
					return handleBlockException(request, body, execution, (BlockException) e);
				}
			}

在Catch中增加了rethrow exception的处理,如果还能够忍受这种情况持续下去直到Sentinel官方发布新版本去解决这个问题。那么就可以阅读到此了,只需要默默等待。

如果你无法忍受这个情况,在新版本发布前解决掉,那么只能自己想办法了。解决起来也很简单,就是覆写Sentinel的拦截器,

1、去掉 @SentinelRestTemplate 注解的使用,先临时注释掉吧,待后续官方解决后再恢复回来吧

2、自定义一个SentinelProtectInterceptor 拦截器,同时修改不抛异常的bug,

@Component
	@ConditionalOnClass(name = "org.springframework.web.client.RestTemplate")
	@ConditionalOnProperty(name = "resttemplate.sentinel.enabled", havingValue = "true", matchIfMissing = true)	
	public class SentinelProtectInterceptor implements ClientHttpRequestInterceptor {
		@Override
		public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
				throws IOException {
			URI uri = request.getURI();
			String hostResource = request.getMethod().toString() + ":" + uri.getScheme()
					+ "://" + uri.getHost()
					+ (uri.getPort() == -1 ? "" : ":" + uri.getPort());
			String hostWithPathResource = hostResource + uri.getPath();
			boolean entryWithPath = true;
			if (hostResource.equals(hostWithPathResource)) {
				entryWithPath = false;
			}
			hostWithPathResource = SentinelRestTemplateUtil.UrlCleanerUtil.urlCleaner(hostWithPathResource);
			
			Entry hostEntry = null;
			Entry hostWithPathEntry = null;
			ClientHttpResponse response = null;
			try {
				hostEntry = SphU.entry(hostResource, EntryType.OUT);
				if (entryWithPath) {
					hostWithPathEntry = SphU.entry(hostWithPathResource, EntryType.OUT);
				}
				response = execution.execute(request, body);
				if (staticRestTemplate().getErrorHandler().hasError(response)) {
					Tracer.trace(
							new IllegalStateException("RestTemplate ErrorHandler has error"));
				}
			}
			catch (Throwable e) {
				if (!BlockException.isBlockException(e)) {
					Tracer.trace(e);
					if (entryWithPath) {
						Tracer.traceEntry(e, hostWithPathEntry);
					}
					Tracer.traceEntry(e, hostEntry);
					throw (IOException) e;
				}
				else {
					return handleBlockException(request, body, execution, (BlockException) e);
				}
			}
			finally {
				if (hostWithPathEntry != null) {
					hostWithPathEntry.exit();
				}
				if (hostEntry != null) {
					hostEntry.exit();
				}
			}
			return response;
		}
	}

3、   注入这个拦截器备用

	@Autowired(required = false)
	private SentinelProtectInterceptor sentinelProtectInterceptor;

4、RestTemplate 实例化的时候增加自定义Sentinel拦截器的实现。

@Bean
// @SentinelRestTemplate(fallback = "fallback", fallbackClass = SentinelRestTemplateUtil.FallbackUtil.class, blockHandler = "blockHandler", blockHandlerClass = SentinelRestTemplateUtil.BlockHandlerUtil.class, urlCleaner = "urlCleaner", urlCleanerClass = SentinelRestTemplateUtil.UrlCleanerUtil.class)
public RestTemplate restTemplate() {
	RestTemplate restTemplate = new RestTemplate();
    // 其他代码省略.....................

    // 这里增加拦截器
    if (sentinelProtectInterceptor != null) {
	    restTemplate.getInterceptors().add(sentinelProtectInterceptor);
	}
    return restTemplate;
}

5、至于 fallback,blockhandler,urlCleaner的代码,自己处理即可

@Slf4j
public class SentinelRestTemplateUtil {
	
	public static class FallbackUtil {
		/* handle degrade isDegradeFailure 降级  */
		// com.alibaba.cloud.sentinel.custom.SentinelProtectInterceptor.handleBlockException(HttpRequest, byte[], ClientHttpRequestExecution, BlockException)
		public static SentinelClientHttpResponse fallback(HttpRequest request,
				byte[] body, ClientHttpRequestExecution execution, BlockException ex) {
			log.error("RestTemplate call url={}, 已触发降级",request.getURI(), ex);
			return new SentinelClientHttpResponse("{\"msg\":\"restTemplate request fallback success\"}");
		}
	}
	
	public static class BlockHandlerUtil {
		/* handle flow 限流  */
		// com.alibaba.cloud.sentinel.custom.SentinelProtectInterceptor.handleBlockException(HttpRequest, byte[], ClientHttpRequestExecution, BlockException)
		public static SentinelClientHttpResponse blockHandler(HttpRequest request,
				byte[] body, ClientHttpRequestExecution execution, BlockException ex) {
			log.error("RestTemplate call url=:{},已触发限流",request.getURI(), ex);
			return new SentinelClientHttpResponse("{\"msg\":\"restTemplate request blockHandler success\"}");
		}
	}
	
	public static class UrlCleanerUtil {
		/* Url清洗 hostWithPathResource */
		// com.alibaba.cloud.sentinel.custom.SentinelProtectInterceptor.intercept(HttpRequest, byte[], ClientHttpRequestExecution)
		public static String urlCleaner(String url) {
			return url;
		}
	}
	
}

通过以上的配置便可以临时解决掉Sentinel遇到异常返回null的Response而不抛出异常原因的问题。待官方版本解决发布后再考虑还原回去继续使用@SentinelRestTemplate注解即可。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值