随着业务的发展,微服务势在必行;最近接手一个老项目使用sentinel+nacos做的熔断限流,熔断规则配置的都一样,感觉有点冗余,开发效率低下。想着改造升级一下,由于服务依赖接口都是通过feignClient调用的,对feignClient调用接口,自动扫描动态实现熔断规则。
一、首页扫描feignClient依赖接口,动态生成熔断规则,推送nacos
-
实现ApplicationRunner接口,重写run方法,根据EnableFeignClients指定包加载feignClient类集合
@Override public void run(ApplicationArguments args) { Class<?> mainClass = deduceMainApplicationClass(); log.info("开始加载默认规则,mainClass:{}", mainClass); if (mainClass == null) { throw new RuntimeException("can not fount main class"); } EnableFeignClients enableFeignClientsAnnotation = mainClass.getAnnotation(EnableFeignClients.class); if (enableFeignClientsAnnotation != null) { String[] feignClientPackages; String[] feignClientDeclaredPackages = enableFeignClientsAnnotation.basePackages(); //声明了feignClient的包名 if (feignClientDeclaredPackages.length == 0) { feignClientPackages = new String[]{mainClass.getPackage().getName()}; } else { feignClientPackages = feignClientDeclaredPackages; } //初始化降级规则 initDeGradeRule(feignClientPackages); } log.info("默认降级规则处理完成"); }
-
根据feignClient类集合,初始化熔断规则,并推送nacos
private void initDeGradeRule(String[] feignClientPackages) { List<DegradeRule> localDegradeRuleList = new ArrayList<>(); Set<Class> feignClientClass = getFeignClientClass(feignClientPackages); for (Class clientClass : feignClientClass) { List<DegradeRule> rules = initRules(clientClass); localDegradeRuleList.addAll(rules); } List<DegradeRule> remoteDegradeRuleList = fetchRemoteRules(); //远程nacos没有规则,那就直接利用本地规则 if (remoteDegradeRuleList == null || remoteDegradeRuleList.isEmpty()) { pushRules(localDegradeRuleList); return; } //本地规则 合并 远程规则策略 proess(localDegradeRuleList, remoteDegradeRuleList); //推送本地规则,到nacos pushRules(localDegradeRuleList); }
-
获取FeignClient class set
private Set<Class> getFeignClientClass(String[] packageNames) { ClassScanner classScanner = new ClassScanner(); Set<Class> feignClientClass = new HashSet<>(); for (String packageName : packageNames) { feignClientClass.addAll(classScanner.scan(packageName, FeignClient.class)); } return feignClientClass; }
-
根据feignClient class对象,提取请求映射
private final static String HTTP_PROTOCOL_PREFIX = "http://"; private final static String ANNOTATION_VALUE_PREFIX = "${"; private final static String ANNOTATION_VALUE_SUFFIX = "}"; public List<DegradeRule> initRules(Class cla) { List<DegradeRule> degradeRuleList = new ArrayList<>(); FeignClient feignClient = (FeignClient) cla.getAnnotation(FeignClient.class); String classRequestMappingUrl = ""; RequestMapping classRequestMapping = (RequestMapping) cla.getAnnotation(RequestMapping.class); if (null != classRequestMapping) { classRequestMappingUrl = classRequestMapping.value()[0]; } String serviceName = feignClient.name(); if (serviceName.startsWith(ANNOTATION_VALUE_PREFIX) && serviceName.endsWith(ANNOTATION_VALUE_SUFFIX)) { serviceName = this.environment.resolvePlaceholders(serviceName); } Method[] methods = cla.getDeclaredMethods(); for (Method method : methods) { degradeRuleList.add(buildDegradeRule(getResourceName(classRequestMappingUrl, serviceName, method))); } DegradeRuleManager.loadRules(degradeRuleList); return degradeRuleList;
-
根据请求映射,拼接资源标识
private String getResourceName(String crmu, String serviceName, Method method) { crmu = crmu.startsWith("/") ? crmu : "/" + crmu; String resourceName = ""; RequestMapping methodRequestMapping = method.getAnnotation(RequestMapping.class); if (null != methodRequestMapping) { String mrm = methodRequestMapping.value()[0]; resourceName = HTTP_PROTOCOL_PREFIX + serviceName + crmu + (mrm.startsWith("/") ? mrm : "/" + mrm); } PostMapping methodPostMapping = method.getAnnotation(PostMapping.class); if (null != methodPostMapping) { String mpm = methodPostMapping.value()[0]; resourceName = HTTP_PROTOCOL_PREFIX + serviceName + crmu + (mpm.startsWith("/") ? mpm : "/" + mpm); } return resourceName; }
-
使用资源标识,构建默认熔断规则
private DegradeRule buildDegradeRule(String resourceName) { DegradeRule rule = new DegradeRule(); //设置资源名 rule.setResource(resourceName); //设置降级规则 TR 10 ms rule.setCount(200); // 规则类型 RT rule.setGrade(RuleConstant.DEGRADE_GRADE_RT); // 窗口时间 rule.setTimeWindow(10); rule.setMinRequestAmount(3); rule.setStatIntervalMs(30000); rule.setSlowRatioThreshold(0.6); return rule; }
-
获取远程nacos配置
private List<DegradeRule> fetchRemoteRules() { return JSONObject.parseArray(nacosService.getConfig(dataId, groupId), DegradeRule.class); }
-
本地规则 合并 远程规则策略
private void proess(List<DegradeRule> localDegradeRuleList, List<DegradeRule> remoteDegradeRuleList) { for (DegradeRule rule : remoteDegradeRuleList) { if (localDegradeRuleList.contains(rule)) { DegradeRule ldr = localDegradeRuleList.get(localDegradeRuleList.indexOf(rule)); if (ldr.equals(rule)) { continue; } localDegradeRuleList.remove(ldr); localDegradeRuleList.add(rule); } else { localDegradeRuleList.add(rule); } } }
-
推送熔断规则到nacos
private void pushRules(List<DegradeRule> localDegradeRuleList) { SerializeConfig serializeConfig = new SerializeConfig(); serializeConfig.propertyNamingStrategy = PropertyNamingStrategy.CamelCase; String contentStr = JSON.toJSONString(localDegradeRuleList, serializeConfig, SerializerFeature.PrettyFormat); nacosService.publish(dataId, groupId, contentStr, ConfigType.JSON.getType()); }
二、使用AOP切feign调用,动态增加熔断逻辑执行
10. 参考Sentinel的 SentinelResourceAspect 继承AbstractSentinelAspectSupport 实现feign调用拦截
@Aspect
@Slf4j
@Component
public class FeignMethodAspect extends AbstractSentinelAspectSupport {
@Around("within(feign.Client+)")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
log.info("within(feign.Client+) pjp {}, args:{}", pjp, pjp.getArgs());
String resourceName = pjp.getArgs()[0].toString().split(" ")[1];
Entry entry = null;
try {
Object var18;
try {
entry = SphU.entry(resourceName, ResourceTypeConstants.COMMON, EntryType.OUT, pjp.getArgs());
Object result = pjp.proceed();
var18 = result;
return var18;
} catch (BlockException var15) {
String resource = var15.getRule().getResource();
log.error("FeignClient接口触发了resource:{}规则,接口已经被“限流/熔断/降级”,业务系统返回默认null值,异常信息:{}", resource, var15);
return null;
} catch (Throwable var16) {
this.traceException(var16);
}
} finally {
if (entry != null) {
entry.exit(1, pjp.getArgs());
}
}
return null;
}
}
- 配置使用JDK动态代理,否则拦截feignClient 调用会失败
spring:
aop:
proxy-target-class: false
- 推送nacosService 依赖NacosConfigManager
public Boolean publish(String dataId, String group, String content, String type) {
try {
return nacosConfigManager.getConfigService().publishConfig(dataId, group, content, type);
} catch (NacosException e) {
log.error("NacosService publish e:{}", e);
}
return false;
}
- 获取nacos配置
public String getConfig(String dataId, String group) {
try {
return nacosConfigManager.getConfigService().getConfig(dataId, group, 10000l);
} catch (NacosException e) {
log.error("NacosService publish e:{}", e);
}
return Strings.EMPTY;
}
见字如面,感谢您的耐心阅读,希望对您有所帮助,点赞是一种美德,祝您工作顺利 步步高升!