写点什么

不改一行源码,实现 sentinel-dashboard 所有配置支持 apollo 持久化

作者:铁匠
  • 2022 年 8 月 02 日
  • 本文字数:3313 字

    阅读完需:约 11 分钟

sentinel-dashboard 为什么需要定制

Sentinel 是阿里巴巴开源的流量治理组件。功能非常齐全,包括了:请求过滤、降级、限流、流量监控等功能。如果对 sentinel 还不是很了解可以查看官方文档:https://sentinelguard.io/zh-cn/docs/basic-api-resource-rule.html


虽然 sentinel 的设计非常优秀,基本上满足了流量治理的所有需求,但是 sentinel-dashboard(管理后台)的配置都是存储在内存,在服务重启后就会丢失。所以 sentinel 目前是不具备在生产环境上使用的。即使 sentinel 客户端是支持了从 apollo、consul、etcd、eureka、nacos、redis、spring-cloud-config、zookeeper 读取配置,但是如果不使用 dashboard,直接手动修改配置的话,官网也没有提供详细的参数配置文档,想知道哪些参数可配置,需要自己查看源码,使用上非常不友好。


而这个问题早在 2020 年就有人提出来了(github issue) dashboard 配置持久化功能,但是官方至今(2022-07)依然没有实现这个功能。


https://github.com/alibaba/Sentinel/issues/1759

https://github.com/alibaba/Sentinel/issues/2179


值得一提的是,阿里云的商业版 sentinel-dashboard 是有这个功能的。并且在 test 代码中可以看到有对应持久化实现的。所以这很明显官方并不想在开源版实现这个功能,需要我们自己去实现。这其中的原由已经非常明显了。



方案选型

目前已经实现的组件中,sentinel 客户端已经支持:


  • apollo

  • consul

  • etcd

  • eureka

  • nacos

  • redis

  • spring-cloud-config

  • zookeeper



以最小化改动原则,我们可以从上面其中一个作为持久化存储方案,否则就需要自己再开发一套客户端同步数据组件。


这里我选择 apollo,理由是:apollo 作为配置中心,有丰富的配置功能,与其他方案如 nacos 都要完善和稳定许多。而其他如 redis、zookeeper 在数据排查方面都不是太方便。

源码分析

sentinel-dashboard 的源码结构非常简单。后端使用 spring-boot,前端使用 angular1。


我们打开浏览器抓包工具,在界面上操作增删改查对应配置,就可以知道对应的接口是多少,然后通过接口路径找到对应的 Controller,继续往下跟踪就可以知道完整的处理流程了。

例如:新增网关流控规则的接口是 /gateway/flow/new.json


通过分析不难发现,不管是什么配置,对应增删改查的接口路径都是类似的。


sentinel 规则总共有 7 中类型,都实现了 RuleEntity 接口


我们需要实现的也是将这 7 种数据类型持久化到 apollo。


从 sentinel 的架构设计上可以知道分为 sentinel 客户端(也就是我们的应用)和 sentinel-dashboard(管理后台)。


通过分析 FlowControllerV1 源码,可以知道配置读写都是通过 SentinelApiClient 来完成的。


  • 读数据:通过 SentinelApiClient 请求客户端,拉取配置,然后更新到内存


  • 写数据:先保存到内存,然后调用 SentinelApiClient 将请求同步到客户端


实现

对于在生产环境中使用 Sentinel,官网文档中给我们介绍了几种模式。通过上面源码分析的流程实现的就是原始模式,我们的改造方案是要实现推模式。


对于改造方案,如果做过这方面调研的同学,找到的资料基本上都是只实现了流量控制规则持久化,而剩下其他 6 中规则并没有实现持久化,包括姚秋辰(姚半仙)老师在极客时间上的专栏《Spring Cloud 微服务项目实战》第 20 章节Sentinel 实战:如何接入 Nacos 实现规则持久化? 中的方案也只是把流控规则配置做了持久化。大家可以自己搜索一下,这里不再赘述。


以上方案都存在几个不足。


  1. 只实现了流控规则持久化

  2. 需要修改源码(包括前端代码),不放面后续滚动升级

  3. 如果 7 中类型数据都做持久化的,那需要修改的地方会比较多


