一、fallback降级
1、在 @FeignClient 加上注解
@FeignClient(value = "server",fallback = ServerApiFallBack.class)
@Component
public interface ServerApi {
@GetMapping("/server/test/{id}")
Map test(@PathVariable String id);
}
2、ServerApiFallBack 熔断类
@Component
public class ServerApiFallBack implements ServerApi{
@Override
public Map test(String id) {
ImmutableMap map = ImmutableMap.of("code", 500, "reason", "服务不可用");
return map;
}
}
3、Controller接口
@RestController
@RequestMapping("/client")
public class ClientController {
@GetMapping("/test/{id}")
public Map test(@PathVariable String id){
return serverApi.test(id);
}
}
4、此时调用接口,服务出现异常不步可用时,会触发 fallback 服务熔断,具体逻辑在 SentinelInvocationHandler 中实现
catch (Throwable ex) {
...
if (fallbackFactory != null) {
try {
//回调服务熔断方法
Object fallbackResult = fallbackMethodMap.get(method)
.invoke(fallbackFactory.create(ex), args);
return fallbackResult;
}
...
二、SentinelResource流控降级
1、在 @FeignClient 加上注解
@FeignClient(value = "server",fallback = ServerApiFallBack.class)
@Component
public interface ServerApi {
@GetMapping("/server/test/{id}")
Map test(@PathVariable String id);
}
2、Controller接口
@SentinelResource(value = "sentinel-test", blockHandler = "blockHandler")
@GetMapping("/test/{id}")
public Map test(@PathVariable String id){
return serverApi.test(id);
}
public Map blockHandler(String id ,BlockException e) {
ImmutableMap map = ImmutableMap.of(
"code", 500,
"reason", "sentinel服务流量控制",
"error", e);
return map;
}
3、配置熔断规则
在sentine控制台设置熔断规则
4、访问接口
多次访问接口,会发生熔断,结果:
{
"code": 500,
"reason": "sentinel服务流量控制",
"error": {
...
}
此时熔断逻辑是在SentinelResourceAspect中
try {
entry = SphU.entry(resourceName, resourceType, entryType, pjp.getArgs());
Object result = pjp.proceed();
return result;
} catch (BlockException ex) {
//熔断
return handleBlockException(pjp, annotation, ex);
}
根据@SentinelResource中配置的blockHandler、blockHandlerClass、fallback、defaultFallback、fallbackClass调用熔断的方法。
三、Sentinel全局熔断
1、给接口设置熔断规则,例如给controller接口设置熔断规则,资源名是/client/test/{id},当触发熔断规则时,具体逻辑是在SentinelWebInterceptor 中处理,它实现了springmvc 的 HandlerInterceptor 拦截器接口。在preHandle方法中触发接口的流控。
catch (BlockException e) {
try {
//处理熔断
handleBlockException(request, response, e);
}
protected void handleBlockException(HttpServletRequest request, HttpServletResponse response, BlockException e)
throws Exception {
//配置中存在BlockExceptionHandler时,调用其handle()方法
if (baseWebMvcConfig.getBlockExceptionHandler() != null) {
baseWebMvcConfig.getBlockExceptionHandler().handle(request, response, e);
} else {
// Throw BlockException directly. Users need to handle it in Spring global exception handler.
throw e;
}
}
2、baseWebMvcConfig 中配置 BlockExceptionHandler
在SentinelWebAutoConfiguration配置类中,创建了SentinelWebMvcConfig
@Autowired
private Optional<BlockExceptionHandler> blockExceptionHandlerOptional;
@Bean
@ConditionalOnProperty(name = "spring.cloud.sentinel.filter.enabled",
matchIfMissing = true)
public SentinelWebMvcConfig sentinelWebMvcConfig() {
SentinelWebMvcConfig sentinelWebMvcConfig = new SentinelWebMvcConfig();
sentinelWebMvcConfig.setHttpMethodSpecify(properties.getHttpMethodSpecify());
sentinelWebMvcConfig.setWebContextUnify(properties.getWebContextUnify());
//自定义 BlockExceptionHandler
if (blockExceptionHandlerOptional.isPresent()) {
blockExceptionHandlerOptional
.ifPresent(sentinelWebMvcConfig::setBlockExceptionHandler);
}
else {
//自定义熔断时跳转页面 blockPage
if (StringUtils.hasText(properties.getBlockPage())) {
sentinelWebMvcConfig.setBlockExceptionHandler(((request, response,
e) -> response.sendRedirect(properties.getBlockPage())));
}
else {
//默认的 DefaultBlockExceptionHandler
sentinelWebMvcConfig
.setBlockExceptionHandler(new DefaultBlockExceptionHandler());
}
}
urlCleanerOptional.ifPresent(sentinelWebMvcConfig::setUrlCleaner);
requestOriginParserOptional.ifPresent(sentinelWebMvcConfig::setOriginParser);
return sentinelWebMvcConfig;
}
3、自定义 BlockExceptionHandler
@Component
public class GlobalBlockExceptionHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
response.setStatus(500);
ServletOutputStream out = response.getOutputStream();
out.print(new String("触发 Sentinel 全局熔断处理".getBytes(StandardCharsets.UTF_8),"iso-8859-1"));
out.flush();
out.close();
}
}