动态配置服务:可以让您以中心化、外部化和动态化的方式管理所有环境的应用配置和服务配置,因此可用来解决上一篇文章末尾的疑问,sentinel无法动态配置。
动态DNS服务:支持自定义配置权重路由,更容易地实现中间层负载均衡
以springboot搭建spring-authorization-server(即认证与资源服务器)
升级为springcloud2021.0.x框架,引入nacos作为注册中心
引入sentinel,进行限流降级熔断配置
引入nacos配置中心
升级sentinel,配合nacos,使项目能动态配置落地所有Feign的降级配置
引入gateway网关,swagger文档工具
待续
pom.xml中引入相关依赖
因为新版cloud对nacos配置做了些许改变,因此引入依赖spring-cloud-starter-bootstrap
参考地址:SpringCloud项目无法读取bootstrap.yml配置文件的解决办法_jackob-94的博客-CSDN博客_读取不到bootstrap.yml
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-config
2021.0.1.0
org.springframework.cloud
spring-cloud-starter-bootstrap
3.1.2
修改项目application配置文件
名字改为bootstrap,增加以下内容
spring:
cloud:
nacos:
config:
server-addr: 192.168.1.69:8848
namespace: 0f0fe57f-d908-48c3-b5a5-78f12a8411ce
file-extension: yaml
prefix: user-server
shared-dataids: base.yaml
增加bootstrap配置信息(我这里指定了命名空间,可使用默认的)
spring:
cloud:
sentinel:
datasource:
ds:
nacos:
server-addr: 192.168.1.69:8848
namespace: 0f0fe57f-d908-48c3-b5a5-78f12a8411ce
dataId: ${spring.application.name}_FEIGN_RULES
groupId: DEFAULT_GROUP
rule-type: degrade
动态配置代码
代码较长,总结了大致思路。新建AutoLoadFeignFuseConfig 配置类,此类功能为,动态获取当前项目下所有带@FeignClient的接口,读取其中各接口,然后根据自定义的命名规则,以及自定义的熔断配置模板进行生成配置文件,存到nacos上。且保存到nacos前,先拉去nacos上的原有配置,手动进行合并。
/**
* @author zxg
*
* 自动加载全局Feign接口,进行全局降级熔断配置
*
* 参考地址1:https://juejin.cn/post/6933072764395847693
* 仓靠地址2:https://blog.csdn.net/dawnsun2013/article/details/124328651
*/
@Component
@Slf4j
public class AutoLoadFeignFuseConfig implements ApplicationRunner , EnvironmentAware {
@Autowired
private NacosConfigManager nacosConfigManager;
private Environment environment;
@Value("${spring.application.name}")
private String applicationName;
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
private static final String GROUP_ID = "DEFAULT_GROUP";
private static final String DATA_ID = "FEIGN_RULES";
private final static String HTTP_PROTOCOL_PREFIX = "http://";
private static final String DYNAMIC_VALUE_PREFIX = "${";
private static final String DYNAMIC_VALUE_SUFFIX = "}";
private String getDataId(){
return applicationName + "_" + DATA_ID;
}
/**
* 加载项目中feign,初始化降级规则
*/
@Override
public void run(ApplicationArguments args) throws Exception {
// 获取启动类对象
Class> mainClass = deduceMainApplication();
log.info("==============开始加载Feign默认规则");
if (mainClass == null){
throw new RuntimeException("没有发现main主程序类");
}
EnableFeignClients enableFeignClients = mainClass.getAnnotation(EnableFeignClients.class);
// 定义feign所在文件夹目录
String[] feignClientPackages;
if (enableFeignClients != null){
String[] feignClientDeclaredPackages = enableFeignClients.basePackages();
// 声明feign的包名
if (feignClientDeclaredPackages.length == 0){
feignClientPackages = new String[]{mainClass.getPackage().getName()};
}else {
feignClientPackages = feignClientDeclaredPackages;
}
// 初始化降级规则
initDegradeRule(feignClientPackages);
}
log.info("==============Feign降级处理完成");
}
/**
* 获取启动类对象
*/
private Class> deduceMainApplication(){
try {
StackTraceElement[] stackTraceElements = new RuntimeException().getStackTrace();
for (StackTraceElement stackTraceElement : stackTraceElements) {
if ("main".equals(stackTraceElement.getMethodName())){
return Class.forName(stackTraceElement.getClassName());
}
}
} catch (ClassNotFoundException e) {
log.warn("获取启动类对象失败:" + e.getMessage());
}
return null;
}
/**
* 初始化降级规则
* @param feignClientPackages feign所在包数组
*/
private void initDegradeRule(String[] feignClientPackages) {
List localDegradeRuleList = new ArrayList<>();
Set feignClientClazz = getFeignClientClazz(feignClientPackages);
// 生成本地配置
for (Class clientClazz : feignClientClazz) {
List rules = initRules(clientClazz);
localDegradeRuleList.addAll(rules);
}
// 获取nacos上远程规则
List remoteDegradeRuleList = fetchRemoteRules();
// 若远程配置不存在,直接上传本地配置
if (remoteDegradeRuleList == null || remoteDegradeRuleList.isEmpty()){
// 上传配置到nacos
pushRules(localDegradeRuleList);
return;
}
// 本地规则 合并 远程规则策略
proess(localDegradeRuleList, remoteDegradeRuleList);
// 上传配置到nacos
pushRules(localDegradeRuleList);
}
/**
* 获取nacos上远程规则
*/
private List fetchRemoteRules() {
try {
return JSONObject.parseArray(nacosConfigManager.getConfigService().getConfig(getDataId(), GROUP_ID, 10000L), DegradeRule.class);
} catch (NacosException e) {
log.error("获取远程Feign配置失败:" + e.getErrMsg());
}
return new ArrayList<>();
}
/**
* 上传配置到nacos
*/
private void pushRules(List rules){
try {
String contentStr = JSONUtil.toJsonStr(rules);
nacosConfigManager.getConfigService().publishConfig(getDataId(), GROUP_ID, contentStr);
} catch (NacosException e) {
log.error("上传Feign降级熔断配置信息到nacos失败");
}
}
/**
* 本地规则 合并 远程规则策略
* @param localDegradeRuleList 本地配置
* @param remoteDegradeRuleList 远程配置
*/
private void proess(List localDegradeRuleList, List 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);
}
}
}
/**
* 扫描Feign的包路径,获取类对象
* @param packageNames 包路径
*/
private Set getFeignClientClazz(String[] packageNames){
Set feignClientClazz = new HashSet<>();
for (String packageName : packageNames) {
packageName = packageName.replace(".**","");
Set> classes = ClassScanner.scanAllPackageByAnnotation(packageName, FeignClient.class);
feignClientClazz.addAll(classes);
}
return feignClientClazz;
}
/**
* 初始化规则
*/
public List initRules(Class clazz){
List degradeRuleList = new ArrayList<>();
FeignClient feignClient = (FeignClient) clazz.getAnnotation(FeignClient.class);
// String url = feignClient.url();
// String serverName = "";
// if (StringUtils.isNotBlank(url)){
// if (url.startsWith(DYNAMIC_VALUE_PREFIX) && url.endsWith(DYNAMIC_VALUE_SUFFIX)){
// url = this.environment.resolvePlaceholders(url);
// }
// }else {
// serverName = feignClient.name();
// }
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
// degradeRuleList.add(buildDegradeRule(getResourceName(url, serverName, method)));
degradeRuleList.add(buildDegradeRule(getResourceName(method)));
}
DegradeRuleManager.loadRules(degradeRuleList);
return degradeRuleList;
}
/**
* 获取资源服务器名
*/
private String getResourceName(Method method) {
String resourceName = "";
RequestMapping methodRequestMapping = method.getAnnotation(RequestMapping.class);
if (null != methodRequestMapping) {
String mrm = methodRequestMapping.value()[0];
resourceName = (mrm.startsWith("/") ? mrm : "/" + mrm);
}
GetMapping methodGetMapping = method.getAnnotation(GetMapping.class);
if (null != methodGetMapping) {
String mpm = methodGetMapping.value()[0];
resourceName = (mpm.startsWith("/") ? mpm : "/" + mpm);
}
PostMapping methodPostMapping = method.getAnnotation(PostMapping.class);
if (null != methodPostMapping) {
String mpm = methodPostMapping.value()[0];
resourceName = (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,查看nacos
因为项目配置文件中配置了相关代码,因此sentinel会自动拉去配置文件,生成对应降级策略,如下图
要点主要是如何获取当前项目的所有feign接口,包括所依赖的jar包下,可以通过反射,也可以通过其他方法。找到后组装成sentinel需要的类存入nacos,然后通过配置文件生成对应的降级熔断策略
后续整合gateway+swagger接口文档部分,请关注后续部分