通过上面源码分析可以知道,其实数据拉取和推送都是通过SentinelApiClient 的 fetchXXX(拉取数据)和 setXXX, modifyXXX(推送数据)方法来实现的,所以我们只要把对应的方法改成从 apollo 拉取数据和将数据推送到 apollo 上就可以了,


因为 SentinelApiClient 没有定义接口,所以要在不改变源码的情况下改变它的默认行为,就要通过 aop 来实现了。


下面是实现网关流控规则读写 apollo 的示例代码。


@Aspect@Componentpublic class SentinelApiClientAspect {
private static final Logger LOG = LoggerFactory.getLogger(SentinelApiClientAspect.class);
@SuppressWarnings("PMD.ThreadPoolCreationRule") private static final ExecutorService EXECUTOR = Executors.newSingleThreadExecutor( new NamedThreadFactory("sentinel-dashboard-api-aspect"));
@Resource private DynamicRuleStoreFactory factory;

@Pointcut("execution(public * com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient.fetchGatewayFlowRules(..))") public void fetchGatewayFlowRulesPointcut() { }
@Pointcut("execution(public * com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient.modifyGatewayFlowRules(..))") public void modifyGatewayFlowRulesPointcut() { }
/** * 拉取网关流控规则配置 */ @Around("fetchGatewayFlowRulesPointcut()") public Object fetchGatewayFlowRules(final ProceedingJoinPoint pjp) throws Throwable { return fetchRulesWithCompletableFuture(pjp, RuleType.GW_FLOW); }
/** * 推送网关流控规则配置 */ @Around("modifyGatewayFlowRulesPointcut()") public Object modifyGatewayFlowRules(final ProceedingJoinPoint pjp) throws Throwable { return publishRules(pjp, RuleType.GW_FLOW); }
// 中间省略了部分代码,完整代码可以从 github 查看
private Object fetchRules(ProceedingJoinPoint pjp, RuleType ruleType) throws Throwable { DynamicRuleStore<?> dynamicRuleStore = factory.getDynamicRuleStoreByType(ruleType); if (dynamicRuleStore == null) { return pjp.proceed(); } Object[] args = pjp.getArgs(); String app = (String) args[0]; return dynamicRuleStore.getRules(app); }
private CompletableFuture<Object> fetchRulesWithCompletableFuture(ProceedingJoinPoint pjp, RuleType ruleType) { return CompletableFuture.supplyAsync(() -> { try { return fetchRules(pjp, ruleType); } catch (Throwable e) { throw new RuntimeException("fetch rules error: " + ruleType.getName(), e); } }, EXECUTOR); }

@SuppressWarnings("unchecked") private boolean publishRules(ProceedingJoinPoint pjp, RuleType ruleType) { DynamicRuleStore<RuleEntity> dynamicRuleStore = factory.getDynamicRuleStoreByType(ruleType); Object[] args = pjp.getArgs(); String app = (String) args[0]; List<RuleEntity> rules = (List<RuleEntity>) args[3]; try { dynamicRuleStore.publish(app, rules); return true; } catch (Exception e) { LOG.error("publish rules error", e); return true; } }
private CompletableFuture<Void> publishRulesWithCompletableFuture(ProceedingJoinPoint pjp, RuleType ruleType) { return CompletableFuture.runAsync(() -> publishRules(pjp, ruleType), EXECUTOR); }}
复制代码


对应 apollo 读写数据的代码在 test 包下已经有了,拿过来稍加改动就可以了



完整的代码实现可以在 github 上查看:https://github.com/fengjx/Sentinel,整个改动没有修改一行源码,只是新增了一些类,方便后续升级不会引起代码冲突。

改造后的效果

dashboard 配置



apollo 配置


升级 &版本维护

本项目从 sentinel 官方 github 仓库 fork,只针对 dashboard 模块进行修改,保持与官方发布版本同步修改,版本对应关系


相关文档

发布于: 刚刚阅读数: 3
用户头像

铁匠

关注

不予评判地关注当下 2018.04.15 加入

http://blog.fengjx.com

评论

发布
暂无评论
不改一行源码,实现 sentinel-dashboard 所有配置支持 apollo 持久化_微服务_铁匠_InfoQ写作社区